7 Commits 1ac0ae9224 ... 56e0d052f1

Auteur SHA1 Bericht Datum
  Ryan C. Gordon 56e0d052f1 opengles2: Rework render targets. 5 dagen geleden
  Michael Fitzmayer 51925aa92e [N-Gage] Resolve hang on repeated app launch. 5 dagen geleden
  Frank Praznik e3393e6304 wayland: Queue the surface frame callback after the initial commit 5 dagen geleden
  Sam Lantinga 702f9d94cd Use SDL_HasWindows() 5 dagen geleden
  Michael Fitzmayer 77cd3872c4 [N-Gage] Add missing include, clean-up. 5 dagen geleden
  Michael Fitzmayer b9da2b8d97 [N-Gage] Add Gouraud-shaded triangle rasteriser 6 dagen geleden
  misscelan 37089cf0a8 Update for N-Gage - Audio is now double buffered (#15516) 5 dagen geleden

+ 28 - 14
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)

+ 165 - 159
src/audio/ngage/SDL_ngageaudio.cpp

@@ -75,13 +75,17 @@ void CAudio::ConstructL(TInt aLatency)
 CAudio::~CAudio()
 {
     if (iStream) {
+        // Stop the processing thread before stopping the stream.
+        StopThread();
+
         iStream->Stop();
 
-        while (iState != EStateDone) {
-            User::After(100000); // 100ms.
-        }
+        // MaoscBufferCopied(KErrAbort) can't fire here because the active
+        // scheduler is no longer pumping; force the state to avoid deadlock.
+        iState = EStateDone;
 
         delete iStream;
+        iStream = NULL;
     }
 }
 
@@ -98,89 +102,9 @@ 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()
@@ -196,7 +120,16 @@ void CAudio::StartThread()
 
     TInt err = iProcess.Create(_L("ProcessThread"), ProcessThreadCB, KDefaultStackSize * 2, heapMinSize, heapMaxSize, this);
     if (err == KErrNone) {
-        iProcess.SetPriority(EPriorityLess);
+        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 +145,211 @@ 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();
 
+    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 (device) {
+        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 {
-            device = NGAGE_GetAudioDeviceAddr();
+            /*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);
+        /*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;
-        Feed();
     } else if (aError == KErrAbort) {
-        // The stream has been stopped.
+        /* 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) {
-        // 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);

+ 19 - 1
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;
 

+ 20 - 17
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

+ 10 - 3
src/main/ngage/SDL_sysmain_main.cpp

@@ -87,9 +87,15 @@ GLDEF_C TInt E32Main()
               TInt targetLatency = 225;
               InitAudio(&targetLatency);
 
-              // Wait until audio is ready.
-              while (!AudioIsReady()) {
+              // Wait until audio is ready (timeout after ~5 seconds).
+              TInt audioWaitMs = 0;
+              while (!AudioIsReady() && audioWaitMs < 5000) {
                   User::After(100000); // 100ms.
+                  audioWaitMs += 100;
+              }
+              if (!AudioIsReady()) {
+                  SDL_Log("Error: Audio failed to initialise within timeout");
+                  User::Leave(KErrTimedOut);
               }
 
               // Create and start the rendering backend.
@@ -104,8 +110,8 @@ GLDEF_C TInt E32Main()
               // Start the active scheduler to handle events.
               CActiveScheduler::Start();
 
-              CleanupStack::PopAndDestroy(gRenderer);
               CleanupStack::PopAndDestroy(mainApp);
+              CleanupStack::PopAndDestroy(gRenderer);
 
               User::SwitchHeap(oldHeap);
 
@@ -154,6 +160,7 @@ static bool callbacks_initialized = false;
 
 static void ShutdownApp(SDL_AppResult result)
 {
+    callbacks_initialized = false;
     DeinitAudio();
     SDL_AppQuit(NULL, result);
     SDL_Quit();

+ 40 - 0
src/render/ngage/SDL_render_ngage.c

@@ -306,6 +306,35 @@ static bool NGAGE_QueueCopyEx(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SD
 
 static bool NGAGE_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, const float *xy, int xy_stride, const SDL_FColor *color, int color_stride, const float *uv, int uv_stride, int num_vertices, const void *indices, int num_indices, int size_indices, float scale_x, float scale_y)
 {
+    int count = indices ? num_indices : num_vertices;
+    NGAGE_Vertex *verts = (NGAGE_Vertex *)SDL_AllocateRenderVertices(renderer, count * sizeof(NGAGE_Vertex), 0, &cmd->data.draw.first);
+    if (!verts) {
+        return false;
+    }
+    cmd->data.draw.count = count;
+
+    for (int i = 0; i < count; i++) {
+        int src_idx = i;
+        if (indices) {
+            if (size_indices == 4) {
+                src_idx = ((const Uint32 *)indices)[i];
+            } else if (size_indices == 2) {
+                src_idx = ((const Uint16 *)indices)[i];
+            } else {
+                src_idx = ((const Uint8 *)indices)[i];
+            }
+        }
+        const float *pos = (const float *)(((const Uint8 *)xy) + src_idx * xy_stride);
+        const SDL_FColor *c = (const SDL_FColor *)(((const Uint8 *)color) + src_idx * color_stride);
+
+        verts[i].x = (int)(pos[0] * scale_x);
+        verts[i].y = (int)(pos[1] * scale_y);
+        verts[i].color.r = (Uint8)(c->r * 255.0f);
+        verts[i].color.g = (Uint8)(c->g * 255.0f);
+        verts[i].color.b = (Uint8)(c->b * 255.0f);
+        verts[i].color.a = (Uint8)(c->a * 255.0f);
+    }
+
     return true;
 }
 
@@ -438,6 +467,17 @@ static bool NGAGE_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd
 
         case SDL_RENDERCMD_GEOMETRY:
         {
+            NGAGE_Vertex *verts = (NGAGE_Vertex *)(((Uint8 *)vertices) + cmd->data.draw.first);
+            const int count = cmd->data.draw.count;
+
+            if (phdata->viewport && (phdata->viewport->x || phdata->viewport->y)) {
+                for (int i = 0; i < count; i++) {
+                    verts[i].x += phdata->viewport->x;
+                    verts[i].y += phdata->viewport->y;
+                }
+            }
+
+            NGAGE_DrawGeometry(verts, count);
             break;
         }
         }

+ 198 - 8
src/render/ngage/SDL_render_ngage.cpp

@@ -22,6 +22,7 @@
 extern "C" {
 #endif
 
+#include "../../SDL_hints_c.h"
 #include "../../events/SDL_keyboard_c.h"
 #include "../SDL_sysrender.h"
 #include "SDL_internal.h"
@@ -110,6 +111,11 @@ void NGAGE_DrawPoints(NGAGE_Vertex *verts, const int count)
     gRenderer->DrawPoints(verts, count);
 }
 
+void NGAGE_DrawGeometry(NGAGE_Vertex *verts, const int count)
+{
+    gRenderer->DrawGeometry(verts, count);
+}
+
 void NGAGE_FillRects(NGAGE_Vertex *verts, const int count)
 {
     gRenderer->FillRects(verts, count);
@@ -149,6 +155,22 @@ 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 +188,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 +290,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)
@@ -414,6 +440,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();
@@ -701,6 +728,177 @@ void CRenderer::DrawPoints(NGAGE_Vertex *aVerts, const TInt aCount)
     }
 }
 
+// Gouraud-shaded triangle scanline fill directly into an EColor4K (XRGB4444) framebuffer.
+// Colors are interpolated per-scanline endpoint and per-pixel.
+static void FillTriangle(TUint16 *aPixels, TInt aStride,
+                         TInt aBmpW, TInt aBmpH,
+                         TInt aX0, TInt aY0, TInt aR0, TInt aG0, TInt aB0,
+                         TInt aX1, TInt aY1, TInt aR1, TInt aG1, TInt aB1,
+                         TInt aX2, TInt aY2, TInt aR2, TInt aG2, TInt aB2)
+{
+    // Sort vertices by Y ascending (bubble sort on 3 elements).
+    // Swap positions and colors together.
+#define SWAP3(ax, ay, ar, ag, ab, bx, by, br, bg, bb) \
+    do {                                              \
+        TInt _t;                                      \
+        _t = ax;                                      \
+        ax = bx;                                      \
+        bx = _t;                                      \
+        _t = ay;                                      \
+        ay = by;                                      \
+        by = _t;                                      \
+        _t = ar;                                      \
+        ar = br;                                      \
+        br = _t;                                      \
+        _t = ag;                                      \
+        ag = bg;                                      \
+        bg = _t;                                      \
+        _t = ab;                                      \
+        ab = bb;                                      \
+        bb = _t;                                      \
+    } while (0)
+
+    if (aY0 > aY1) {
+        SWAP3(aX0, aY0, aR0, aG0, aB0, aX1, aY1, aR1, aG1, aB1);
+    }
+    if (aY1 > aY2) {
+        SWAP3(aX1, aY1, aR1, aG1, aB1, aX2, aY2, aR2, aG2, aB2);
+    }
+    if (aY0 > aY1) {
+        SWAP3(aX0, aY0, aR0, aG0, aB0, aX1, aY1, aR1, aG1, aB1);
+    }
+#undef SWAP3
+
+    TInt totalHeight = aY2 - aY0;
+    if (totalHeight == 0) {
+        return;
+    }
+
+    // Walk upper half [y0..y1] and lower half [y1..y2].
+    for (TInt part = 0; part < 2; ++part) {
+        TInt segHeight = (part == 0) ? (aY1 - aY0) : (aY2 - aY1);
+        if (segHeight == 0) {
+            continue;
+        }
+        TInt yStart = (part == 0) ? aY0 : aY1;
+        TInt yEnd = (part == 0) ? aY1 : aY2;
+
+        for (TInt y = yStart; y <= yEnd; ++y) {
+            if (y < 0 || y >= aBmpH) {
+                continue;
+            }
+
+            // 16.16 interpolation factors for long edge (v0->v2) and short edge.
+            TInt tLong = ((y - aY0) << 16) / totalHeight;
+            TInt tShort = ((y - yStart) << 16) / segHeight;
+
+            // Interpolate X along both edges.
+            TInt xLong = aX0 + (((aX2 - aX0) * tLong) >> 16);
+            TInt xShort = (part == 0)
+                              ? aX0 + (((aX1 - aX0) * tShort) >> 16)
+                              : aX1 + (((aX2 - aX1) * tShort) >> 16);
+
+            // Interpolate color along both edges (values 0-255).
+            TInt rLong = aR0 + (((aR2 - aR0) * tLong) >> 16);
+            TInt gLong = aG0 + (((aG2 - aG0) * tLong) >> 16);
+            TInt bLong = aB0 + (((aB2 - aB0) * tLong) >> 16);
+            TInt rShort, gShort, bShort;
+            if (part == 0) {
+                rShort = aR0 + (((aR1 - aR0) * tShort) >> 16);
+                gShort = aG0 + (((aG1 - aG0) * tShort) >> 16);
+                bShort = aB0 + (((aB1 - aB0) * tShort) >> 16);
+            } else {
+                rShort = aR1 + (((aR2 - aR1) * tShort) >> 16);
+                gShort = aG1 + (((aG2 - aG1) * tShort) >> 16);
+                bShort = aB1 + (((aB2 - aB1) * tShort) >> 16);
+            }
+
+            // Determine left/right endpoints and their colors.
+            TInt xLeft, xRight;
+            TInt rLeft, gLeft, bLeft;
+            TInt rRight, gRight, bRight;
+            if (xLong < xShort) {
+                xLeft = xLong;
+                xRight = xShort;
+                rLeft = rLong;
+                gLeft = gLong;
+                bLeft = bLong;
+                rRight = rShort;
+                gRight = gShort;
+                bRight = bShort;
+            } else {
+                xLeft = xShort;
+                xRight = xLong;
+                rLeft = rShort;
+                gLeft = gShort;
+                bLeft = bShort;
+                rRight = rLong;
+                gRight = gLong;
+                bRight = bLong;
+            }
+
+            // Clamp X to bitmap width.
+            if (xLeft < 0) {
+                xLeft = 0;
+            }
+            if (xRight >= aBmpW) {
+                xRight = aBmpW - 1;
+            }
+
+            TInt spanWidth = xRight - xLeft;
+            TUint16 *row = aPixels + y * aStride;
+
+            // Compute per-pixel color deltas once per span (one division each)
+            // then step incrementally; avoids a division per pixel.
+            if (spanWidth > 0) {
+                TInt dr = ((rRight - rLeft) << 16) / spanWidth;
+                TInt dg = ((gRight - gLeft) << 16) / spanWidth;
+                TInt db = ((bRight - bLeft) << 16) / spanWidth;
+                TInt r = rLeft << 16;
+                TInt g = gLeft << 16;
+                TInt b = bLeft << 16;
+                for (TInt x = xLeft; x <= xRight; ++x) {
+                    // Pack to XRGB4444.
+                    row[x] = (TUint16)((((r >> 16) >> 4) << 8) | (((g >> 16) >> 4) << 4) | ((b >> 16) >> 4));
+                    r += dr;
+                    g += dg;
+                    b += db;
+                }
+            } else {
+                row[xLeft] = (TUint16)(((rLeft >> 4) << 8) | ((gLeft >> 4) << 4) | (bLeft >> 4));
+            }
+        }
+    }
+}
+
+void CRenderer::DrawGeometry(NGAGE_Vertex *aVerts, const TInt aCount)
+{
+    if (aCount < 3) {
+        return;
+    }
+
+    CFbsBitmap *bmp = GetCurrentBitmap();
+    if (bmp) {
+        TUint16 *pixels = reinterpret_cast<TUint16 *>(bmp->DataAddress());
+        if (pixels) {
+            TSize bmpSize = bmp->SizeInPixels();
+            TInt stride = CFbsBitmap::ScanLineLength(bmpSize.iWidth, EColor4K) / 2;
+
+            for (TInt i = 0; i + 2 < aCount; i += 3) {
+                FillTriangle(pixels, stride,
+                             bmpSize.iWidth, bmpSize.iHeight,
+                             aVerts[i].x, aVerts[i].y,
+                             aVerts[i].color.r, aVerts[i].color.g, aVerts[i].color.b,
+                             aVerts[i + 1].x, aVerts[i + 1].y,
+                             aVerts[i + 1].color.r, aVerts[i + 1].color.g, aVerts[i + 1].color.b,
+                             aVerts[i + 2].x, aVerts[i + 2].y,
+                             aVerts[i + 2].color.r, aVerts[i + 2].color.g, aVerts[i + 2].color.b);
+            }
+            return;
+        }
+    }
+}
+
 void CRenderer::FillRects(NGAGE_Vertex *aVerts, const TInt aCount)
 {
     CFbsBitGc *gc = GetCurrentGc();
@@ -934,14 +1132,6 @@ void CRenderer::HandleEvent(const TWsEvent &aWsEvent)
         timestamp = SDL_GetPerformanceCounter();
         SDL_SendKeyboardKey(timestamp, 1, aWsEvent.Key()->iCode, ConvertScancode(aWsEvent.Key()->iScanCode), true);
 
-        if (aWsEvent.Key()->iScanCode == EStdKeyHash) {
-            if (iShowFPS) {
-                iShowFPS = EFalse;
-            } else {
-                iShowFPS = ETrue;
-            }
-        }
-
         break;
     case EEventKeyUp: /* Key events */
         timestamp = SDL_GetPerformanceCounter();

+ 7 - 0
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
@@ -99,6 +103,7 @@ bool NGAGE_CreateTextureData(NGAGE_TextureData *data, const int width, const int
 void NGAGE_DestroyTextureData(NGAGE_TextureData *data);
 void *NGAGE_GetBitmapDataAddress(NGAGE_TextureData *data);
 int NGAGE_GetBitmapScanLineLength(NGAGE_TextureData *data);
+void NGAGE_DrawGeometry(NGAGE_Vertex *verts, const int count);
 void NGAGE_DrawLines(NGAGE_Vertex *verts, const int count);
 void NGAGE_DrawPoints(NGAGE_Vertex *verts, const int count);
 void NGAGE_FillRects(NGAGE_Vertex *verts, const int count);
@@ -108,6 +113,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
 }

+ 2 - 0
src/render/ngage/SDL_render_ngage_c.hpp

@@ -40,12 +40,14 @@ class CRenderer : public MDirectScreenAccess
     bool CreateTextureData(NGAGE_TextureData *aTextureData, const TInt aWidth, const TInt aHeight, const TInt aAccess);
     void DrawLines(NGAGE_Vertex *aVerts, const TInt aCount);
     void DrawPoints(NGAGE_Vertex *aVerts, const TInt aCount);
+    void DrawGeometry(NGAGE_Vertex *aVerts, const TInt aCount);
     void FillRects(NGAGE_Vertex *aVerts, const TInt aCount);
     void Flip();
     void SetDrawColor(TUint32 iColor);
     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);

+ 24 - 57
src/render/opengles2/SDL_render_gles2.c

@@ -54,15 +54,6 @@
  * Context structures                                                                            *
  *************************************************************************************************/
 
-typedef struct GLES2_FBOList GLES2_FBOList;
-
-struct GLES2_FBOList
-{
-    Uint32 w, h;
-    GLuint FBO;
-    GLES2_FBOList *next;
-};
-
 typedef struct
 {
     GLuint texture;
@@ -71,6 +62,7 @@ typedef struct
 typedef struct
 {
     GLuint texture;
+    GLuint fbo;  // framebuffer object; this is zero unless this texture is a render target.
     bool texture_external;
     GLenum texture_type;
     GLenum pixel_format;
@@ -90,7 +82,6 @@ typedef struct
     SDL_ScaleMode texture_scale_mode;
     SDL_TextureAddressMode texture_address_mode_u;
     SDL_TextureAddressMode texture_address_mode_v;
-    GLES2_FBOList *fbo;
 } GLES2_TextureData;
 
 typedef enum
@@ -193,7 +184,6 @@ typedef struct GLES2_RenderData
 #define SDL_PROC(ret, func, params) ret (APIENTRY *func) params;
 #include "SDL_gles2funcs.h"
 #undef SDL_PROC
-    GLES2_FBOList *framebuffers;
     GLuint window_framebuffer;
 
     GLuint shader_id_cache[GLES2_SHADER_COUNT];
@@ -301,23 +291,6 @@ static bool GLES2_LoadFunctions(GLES2_RenderData *data)
     return true;
 }
 
-static GLES2_FBOList *GLES2_GetFBO(GLES2_RenderData *data, Uint32 w, Uint32 h)
-{
-    GLES2_FBOList *result = data->framebuffers;
-    while ((result) && ((result->w != w) || (result->h != h))) {
-        result = result->next;
-    }
-    if (!result) {
-        result = (GLES2_FBOList *)SDL_malloc(sizeof(GLES2_FBOList));
-        result->w = w;
-        result->h = h;
-        data->glGenFramebuffers(1, &result->FBO);
-        result->next = data->framebuffers;
-        data->framebuffers = result;
-    }
-    return result;
-}
-
 static bool GLES2_ActivateRenderer(SDL_Renderer *renderer)
 {
     GLES2_RenderData *data = (GLES2_RenderData *)renderer->internal;
@@ -1685,14 +1658,6 @@ static void GLES2_DestroyRenderer(SDL_Renderer *renderer)
         }
 
         if (data->context) {
-            while (data->framebuffers) {
-                GLES2_FBOList *nextnode = data->framebuffers->next;
-                data->glDeleteFramebuffers(1, &data->framebuffers->FBO);
-                GL_CheckError("", renderer);
-                SDL_free(data->framebuffers);
-                data->framebuffers = nextnode;
-            }
-
 #if USE_VERTEX_BUFFER_OBJECTS
             data->glDeleteBuffers(SDL_arraysize(data->vertex_buffers), data->vertex_buffers);
             GL_CheckError("", renderer);
@@ -1956,6 +1921,11 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD
     if (texture->format != SDL_PIXELFORMAT_EXTERNAL_OES) {
         renderdata->glTexImage2D(data->texture_type, 0, format, texture->w, texture->h, 0, format, type, NULL);
         if (!GL_CheckError("glTexImage2D()", renderer)) {
+            if (!data->texture_external) {
+                renderdata->glDeleteTextures(1, &data->texture);
+            }
+            SDL_free(data->pixel_data);
+            SDL_free(data);
             return false;
         }
     }
@@ -1965,9 +1935,20 @@ static bool GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SD
     SDL_SetNumberProperty(SDL_GetTextureProperties(texture), SDL_PROP_TEXTURE_OPENGLES2_TEXTURE_TARGET_NUMBER, data->texture_type);
 
     if (texture->access == SDL_TEXTUREACCESS_TARGET) {
-        data->fbo = GLES2_GetFBO((GLES2_RenderData *)renderer->internal, texture->w, texture->h);
-    } else {
-        data->fbo = NULL;
+        renderdata->glGenFramebuffers(1, &data->fbo);
+        renderdata->glBindFramebuffer(GL_FRAMEBUFFER, data->fbo);
+        renderdata->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, data->texture_type, data->texture, 0);
+        const GLenum status = renderdata->glCheckFramebufferStatus(GL_FRAMEBUFFER);
+        renderdata->glBindFramebuffer(GL_FRAMEBUFFER, renderer->target ? ((GLES2_TextureData *)renderer->target->internal)->fbo : renderdata->window_framebuffer);  // rebind previous fbo.
+        if (status != GL_FRAMEBUFFER_COMPLETE) {
+            renderdata->glDeleteFramebuffers(1, &data->fbo);
+            if (!data->texture_external) {
+                renderdata->glDeleteTextures(1, &data->texture);
+            }
+            SDL_free(data->pixel_data);
+            SDL_free(data);
+            return SDL_SetError("Texture framebuffer was incomplete");
+        }
     }
 
     return GL_CheckError("", renderer);
@@ -2206,24 +2187,8 @@ static void GLES2_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
 static bool GLES2_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 {
     GLES2_RenderData *data = (GLES2_RenderData *)renderer->internal;
-    GLES2_TextureData *texturedata = NULL;
-    GLenum status;
-
     data->drawstate.viewport_dirty = true;
-
-    if (!texture) {
-        data->glBindFramebuffer(GL_FRAMEBUFFER, data->window_framebuffer);
-    } else {
-        texturedata = (GLES2_TextureData *)texture->internal;
-        data->glBindFramebuffer(GL_FRAMEBUFFER, texturedata->fbo->FBO);
-        // TODO: check if texture pixel format allows this operation
-        data->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texturedata->texture_type, texturedata->texture, 0);
-        // Check FBO status
-        status = data->glCheckFramebufferStatus(GL_FRAMEBUFFER);
-        if (status != GL_FRAMEBUFFER_COMPLETE) {
-            return SDL_SetError("glFramebufferTexture2D() failed");
-        }
-    }
+    data->glBindFramebuffer(GL_FRAMEBUFFER, texture ? ((GLES2_TextureData *)texture->internal)->fbo : data->window_framebuffer);
     return true;
 }
 
@@ -2244,6 +2209,9 @@ static void GLES2_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture)
 
     // Destroy the texture
     if (tdata) {
+        if (tdata->fbo) {
+            data->glDeleteFramebuffers(1, &tdata->fbo);
+        }
         if (tdata->texture && !tdata->texture_external) {
             data->glDeleteTextures(1, &tdata->texture);
         }
@@ -2433,7 +2401,6 @@ static bool GLES2_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL
     data->glGenBuffers(SDL_arraysize(data->vertex_buffers), data->vertex_buffers);
 #endif
 
-    data->framebuffers = NULL;
     data->glGetIntegerv(GL_FRAMEBUFFER_BINDING, &window_framebuffer);
     data->window_framebuffer = (GLuint)window_framebuffer;
 

+ 1 - 3
src/video/uikit/SDL_uikitevents.m

@@ -47,9 +47,7 @@ static BOOL UIKit_EventPumpEnabled = YES;
     bool wants_observation = (UIKit_EventPumpEnabled || SDL_HasMainCallbacks());
     if (!wants_observation) {
         // Make sure no windows have active animation callbacks
-        int num_windows = 0;
-        SDL_free(SDL_GetWindows(&num_windows));
-        if (num_windows > 0) {
+        if (SDL_HasWindows()) {
             wants_observation = true;
         }
     }

+ 13 - 12
src/video/wayland/SDL_waylandwindow.c

@@ -2299,7 +2299,7 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
         wl_surface_commit(data->surface);
     }
 
-    // Make sure the window can't be resized to 0 or it can be spuriously closed by the window manager.
+    // Make sure the window can't be resized to 0, or it can be spuriously closed by the window manager.
     data->system_limits.min_width = SDL_max(data->system_limits.min_width, 1);
     data->system_limits.min_height = SDL_max(data->system_limits.min_height, 1);
 
@@ -2342,6 +2342,13 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
         }
     }
 
+    // No frame callback on an external surface, as it may already have one attached.
+    if (!(window->flags & SDL_WINDOW_EXTERNAL)) {
+        // Fire a callback when the compositor wants a new frame.
+        data->surface_frame_callback = wl_surface_frame(data->surface);
+        wl_callback_add_listener(data->surface_frame_callback, &surface_frame_listener, data);
+    }
+
     data->show_hide_sync_required = true;
     struct wl_callback *cb = wl_display_sync(_this->internal->display);
     wl_callback_add_listener(cb, &show_hide_sync_listener, (void *)((uintptr_t)window->id));
@@ -2408,6 +2415,11 @@ void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
 
     wind->shell_surface_status = WAYLAND_SHELL_SURFACE_STATUS_HIDDEN;
 
+    if (wind->surface_frame_callback) {
+        wl_callback_destroy(wind->surface_frame_callback);
+        wind->surface_frame_callback = NULL;
+    }
+
     if (wind->server_decoration) {
         zxdg_toplevel_decoration_v1_destroy(wind->server_decoration);
         wind->server_decoration = NULL;
@@ -2986,13 +2998,6 @@ bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Proper
         wl_callback_add_listener(data->gles_swap_frame_callback, &gles_swap_frame_listener, data);
     }
 
-    // No frame callback on external surfaces as it may already have one attached.
-    if (!external_surface) {
-        // Fire a callback when the compositor wants a new frame to set the surface damage region.
-        data->surface_frame_callback = wl_surface_frame(data->surface);
-        wl_callback_add_listener(data->surface_frame_callback, &surface_frame_listener, data);
-    }
-
     if (window->flags & SDL_WINDOW_TRANSPARENT) {
         if (_this->gl_config.alpha_size == 0) {
             _this->gl_config.alpha_size = 8;
@@ -3584,10 +3589,6 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
             WAYLAND_wl_event_queue_destroy(wind->gles_swap_frame_event_queue);
         }
 
-        if (wind->surface_frame_callback) {
-            wl_callback_destroy(wind->surface_frame_callback);
-        }
-
         if (!(window->flags & SDL_WINDOW_EXTERNAL)) {
             wl_surface_destroy(wind->surface);
         } else {