From 37089cf0a8b82776cc69fc54e1204601244ea83b Mon Sep 17 00:00:00 2001
From: misscelan <[EMAIL REDACTED]>
Date: Wed, 6 May 2026 16:39:23 +0200
Subject: [PATCH] Update for N-Gage - Audio is now double buffered (#15516)
[N-Gage] Audio is now double buffered to avoid stuttering and glitches. Some audio platform specific variables were exposed through SDL_Hints. Same method was used to display FPS. N-gage functions to obtain the current buffer and screen pitch were added to the render.
Adds hints:
SDL_AUDIO_NGAGE_LATENCY
SDL_AUDIO_NGAGE_SCHEDULER_TICK
SDL_AUDIO_NGAGE_PROCESS_TICK
SDL_AUDIO_NGAGE_PROCESS_PRIORITY
SDL_RENDER_SHOW_FPS
Adds functions to get current buffer address and pitch:
void *NGAGE_GetBackbufferAddress(void);
int NGAGE_GetBackbufferPitch(void);
---------
Co-authored-by: Michael Fitzmayer <mail@michael-fitzmayer.de>
Co-authored-by: Eddy Jansson <eloj@users.noreply.github.com>
---
src/audio/ngage/SDL_ngageaudio.c | 42 ++-
src/audio/ngage/SDL_ngageaudio.cpp | 357 +++++++++++++-----------
src/audio/ngage/SDL_ngageaudio.h | 20 +-
src/audio/ngage/SDL_ngageaudio.hpp | 37 +--
src/render/ngage/SDL_render_ngage.cpp | 35 ++-
src/render/ngage/SDL_render_ngage_c.h | 6 +
src/render/ngage/SDL_render_ngage_c.hpp | 1 +
7 files changed, 303 insertions(+), 195 deletions(-)
diff --git a/src/audio/ngage/SDL_ngageaudio.c b/src/audio/ngage/SDL_ngageaudio.c
index 5b964d84f7cf7..6e2ced16ac031 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 aff7ff0b161b6..687b2c597ce1a 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<CAudio *>(aPtr);
SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
- while (self->iStreamStarted) {
- if (device) {
+
+ 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);
- } else {
- device = NGAGE_GetAudioDeviceAddr();
+
+ /* 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);
}
- User::After(100000); // 100ms.
}
+
+ delete cleanup;
return KErrNone;
}
+static TBool 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();
+ CActiveScheduler *scheduler = new CActiveScheduler();
+ CActiveScheduler::Install(scheduler);
+
+ TRAPD(err, {
+ TInt latency = *(TInt *)aParams;
+ CAudio *audio = CAudio::NewL(latency);
+ CleanupStack::PushL(audio);
+
+ audio->iBufferReady = EFalse;
+
+ gAudioRunning = ETrue;
+ audio->Start();
+
+
+ TInt processTick = 5000; // Default 5ms
+ const char *tickHint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_PROCESS_TICK);
+ if (tickHint) {
+ processTick = SDL_atoi(tickHint) * 1000;
+ }
+
+
+ while (gAudioRunning)
+ {
+ TInt error;
+ CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle);
+
+ /*there is some mix data sound ready*/
+ if (audio->iBufferReady)
+ {
+ audio->iBufferReady = EFalse;
+
+ 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) {
- iStream->SetVolume(1);
+ 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();
- } else {
+ /* 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) {
+ if (aError == KErrNone)
+ {
iState = EStatePlaying;
- Feed();
- } else if (aError == KErrAbort) {
- // The stream has been stopped.
+ }
+ else if (aError == KErrAbort)
+ {
+ /* The stream has been stopped.*/
iState = EStateDone;
- } else {
+ }
+ 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) {
- // The number of samples played gets reset to zero when we restart
- // playback after underflow.
- iBaseSamplesPlayed = iSamplesWritten;
-
+
+ /* 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();
- Cancel();
-
- iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate8000Hz, TMdaAudioDataSettings::EChannelsMono);
+ TInt ignoredError;
+ TRAP(ignoredError, iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate8000Hz, TMdaAudioDataSettings::EChannelsMono));
+ /* This wakes up ProcessThreadCB so it can call SDL_PlaybackAudioThreadIterate*/
iState = EStatePlaying;
- Feed();
- return;
+ return;
} else if (aError != KErrNone) {
- // Handle error.
+
}
- // We shouldn't get here.
+ /* We shouldn't get here.*/
SDL_Log("%s: %d", SDL_FUNCTION, aError);
}
-static TBool gAudioRunning;
+
TBool AudioIsReady()
{
return gAudioRunning;
}
-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);
-
- gAudioRunning = ETrue;
- audio->Start();
- TBool once = EFalse;
-
- while (gAudioRunning) {
- // Allow active scheduler to process any events.
- TInt error;
- CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle);
-
- 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.
- }
-
- CleanupStack::PopAndDestroy(audio);
- });
- delete scheduler;
- delete cleanup;
- return err;
-}
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 ee3afedb7395f..d7b4a952ba454 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 68e714a83fa8b..4988b5031af91 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 cd312406afbe6..1207df7ed0c97 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 76a36199899cb..20e816caab1d2 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 97e4ff272ae70..201dafff38785 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);