sdl12-compat: More YUV overlay work.

From 47cf66cbf52fa923d9fb0dfee36a5f47f78d0941 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Sun, 2 May 2021 22:09:43 -0400
Subject: [PATCH] More YUV overlay work.

Now queues the overlay to render over the surface texture during
PresentScreen, since SMPEG calls both SDL_DisplayYUVOverlay() and SDL_Flip(),
so we need to unify these two actions into a single rendered frame, the same
way we do with SDL_UpdateRects().

Fixes #49.
---
 src/SDL12_compat.c | 109 ++++++++++++++++++++++++++-------------------
 1 file changed, 62 insertions(+), 47 deletions(-)

diff --git a/src/SDL12_compat.c b/src/SDL12_compat.c
index 93d1680..c9b64b8 100644
--- a/src/SDL12_compat.c
+++ b/src/SDL12_compat.c
@@ -242,6 +242,15 @@ typedef struct SDL12_Overlay
     Uint32 UnusedBits :31;
 } SDL12_Overlay;
 
+typedef struct SDL12_YUVData  /* internal struct, not part of public SDL 1.2 API */
+{
+    SDL_Texture *texture20;
+    SDL_bool dirty;
+    Uint8 *pixelbuf;
+    Uint8 *pixels[3];
+    Uint16 pitches[3];
+} SDL12_YUVData;
+
 typedef struct
 {
     Uint32 hw_available :1;
@@ -781,6 +790,8 @@ static Uint32 VideoSurfacePresentTicks = 0;
 static Uint32 VideoSurfaceLastPresentTicks = 0;
 static SDL_Surface *VideoConvertSurface20 = NULL;
 static SDL_GLContext VideoGLContext20 = NULL;
+static SDL12_Overlay *QueuedDisplayOverlay12 = NULL;
+static SDL12_Rect QueuedDisplayOverlayDstRect12;
 static char *WindowTitle = NULL;
 static char *WindowIconTitle = NULL;
 static SDL_Surface *VideoIcon20 = NULL;
@@ -3813,6 +3824,15 @@ PresentScreen(void)
 
     SDL20_RenderClear(VideoRenderer20);
     SDL20_RenderCopy(VideoRenderer20, VideoTexture20, NULL, NULL);
+
+    /* Render any pending YUV overlay over the surface texture. */
+    if (QueuedDisplayOverlay12) {
+        SDL12_YUVData *hwdata = (SDL12_YUVData *) QueuedDisplayOverlay12->hwdata;
+        SDL_Rect dstrect20;
+        SDL20_RenderCopy(VideoRenderer20, hwdata->texture20, NULL, Rect12to20(&QueuedDisplayOverlayDstRect12, &dstrect20));
+        QueuedDisplayOverlay12 = NULL;
+    }
+
     SDL20_RenderPresent(VideoRenderer20);
     VideoSurfaceLastPresentTicks = SDL20_GetTicks();
     VideoSurfacePresentTicks = 0;
@@ -4204,17 +4224,6 @@ SDL_GetWMInfo(SDL_SysWMinfo * info)
     return 0; /* some programs only test against 0, not -1 */
 }
 
-
-typedef struct SDL12_YUVData
-{
-    SDL_Texture *texture20;
-    SDL_bool display_requested;
-    SDL12_Rect display_rect;
-    Uint8 *pixelbuf;
-    Uint8 *pixels[3];
-    Uint16 pitches[3];
-} SDL12_YUVData;
-
 DECLSPEC SDL12_Overlay * SDLCALL
 SDL_CreateYUVOverlay(int w, int h, Uint32 format12, SDL12_Surface *display12)
 {
@@ -4298,11 +4307,23 @@ SDL_CreateYUVOverlay(int w, int h, Uint32 format12, SDL12_Surface *display12)
 DECLSPEC int SDLCALL
 SDL_LockYUVOverlay(SDL12_Overlay *overlay12)
 {
+    SDL12_YUVData *hwdata;
+
     if (!overlay12) {
         return SDL20_InvalidParamError("overlay");
     }
 
-    overlay12->pixels = ((SDL12_YUVData *) overlay12->hwdata)->pixels;
+    /* SMPEG locks the YUV overlay, calls SDL_DisplayYUVOverlay() on it,
+       copies data to it then unlocks it, in that order, which _seems_ like an
+       app bug, but it works in 1.2. Most of SDL 1.2's yuv lock/unlock
+       implementations are just no-ops anyhow, so we upload during display,
+       if the overlay has been locked since last time (and even
+       if it is still locked), to accomodate this usage pattern. */
+
+    hwdata = (SDL12_YUVData *) overlay12->hwdata;
+    hwdata->dirty = SDL_TRUE;  /* assume the contents will change and we must upload */
+    overlay12->pixels = hwdata->pixels;
+
     return 0;  /* success */
 }
 
@@ -4319,56 +4340,50 @@ SDL_DisplayYUVOverlay(SDL12_Overlay *overlay12, SDL12_Rect *dstrect12)
         return SDL20_SetError("No software screen surface available");
     }
 
-    /* SMPEG locks the YUV overlay, calls SDL_DisplayYUVOverlay() on it, copies
-       data to it then unlocks it, in that order, which _seems_ like an app
-       bug, but it works in 1.2, so we tapdance to make that order work here. */
     hwdata = (SDL12_YUVData *) overlay12->hwdata;
-    if (overlay12->pixels == NULL) {   /* NULL == not locked, present now */
-        SDL_Rect dstrect20;
-        SDL20_RenderClear(VideoRenderer20);
-        SDL20_RenderCopy(VideoRenderer20, VideoTexture20, NULL, NULL);
-        SDL20_RenderCopy(VideoRenderer20, hwdata->texture20, NULL, Rect12to20(dstrect12, &dstrect20));
-        SDL20_RenderPresent(VideoRenderer20);
-        VideoSurfaceLastPresentTicks = SDL20_GetTicks();
-        VideoSurfacePresentTicks = 0;
-    } else {  /* locked! Note that we should display as soon as it unlocks. */
-        hwdata->display_requested = SDL_TRUE;
-        SDL20_memcpy(&hwdata->display_rect, dstrect12, sizeof (SDL12_Rect));
-    }
-
-    return 0;
-}
 
-DECLSPEC void SDLCALL
-SDL_UnlockYUVOverlay(SDL12_Overlay *overlay12)
-{
-    if (overlay12) {
-        SDL12_YUVData *hwdata = (SDL12_YUVData *) overlay12->hwdata;
-        SDL_Rect rect;
-        rect.x = rect.y = 0;
-        rect.w = overlay12->w;
-        rect.h = overlay12->h;
+    /* Upload contents if we've been locked, even if we're _still_ locked, to
+       work around an SMPEG quirk. */
+    if (hwdata->dirty) {
+        SDL_Rect rect20;
+        rect20.x = rect20.y = 0;
+        rect20.w = overlay12->w;
+        rect20.h = overlay12->h;
         if (overlay12->format == SDL12_IYUV_OVERLAY) {
-            SDL20_UpdateYUVTexture(hwdata->texture20, &rect,
+            SDL20_UpdateYUVTexture(hwdata->texture20, &rect20,
                                  hwdata->pixels[0], hwdata->pitches[0],
                                  hwdata->pixels[1], hwdata->pitches[1],
                                  hwdata->pixels[2], hwdata->pitches[2]);
         } else if (overlay12->format == SDL12_YV12_OVERLAY) {
-            SDL20_UpdateYUVTexture(hwdata->texture20, &rect,
+            SDL20_UpdateYUVTexture(hwdata->texture20, &rect20,
                                  hwdata->pixels[0], hwdata->pitches[0],
                                  hwdata->pixels[2], hwdata->pitches[2],
                                  hwdata->pixels[1], hwdata->pitches[1]);
         } else {
-            SDL20_UpdateTexture(hwdata->texture20, &rect, hwdata->pixels[0], hwdata->pitches[0]);
+            SDL20_UpdateTexture(hwdata->texture20, &rect20, hwdata->pixels[0], hwdata->pitches[0]);
         }
-        overlay12->pixels = NULL;
 
-        /* See comments about SMPEG in SDL_DisplayYUVOverlay() */
-        if (hwdata->display_requested) {
-            hwdata->display_requested = SDL_FALSE;
-            SDL_DisplayYUVOverlay(overlay12, &hwdata->display_rect);
+        if (overlay12->pixels == NULL) {  /* must leave it marked as dirty if still locked! */
+            hwdata->dirty = SDL_FALSE;
         }
     }
+
+    /* The app may or may not SDL_Flip() after this...queue this to render on the next present,
+       and start a timer going to force a present, in case they don't. */
+    FIXME("is it legal to display multiple yuv overlays?");  /* if so, this will need to be a list instead of a single pointer. */
+    QueuedDisplayOverlay12 = overlay12;
+    SDL20_memcpy(&QueuedDisplayOverlayDstRect12, dstrect12, sizeof (SDL12_Rect));
+    VideoSurfacePresentTicks = VideoSurfaceLastPresentTicks + 15;  /* flip it later. */
+
+    return 0;
+}
+
+DECLSPEC void SDLCALL
+SDL_UnlockYUVOverlay(SDL12_Overlay *overlay12)
+{
+    if (overlay12) {
+        overlay12->pixels = NULL;
+    }
 }
 
 DECLSPEC void SDLCALL