Răsfoiți Sursa

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>
misscelan 5 zile în urmă
părinte
comite
37089cf0a8

+ 28 - 14
src/audio/ngage/SDL_ngageaudio.c

@@ -42,18 +42,20 @@ static bool NGAGEAUDIO_OpenDevice(SDL_AudioDevice *device)
     }
     }
     device->hidden = phdata;
     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);
         SDL_free(phdata);
         return false;
         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.format = SDL_AUDIO_S16LE;
     device->spec.channels = 1;
     device->spec.channels = 1;
@@ -64,6 +66,13 @@ static bool NGAGEAUDIO_OpenDevice(SDL_AudioDevice *device)
     return true;
     return true;
 }
 }
 
 
+/*********************************************
+
+NGAGEAUDIO_GetDeviceBuf -
+
+Return the buffer that is currently being filled by SDL
+
+**********************************************/
 static Uint8 *NGAGEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
 static Uint8 *NGAGEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
 {
 {
     SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
     SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
@@ -71,19 +80,24 @@ static Uint8 *NGAGEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
         *buffer_size = 0;
         *buffer_size = 0;
         return 0;
         return 0;
     }
     }
-
+     
     *buffer_size = device->buffer_size;
     *buffer_size = device->buffer_size;
-    return phdata->buffer;
+    
+    return phdata->buffer[phdata->fill_index];
 }
 }
 
 
+
+
 static void NGAGEAUDIO_CloseDevice(SDL_AudioDevice *device)
 static void NGAGEAUDIO_CloseDevice(SDL_AudioDevice *device)
 {
 {
     if (device->hidden) {
     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)
 static bool NGAGEAUDIO_Init(SDL_AudioDriverImpl *impl)

+ 197 - 160
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()
 void CAudio::RunL()
 {
 {
     iTimerActive = EFalse;
     iTimerActive = EFalse;
-    Feed();
+
 }
 }
 
 
 void CAudio::DoCancel()
 void CAudio::DoCancel()
@@ -194,9 +117,21 @@ void CAudio::StartThread()
     TInt heapMinSize = 8192;        // 8 KB initial heap size.
     TInt heapMinSize = 8192;        // 8 KB initial heap size.
     TInt heapMaxSize = 1024 * 1024; // 1 MB maximum heap size.
     TInt heapMaxSize = 1024 * 1024; // 1 MB maximum heap size.
 
 
+
     TInt err = iProcess.Create(_L("ProcessThread"), ProcessThreadCB, KDefaultStackSize * 2, heapMinSize, heapMaxSize, this);
     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();
         iProcess.Resume();
     } else {
     } else {
         SDL_Log("Error: Failed to create audio processing thread: %d", err);
         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)
 TInt CAudio::ProcessThreadCB(TAny *aPtr)
 {
 {
+    CTrapCleanup *cleanup = CTrapCleanup::New();
+    if (!cleanup)
+        return KErrNoMemory;
+  
     CAudio *self = static_cast<CAudio *>(aPtr);
     CAudio *self = static_cast<CAudio *>(aPtr);
     SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
     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);
             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;
     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)
 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;
         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();
         StartThread();
 
 
-    } else {
+        /* Kickstart: device is guaranteed valid now*/
+        this->iState = EStatePlaying;
+       
+
+    }
+    else
+    {
         SDL_Log("Error: Failed to open audio stream: %d", aError);
         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*/)
 void CAudio::MaoscBufferCopied(TInt aError, const TDesC8 & /*aBuffer*/)
 {
 {
-    if (aError == KErrNone) {
+    if (aError == KErrNone)
+    {
         iState = EStatePlaying;
         iState = EStatePlaying;
-        Feed();
-    } else if (aError == KErrAbort) {
-        // The stream has been stopped.
+    }
+    else if (aError == KErrAbort)
+    {
+        /* The stream has been stopped.*/
         iState = EStateDone;
         iState = EStateDone;
-    } else {
+    }
+    else
+    {
         SDL_Log("Error: Failed to copy audio buffer: %d", aError);
         SDL_Log("Error: Failed to copy audio buffer: %d", aError);
     }
     }
 }
 }
 
 
+/***************************************************
+ * MaoscPlayComplete -
+ *
+ * The result after playing the mixed chunk
+ *
+ * *******************************************************/
+
 void CAudio::MaoscPlayComplete(TInt 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;
-
+   
+    /* 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();
         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;
         iState = EStatePlaying;
-        Feed();
-        return;
 
 
+        return;
     } else if (aError != KErrNone) {
     } else if (aError != KErrNone) {
-        // Handle error.
+        
     }
     }
 
 
-    // We shouldn't get here.
+    /* We shouldn't get here.*/
     SDL_Log("%s: %d", SDL_FUNCTION, aError);
     SDL_Log("%s: %d", SDL_FUNCTION, aError);
 }
 }
 
 
-static TBool gAudioRunning;
+
 
 
 TBool AudioIsReady()
 TBool AudioIsReady()
 {
 {
     return gAudioRunning;
     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;
 RThread audioThread;
 
 
 void InitAudio(TInt *aLatency)
 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");
     _LIT(KAudioThreadName, "AudioThread");
 
 
     TInt err = audioThread.Create(KAudioThreadName, AudioThreadCB, KDefaultStackSize, 0, aLatency);
     TInt err = audioThread.Create(KAudioThreadName, AudioThreadCB, KDefaultStackSize, 0, aLatency);

+ 19 - 1
src/audio/ngage/SDL_ngageaudio.h

@@ -23,9 +23,27 @@
 #ifndef SDL_ngageaudio_h
 #ifndef SDL_ngageaudio_h
 #define 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
 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;
 } SDL_PrivateAudioData;
 
 

+ 20 - 17
src/audio/ngage/SDL_ngageaudio.hpp

@@ -42,6 +42,16 @@ TBool AudioIsReady();
 void InitAudio(TInt *aLatency);
 void InitAudio(TInt *aLatency);
 void DeinitAudio();
 void DeinitAudio();
 
 
+enum TAudioState
+{
+    EStateNone = 0,
+    EStateOpening,
+    EStatePlaying,
+    EStateWriting,
+    EStateDone
+};
+
+
 class CAudio : public CActive, public MMdaAudioOutputStreamCallback
 class CAudio : public CActive, public MMdaAudioOutputStreamCallback
 {
 {
   public:
   public:
@@ -50,49 +60,42 @@ class CAudio : public CActive, public MMdaAudioOutputStreamCallback
 
 
     void ConstructL(TInt aLatency);
     void ConstructL(TInt aLatency);
     void Start();
     void Start();
-    void Feed();
+
 
 
     void RunL();
     void RunL();
     void DoCancel();
     void DoCancel();
 
 
     static TInt ProcessThreadCB(TAny * /*aPtr*/);
     static TInt ProcessThreadCB(TAny * /*aPtr*/);
 
 
-    // From MMdaAudioOutputStreamCallback
     void MaoscOpenComplete(TInt aError);
     void MaoscOpenComplete(TInt aError);
     void MaoscBufferCopied(TInt aError, const TDesC8 &aBuffer);
     void MaoscBufferCopied(TInt aError, const TDesC8 &aBuffer);
     void MaoscPlayComplete(TInt aError);
     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:
   private:
     CAudio();
     CAudio();
     void StartThread();
     void StartThread();
     void StopThread();
     void StopThread();
 
 
-    CMdaAudioOutputStream *iStream;
+  
     TMdaAudioDataSettings iStreamSettings;
     TMdaAudioDataSettings iStreamSettings;
-    TBool iStreamStarted;
-
-    TPtr8 iBufDes;           // Descriptor for the buffer.
+   
     TInt iLatency;           // Latency target in ms
     TInt iLatency;           // Latency target in ms
     TInt iLatencySamples;    // Latency target in samples.
     TInt iLatencySamples;    // Latency target in samples.
     TInt iMinWrite;          // Min number of samples to write per turn.
     TInt iMinWrite;          // Min number of samples to write per turn.
     TInt iMaxWrite;          // Max 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;
     RTimer iTimer;
     TBool iTimerCreated;
     TBool iTimerCreated;
     TBool iTimerActive;
     TBool iTimerActive;
 
 
-    RThread iProcess;
 };
 };
 
 
 #endif // SDL_ngageaudio_hpp
 #endif // SDL_ngageaudio_hpp

+ 32 - 3
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
 #ifdef __cplusplus
 }
 }
 #endif
 #endif
@@ -166,6 +183,8 @@ CRenderer::CRenderer() : iRenderer(0), iDirectScreen(0), iScreenGc(0), iWsSessio
 
 
 CRenderer::~CRenderer()
 CRenderer::~CRenderer()
 {
 {
+    SDL_RemoveHintCallback(SDL_HINT_RENDER_NGAGE_SHOW_FPS, NGAGE_ShowFPSChanged, this);
+
     delete iRenderer;
     delete iRenderer;
     iRenderer = 0;
     iRenderer = 0;
 
 
@@ -266,6 +285,8 @@ void CRenderer::ConstructL()
         }
         }
         iDirectScreen->ScreenDevice()->SetAutoUpdate(ETrue);
         iDirectScreen->ScreenDevice()->SetAutoUpdate(ETrue);
     }
     }
+
+    SDL_AddHintCallback(SDL_HINT_RENDER_NGAGE_SHOW_FPS, NGAGE_ShowFPSChanged, this);
 }
 }
 
 
 void CRenderer::Restart(RDirectScreenAccess::TTerminationReasons aReason)
 void CRenderer::Restart(RDirectScreenAccess::TTerminationReasons aReason)
@@ -336,6 +357,8 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec
         return false;
         return false;
     }
     }
 
 
+   
+
     NGAGE_TextureData *phdata = (NGAGE_TextureData *)texture->internal;
     NGAGE_TextureData *phdata = (NGAGE_TextureData *)texture->internal;
     if (!phdata || !phdata->bitmap) {
     if (!phdata || !phdata->bitmap) {
         return false;
         return false;
@@ -346,7 +369,7 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec
 
 
     int sw = srcrect->w;
     int sw = srcrect->w;
     int sh = srcrect->h;
     int sh = srcrect->h;
-
+   
     // Fast path: render target texture with no color mod.
     // Fast path: render target texture with no color mod.
     // BitBlt directly from its bitmap — DataAddress() is unreliable
     // BitBlt directly from its bitmap — DataAddress() is unreliable
     // for bitmaps that have been drawn into via a CFbsBitGc.
     // 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);
     SDL_GetTextureBlendMode(texture, &blend);
     bool no_color_key = (blend != SDL_BLENDMODE_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();
         CFbsBitGc *gc = GetCurrentGc();
         if (gc) {
         if (gc) {
             TRect aSource(TPoint(srcrect->x, srcrect->y), TSize(sw, sh));
             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;
         return true;
     }
     }
 
 
+
     // Fast path: color-key with no color mod and no scale.
     // Fast path: color-key with no color mod and no scale.
     // Blit directly from the source bitmap into the destination, skipping transparent pixels.
     // 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) {
     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;
             return false;
         }
         }
     }
     }
+
     TSize scratch_size = iScratchBitmap->SizeInPixels();
     TSize scratch_size = iScratchBitmap->SizeInPixels();
     if (scratch_size.iWidth < sw || scratch_size.iHeight < sh) {
     if (scratch_size.iWidth < sw || scratch_size.iHeight < sh) {
         iScratchBitmap->Reset();
         iScratchBitmap->Reset();
@@ -438,6 +464,7 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec
     void *source = iPixelBufferA;
     void *source = iPixelBufferA;
     void *dest = iPixelBufferB;
     void *dest = iPixelBufferB;
 
 
+
     if (!no_color_mod) {
     if (!no_color_mod) {
         ApplyColorMod(dest, source, src_pitch, sw, sh, texture->color);
         ApplyColorMod(dest, source, src_pitch, sw, sh, texture->color);
         void *tmp = source;
         void *tmp = source;
@@ -934,13 +961,15 @@ void CRenderer::HandleEvent(const TWsEvent &aWsEvent)
         timestamp = SDL_GetPerformanceCounter();
         timestamp = SDL_GetPerformanceCounter();
         SDL_SendKeyboardKey(timestamp, 1, aWsEvent.Key()->iCode, ConvertScancode(aWsEvent.Key()->iScanCode), true);
         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 (aWsEvent.Key()->iScanCode == EStdKeyHash) {
             if (iShowFPS) {
             if (iShowFPS) {
                 iShowFPS = EFalse;
                 iShowFPS = EFalse;
             } else {
             } else {
                 iShowFPS = ETrue;
                 iShowFPS = ETrue;
             }
             }
-        }
+        }*/
 
 
         break;
         break;
     case EEventKeyUp: /* Key events */
     case EEventKeyUp: /* Key events */

+ 6 - 0
src/render/ngage/SDL_render_ngage_c.h

@@ -25,6 +25,10 @@
 #define NGAGE_SCREEN_WIDTH  176
 #define NGAGE_SCREEN_WIDTH  176
 #define NGAGE_SCREEN_HEIGHT 208
 #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
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
 #endif
 #endif
@@ -108,6 +112,8 @@ void NGAGE_SetDrawColor(const Uint32 color);
 void NGAGE_PumpEventsInternal(void);
 void NGAGE_PumpEventsInternal(void);
 void NGAGE_SuspendScreenSaverInternal(bool suspend);
 void NGAGE_SuspendScreenSaverInternal(bool suspend);
 void NGAGE_SetRenderTargetInternal(NGAGE_TextureData *target);
 void NGAGE_SetRenderTargetInternal(NGAGE_TextureData *target);
+void *NGAGE_GetBackbufferAddress(void);
+int NGAGE_GetBackbufferPitch(void);
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }

+ 1 - 0
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 SetClipRect(TInt aX, TInt aY, TInt aWidth, TInt aHeight);
     void UpdateFPS();
     void UpdateFPS();
     void SuspendScreenSaver(TBool aSuspend);
     void SuspendScreenSaver(TBool aSuspend);
+    void SetShowFPS(TBool aShow) { iShowFPS = aShow; }
 
 
     // Render target management.
     // Render target management.
     void SetRenderTarget(NGAGE_TextureData *aTarget);
     void SetRenderTarget(NGAGE_TextureData *aTarget);