diff --git a/src/audio/ngage/SDL_ngageaudio.c b/src/audio/ngage/SDL_ngageaudio.c index 5b964d84f7..6e2ced16ac 100644 --- a/src/audio/ngage/SDL_ngageaudio.c +++ b/src/audio/ngage/SDL_ngageaudio.c @@ -42,18 +42,20 @@ static bool NGAGEAUDIO_OpenDevice(SDL_AudioDevice *device) } device->hidden = phdata; - phdata->buffer = SDL_calloc(1, device->buffer_size); - if (!phdata->buffer) { - SDL_OutOfMemory(); + phdata->buffer[0] = SDL_calloc(1, device->buffer_size); + phdata->buffer[1] = SDL_calloc(1, device->buffer_size); + if (!phdata->buffer[0] || !phdata->buffer[1]) + { + SDL_Log("Error: Failed to allocate audio buffers"); + SDL_free(phdata->buffer[0]); + SDL_free(phdata->buffer[1]); SDL_free(phdata); return false; } - devptr = device; - // Since the phone can change the sample rate during a phone call, - // we set the sample rate to 8KHz to be safe. Even though it - // might be possible to adjust the sample rate dynamically, it's - // not supported by the current implementation. + phdata->fill_index = 0; + + devptr = device; device->spec.format = SDL_AUDIO_S16LE; device->spec.channels = 1; @@ -64,6 +66,13 @@ static bool NGAGEAUDIO_OpenDevice(SDL_AudioDevice *device) return true; } +/********************************************* + +NGAGEAUDIO_GetDeviceBuf - + +Return the buffer that is currently being filled by SDL + +**********************************************/ static Uint8 *NGAGEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden; @@ -71,19 +80,24 @@ static Uint8 *NGAGEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) *buffer_size = 0; return 0; } - + *buffer_size = device->buffer_size; - return phdata->buffer; + + return phdata->buffer[phdata->fill_index]; } + + static void NGAGEAUDIO_CloseDevice(SDL_AudioDevice *device) { if (device->hidden) { - SDL_free(device->hidden->buffer); - SDL_free(device->hidden); - } + SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden; - return; + SDL_free(phdata->buffer[0]); + SDL_free(phdata->buffer[1]); + SDL_free(phdata); + device->hidden = NULL; + } } static bool NGAGEAUDIO_Init(SDL_AudioDriverImpl *impl) diff --git a/src/audio/ngage/SDL_ngageaudio.cpp b/src/audio/ngage/SDL_ngageaudio.cpp index aff7ff0b16..687b2c597c 100644 --- a/src/audio/ngage/SDL_ngageaudio.cpp +++ b/src/audio/ngage/SDL_ngageaudio.cpp @@ -98,89 +98,12 @@ void CAudio::Start() } } -// Feeds more processed data to the audio stream. -void CAudio::Feed() -{ - // If a WriteL is already in progress, or we aren't even playing; - // do nothing! - if ((iState != EStateWriting) && (iState != EStatePlaying)) { - return; - } - // Figure out the number of samples that really have been played - // through the output. - TTimeIntervalMicroSeconds pos = iStream->Position(); - - TInt played = 8 * (pos.Int64() / TInt64(1000)).GetTInt(); // 8kHz. - - played += iBaseSamplesPlayed; - - // Determine the difference between the number of samples written to - // CMdaAudioOutputStream and the number of samples it has played. - // The difference is the amount of data in the buffers. - if (played < 0) { - played = 0; - } - - TInt buffered = iSamplesWritten - played; - if (buffered < 0) { - buffered = 0; - } - - if (iState == EStateWriting) { - return; - } - - // The trick for low latency: Do not let the buffers fill up beyond the - // latency desired! We write as many samples as the difference between - // the latency target (in samples) and the amount of data buffered. - TInt samplesToWrite = iLatencySamples - buffered; - - // Do not write very small blocks. This should improve efficiency, since - // writes to the streaming API are likely to be expensive. - if (samplesToWrite < iMinWrite) { - // Not enough data to write, set up a timer to fire after a while. - // Try againwhen it expired. - if (iTimerActive) { - return; - } - iTimerActive = ETrue; - SetActive(); - iTimer.After(iStatus, (1000 * iLatency) / 8); - return; - } - - // Do not write more than the set number of samples at once. - int numSamples = samplesToWrite; - if (numSamples > iMaxWrite) { - numSamples = iMaxWrite; - } - - SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr(); - if (device) { - SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden; - - iBufDes.Set(phdata->buffer, 2 * numSamples, 2 * numSamples); - iStream->WriteL(iBufDes); - iState = EStateWriting; - - // Keep track of the number of samples written (for latency calculations). - iSamplesWritten += numSamples; - } else { - // Output device not ready yet. Let's go for another round. - if (iTimerActive) { - return; - } - iTimerActive = ETrue; - SetActive(); - iTimer.After(iStatus, (1000 * iLatency) / 8); - } -} void CAudio::RunL() { iTimerActive = EFalse; - Feed(); + } void CAudio::DoCancel() @@ -194,9 +117,21 @@ void CAudio::StartThread() TInt heapMinSize = 8192; // 8 KB initial heap size. TInt heapMaxSize = 1024 * 1024; // 1 MB maximum heap size. + TInt err = iProcess.Create(_L("ProcessThread"), ProcessThreadCB, KDefaultStackSize * 2, heapMinSize, heapMaxSize, this); - if (err == KErrNone) { - iProcess.SetPriority(EPriorityLess); + if (err == KErrNone) + { + TThreadPriority prio = EPriorityLess; + + const char *prioHint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_PROCESS_PRIORITY); + if (prioHint) { + // Symbian priorities: 10 (MuchLess), 20 (Less), 30 (Normal), 40 (More) + prio = (TThreadPriority)SDL_atoi(prioHint); + RThread().SetPriority(prio); + } + + + iProcess.SetPriority(prio); iProcess.Resume(); } else { SDL_Log("Error: Failed to create audio processing thread: %d", err); @@ -212,138 +147,240 @@ void CAudio::StopThread() } } +/*************************************************** +* ProcessThreadCB - +* +* This thread calls the SDL mixer when the buffer is ready and self->iState == EStatePlaying (basically other than initial stated, when not writing) +* +* It only mixes, never calls WriteL +****************************************************/ + TInt CAudio::ProcessThreadCB(TAny *aPtr) { + CTrapCleanup *cleanup = CTrapCleanup::New(); + if (!cleanup) + return KErrNoMemory; + CAudio *self = static_cast(aPtr); SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr(); - while (self->iStreamStarted) { - if (device) { - SDL_PlaybackAudioThreadIterate(device); - } else { - device = NGAGE_GetAudioDeviceAddr(); - } - User::After(100000); // 100ms. + + TInt processTick = 40000; // Default 40ms + const char *tickHint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_PROCESS_TICK); + if (tickHint) + { + processTick = SDL_atoi(tickHint) * 1000; } + + while (self->iStreamStarted) + { + if (self->iState == EStatePlaying && !self->iBufferReady) + { + /* Ask SDL to mix audio into buffer[fill_index]*/ + SDL_PlaybackAudioThreadIterate(device); + + /* Signal AudioThreadCB to write it*/ + self->iBufferReady = ETrue; + } + else + { + /*if we are not ready to obtain the mix data we sleep a bit this thread*/ + User::After(processTick); + } + } + + delete cleanup; return KErrNone; } -void CAudio::MaoscOpenComplete(TInt aError) -{ - if (aError == KErrNone) { - iStream->SetVolume(1); - iStreamStarted = ETrue; - StartThread(); - - } else { - SDL_Log("Error: Failed to open audio stream: %d", aError); - } -} - -void CAudio::MaoscBufferCopied(TInt aError, const TDesC8 & /*aBuffer*/) -{ - if (aError == KErrNone) { - iState = EStatePlaying; - Feed(); - } else if (aError == KErrAbort) { - // The stream has been stopped. - iState = EStateDone; - } else { - SDL_Log("Error: Failed to copy audio buffer: %d", aError); - } -} - -void CAudio::MaoscPlayComplete(TInt aError) -{ - // If we finish due to an underflow, we'll need to restart playback. - // Normally KErrUnderlow is raised at stream end, but in our case the API - // should never see the stream end -- we are continuously feeding it more - // data! Many underflow errors mean that the latency target is too low. - if (aError == KErrUnderflow) { - // The number of samples played gets reset to zero when we restart - // playback after underflow. - iBaseSamplesPlayed = iSamplesWritten; - - iStream->Stop(); - Cancel(); - - iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate8000Hz, TMdaAudioDataSettings::EChannelsMono); - - iState = EStatePlaying; - Feed(); - return; - - } else if (aError != KErrNone) { - // Handle error. - } - - // We shouldn't get here. - SDL_Log("%s: %d", SDL_FUNCTION, aError); -} - static TBool gAudioRunning; - -TBool AudioIsReady() -{ - return gAudioRunning; -} +/*************************************************** +* AudioThreadCB - +* +* This thread owns the scheduler and calls WriteL, wich queues the assigned sound buffer to be played +****************************************************/ TInt AudioThreadCB(TAny *aParams) { CTrapCleanup *cleanup = CTrapCleanup::New(); - if (!cleanup) { - return KErrNoMemory; - } - CActiveScheduler *scheduler = new CActiveScheduler(); - if (!scheduler) { - delete cleanup; - return KErrNoMemory; - } - CActiveScheduler::Install(scheduler); + + TRAPD(err, { + TInt latency = *(TInt *)aParams; + CAudio *audio = CAudio::NewL(latency); + CleanupStack::PushL(audio); - TRAPD(err, - { - TInt latency = *(TInt *)aParams; - CAudio *audio = CAudio::NewL(latency); - CleanupStack::PushL(audio); + audio->iBufferReady = EFalse; - gAudioRunning = ETrue; - audio->Start(); - TBool once = EFalse; + gAudioRunning = ETrue; + audio->Start(); + - while (gAudioRunning) { - // Allow active scheduler to process any events. - TInt error; - CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle); + TInt processTick = 5000; // Default 5ms + const char *tickHint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_PROCESS_TICK); + if (tickHint) { + processTick = SDL_atoi(tickHint) * 1000; + } - if (!once) { - SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr(); - if (device) { - // Stream ready; start feeding audio data. - // After feeding it once, the callbacks will take over. - audio->iState = CAudio::EStatePlaying; - audio->Feed(); - once = ETrue; - } - } - User::After(100000); // 100ms. - } + while (gAudioRunning) + { + TInt error; + CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle); + + /*there is some mix data sound ready*/ + if (audio->iBufferReady) + { + audio->iBufferReady = EFalse; - CleanupStack::PopAndDestroy(audio); - }); + SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr(); + + if (device && device->hidden) + { + SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden; + audio->iState = EStateWriting; + /*sends the chuck mixed to the queue*/ + audio->iBufDes.Set(phdata->buffer[phdata->fill_index], device->buffer_size, device->buffer_size); + TRAPD(werr, audio->iStream->WriteL(audio->iBufDes)); + + if (werr != KErrNone) + { + /*asks ProcessThreadCB to bring another mix chunk*/ + audio->iState = EStatePlaying; + } + else + { + /*swap buffers so while this buffer is being played we can get the mix of the next one if we can*/ + phdata->fill_index = 1 - phdata->fill_index; + } + } + } + + /*sleep a bit this thread not to hog the CPU*/ + User::After(processTick); + } + + CleanupStack::PopAndDestroy(audio); + }); delete scheduler; delete cleanup; return err; } +/*************************************************** +* MaoscOpenComplete - +* +* Opens the audiostream +* +* *******************************************************/ + +void CAudio::MaoscOpenComplete(TInt aError) +{ + if (aError == KErrNone) + { + /*setting the volume to max, users can change the volume later of their channels individually in code*/ + iStream->SetVolume(iStream->MaxVolume()); + iStreamStarted = ETrue; + + /* Wait until SDL has set devptr and hidden data*/ + SDL_AudioDevice *device = NULL; + while (!device || !device->hidden) { + User::After(10000); // 10ms poll + device = NGAGE_GetAudioDeviceAddr(); + } + + /* Now start the ProcessThreadCB thread*/ + StartThread(); + + /* Kickstart: device is guaranteed valid now*/ + this->iState = EStatePlaying; + + + } + else + { + SDL_Log("Error: Failed to open audio stream: %d", aError); + } +} + +/*************************************************** + * MaoscOpenComplete - + * + * This signals the mixed data has been finally copied to the designated audio buffer + * + * *******************************************************/ + +void CAudio::MaoscBufferCopied(TInt aError, const TDesC8 & /*aBuffer*/) +{ + if (aError == KErrNone) + { + iState = EStatePlaying; + } + else if (aError == KErrAbort) + { + /* The stream has been stopped.*/ + iState = EStateDone; + } + else + { + SDL_Log("Error: Failed to copy audio buffer: %d", aError); + } +} + +/*************************************************** + * MaoscPlayComplete - + * + * The result after playing the mixed chunk + * + * *******************************************************/ + +void CAudio::MaoscPlayComplete(TInt aError) +{ + + /* If we finish due to an underflow, we'll need to restart playback. + Normally KErrUnderlow is raised at stream end, but in our case the API + should never see the stream end -- we are continuously feeding it more + data! Many underflow errors mean that the latency target is too low.*/ + if (aError == KErrUnderflow) + { + /* Restart the stream hardware */ + iStream->Stop(); + TInt ignoredError; + TRAP(ignoredError, iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate8000Hz, TMdaAudioDataSettings::EChannelsMono)); + + /* This wakes up ProcessThreadCB so it can call SDL_PlaybackAudioThreadIterate*/ + iState = EStatePlaying; + + return; + } else if (aError != KErrNone) { + + } + + /* We shouldn't get here.*/ + SDL_Log("%s: %d", SDL_FUNCTION, aError); +} + + + +TBool AudioIsReady() +{ + return gAudioRunning; +} + + + RThread audioThread; void InitAudio(TInt *aLatency) { + // Check if the user has provided a custom latency value via a hint + const char *hint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_LATENCY); + if (hint) { + *aLatency = (TInt)SDL_atoi(hint); + } + _LIT(KAudioThreadName, "AudioThread"); TInt err = audioThread.Create(KAudioThreadName, AudioThreadCB, KDefaultStackSize, 0, aLatency); diff --git a/src/audio/ngage/SDL_ngageaudio.h b/src/audio/ngage/SDL_ngageaudio.h index ee3afedb73..d7b4a952ba 100644 --- a/src/audio/ngage/SDL_ngageaudio.h +++ b/src/audio/ngage/SDL_ngageaudio.h @@ -23,9 +23,27 @@ #ifndef SDL_ngageaudio_h #define SDL_ngageaudio_h +#ifndef SDL_HINT_AUDIO_NGAGE_LATENCY +#define SDL_HINT_AUDIO_NGAGE_LATENCY "SDL_AUDIO_NGAGE_LATENCY" +#endif + +#ifndef SDL_HINT_AUDIO_NGAGE_SCHEDULER_TICK +#define SDL_HINT_AUDIO_NGAGE_SCHEDULER_TICK "SDL_AUDIO_NGAGE_SCHEDULER_TICK" +#endif + +#ifndef SDL_HINT_AUDIO_NGAGE_PROCESS_TICK +#define SDL_HINT_AUDIO_NGAGE_PROCESS_TICK "SDL_AUDIO_NGAGE_PROCESS_TICK" +#endif + +#ifndef SDL_HINT_AUDIO_NGAGE_PROCESS_PRIORITY +#define SDL_HINT_AUDIO_NGAGE_PROCESS_PRIORITY "SDL_AUDIO_NGAGE_PROCESS_PRIORITY" +#endif typedef struct SDL_PrivateAudioData { - Uint8 *buffer; + Uint8 *buffer[2]; + int fill_index; /* Which buffer SDL is currently filling */ + int play_index; /* Which buffer the hardware is currently using*/ + int buffer_size; } SDL_PrivateAudioData; diff --git a/src/audio/ngage/SDL_ngageaudio.hpp b/src/audio/ngage/SDL_ngageaudio.hpp index 68e714a83f..4988b5031a 100644 --- a/src/audio/ngage/SDL_ngageaudio.hpp +++ b/src/audio/ngage/SDL_ngageaudio.hpp @@ -42,6 +42,16 @@ TBool AudioIsReady(); void InitAudio(TInt *aLatency); void DeinitAudio(); +enum TAudioState +{ + EStateNone = 0, + EStateOpening, + EStatePlaying, + EStateWriting, + EStateDone +}; + + class CAudio : public CActive, public MMdaAudioOutputStreamCallback { public: @@ -50,49 +60,42 @@ class CAudio : public CActive, public MMdaAudioOutputStreamCallback void ConstructL(TInt aLatency); void Start(); - void Feed(); + void RunL(); void DoCancel(); static TInt ProcessThreadCB(TAny * /*aPtr*/); - // From MMdaAudioOutputStreamCallback void MaoscOpenComplete(TInt aError); void MaoscBufferCopied(TInt aError, const TDesC8 &aBuffer); void MaoscPlayComplete(TInt aError); - enum - { - EStateNone = 0, - EStateOpening, - EStatePlaying, - EStateWriting, - EStateDone - } iState; + TAudioState iState; + CMdaAudioOutputStream *iStream; /*CMdaAudioOutputStream handler*/ + TPtr8 iBufDes; /* Descriptor for the buffer.*/ + TBool iStreamStarted; /* have we initialized the audio stream?*/ + RThread iProcess; /* thread handler */ + TBool iBufferReady; /* Signal AudioThreadCB the buffer is ready*/ private: CAudio(); void StartThread(); void StopThread(); - CMdaAudioOutputStream *iStream; + TMdaAudioDataSettings iStreamSettings; - TBool iStreamStarted; - - TPtr8 iBufDes; // Descriptor for the buffer. + TInt iLatency; // Latency target in ms TInt iLatencySamples; // Latency target in samples. TInt iMinWrite; // Min number of samples to write per turn. TInt iMaxWrite; // Max number of samples to write per turn. - TInt iBaseSamplesPlayed; // amples played before last restart. - TInt iSamplesWritten; // Number of samples written so far. + RTimer iTimer; TBool iTimerCreated; TBool iTimerActive; - RThread iProcess; }; #endif // SDL_ngageaudio_hpp \ No newline at end of file diff --git a/src/render/ngage/SDL_render_ngage.cpp b/src/render/ngage/SDL_render_ngage.cpp index cd312406af..1207df7ed0 100644 --- a/src/render/ngage/SDL_render_ngage.cpp +++ b/src/render/ngage/SDL_render_ngage.cpp @@ -149,6 +149,23 @@ void NGAGE_SetRenderTargetInternal(NGAGE_TextureData *target) } } +static void SDLCALL NGAGE_ShowFPSChanged(void *userdata, const char *name, const char *oldValue, const char *newValue) +{ + CRenderer *renderer = (CRenderer *)userdata; + renderer->SetShowFPS(SDL_GetStringBoolean(newValue, false)); +} + + +void *NGAGE_GetBackbufferAddress(void) +{ + return gRenderer->GetCurrentBitmap()->DataAddress(); +} + +int NGAGE_GetBackbufferPitch(void) +{ + return CFbsBitmap::ScanLineLength(NGAGE_SCREEN_WIDTH, EColor4K) / 2; +} + #ifdef __cplusplus } #endif @@ -166,6 +183,8 @@ CRenderer::CRenderer() : iRenderer(0), iDirectScreen(0), iScreenGc(0), iWsSessio CRenderer::~CRenderer() { + SDL_RemoveHintCallback(SDL_HINT_RENDER_NGAGE_SHOW_FPS, NGAGE_ShowFPSChanged, this); + delete iRenderer; iRenderer = 0; @@ -266,6 +285,8 @@ void CRenderer::ConstructL() } iDirectScreen->ScreenDevice()->SetAutoUpdate(ETrue); } + + SDL_AddHintCallback(SDL_HINT_RENDER_NGAGE_SHOW_FPS, NGAGE_ShowFPSChanged, this); } void CRenderer::Restart(RDirectScreenAccess::TTerminationReasons aReason) @@ -336,6 +357,8 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec return false; } + + NGAGE_TextureData *phdata = (NGAGE_TextureData *)texture->internal; if (!phdata || !phdata->bitmap) { return false; @@ -346,7 +369,7 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec int sw = srcrect->w; int sh = srcrect->h; - + // Fast path: render target texture with no color mod. // BitBlt directly from its bitmap — DataAddress() is unreliable // for bitmaps that have been drawn into via a CFbsBitGc. @@ -359,7 +382,8 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec SDL_GetTextureBlendMode(texture, &blend); bool no_color_key = (blend != SDL_BLENDMODE_BLEND); - if (phdata->gc && no_color_mod && no_scale && no_color_key) { + if (phdata->gc && no_color_mod && no_scale && no_color_key) + { CFbsBitGc *gc = GetCurrentGc(); if (gc) { TRect aSource(TPoint(srcrect->x, srcrect->y), TSize(sw, sh)); @@ -369,6 +393,7 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec return true; } + // Fast path: color-key with no color mod and no scale. // Blit directly from the source bitmap into the destination, skipping transparent pixels. if (no_color_mod && no_scale && !no_color_key && phdata->has_color_key) { @@ -414,6 +439,7 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec return false; } } + TSize scratch_size = iScratchBitmap->SizeInPixels(); if (scratch_size.iWidth < sw || scratch_size.iHeight < sh) { iScratchBitmap->Reset(); @@ -438,6 +464,7 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec void *source = iPixelBufferA; void *dest = iPixelBufferB; + if (!no_color_mod) { ApplyColorMod(dest, source, src_pitch, sw, sh, texture->color); void *tmp = source; @@ -934,13 +961,15 @@ void CRenderer::HandleEvent(const TWsEvent &aWsEvent) timestamp = SDL_GetPerformanceCounter(); SDL_SendKeyboardKey(timestamp, 1, aWsEvent.Key()->iCode, ConvertScancode(aWsEvent.Key()->iScanCode), true); + /* + commented out so it works with hints if (aWsEvent.Key()->iScanCode == EStdKeyHash) { if (iShowFPS) { iShowFPS = EFalse; } else { iShowFPS = ETrue; } - } + }*/ break; case EEventKeyUp: /* Key events */ diff --git a/src/render/ngage/SDL_render_ngage_c.h b/src/render/ngage/SDL_render_ngage_c.h index 76a3619989..20e816caab 100644 --- a/src/render/ngage/SDL_render_ngage_c.h +++ b/src/render/ngage/SDL_render_ngage_c.h @@ -25,6 +25,10 @@ #define NGAGE_SCREEN_WIDTH 176 #define NGAGE_SCREEN_HEIGHT 208 +#ifndef SDL_HINT_RENDER_NGAGE_SHOW_FPS +#define SDL_HINT_RENDER_NGAGE_SHOW_FPS "SDL_RENDER_NGAGE_SHOW_FPS" +#endif + #ifdef __cplusplus extern "C" { #endif @@ -108,6 +112,8 @@ void NGAGE_SetDrawColor(const Uint32 color); void NGAGE_PumpEventsInternal(void); void NGAGE_SuspendScreenSaverInternal(bool suspend); void NGAGE_SetRenderTargetInternal(NGAGE_TextureData *target); +void *NGAGE_GetBackbufferAddress(void); +int NGAGE_GetBackbufferPitch(void); #ifdef __cplusplus } diff --git a/src/render/ngage/SDL_render_ngage_c.hpp b/src/render/ngage/SDL_render_ngage_c.hpp index 97e4ff272a..201dafff38 100644 --- a/src/render/ngage/SDL_render_ngage_c.hpp +++ b/src/render/ngage/SDL_render_ngage_c.hpp @@ -46,6 +46,7 @@ class CRenderer : public MDirectScreenAccess void SetClipRect(TInt aX, TInt aY, TInt aWidth, TInt aHeight); void UpdateFPS(); void SuspendScreenSaver(TBool aSuspend); + void SetShowFPS(TBool aShow) { iShowFPS = aShow; } // Render target management. void SetRenderTarget(NGAGE_TextureData *aTarget);