SDL: Added SDL_SetWindowSurfaceVSync() and SDL_GetWindowSurfaceVSync()

From dfe44452148ee8aa682f89e418202f94aa1c3175 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 27 May 2024 10:27:17 -0700
Subject: [PATCH] Added SDL_SetWindowSurfaceVSync() and
 SDL_GetWindowSurfaceVSync()

Fixes https://github.com/libsdl-org/SDL/issues/9347
---
 include/SDL3/SDL_render.h         |  4 +-
 include/SDL3/SDL_video.h          | 34 +++++++++++++++
 src/dynapi/SDL_dynapi.sym         |  2 +
 src/dynapi/SDL_dynapi_overrides.h |  2 +
 src/dynapi/SDL_dynapi_procs.h     |  2 +
 src/render/SDL_render.c           |  7 +++-
 src/video/SDL_sysvideo.h          |  2 +
 src/video/SDL_video.c             | 70 +++++++++++++++++++++++--------
 src/video/SDL_video_c.h           |  4 +-
 9 files changed, 106 insertions(+), 21 deletions(-)

diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h
index c1ab822280983..dda2546297f18 100644
--- a/include/SDL3/SDL_render.h
+++ b/include/SDL3/SDL_render.h
@@ -2142,6 +2142,8 @@ extern SDL_DECLSPEC int SDLCALL SDL_AddVulkanRenderSemaphores(SDL_Renderer *rend
 /**
  * Toggle VSync of the given renderer.
  *
+ * When a renderer is created, vsync defaults to SDL_RENDERER_VSYNC_DISABLED.
+ *
  * \param renderer The renderer to toggle
  * \param vsync the vertical refresh sync interval, 1 to synchronize present
  *              with every vertical refresh, 2 to synchronize present with
@@ -2168,7 +2170,7 @@ extern SDL_DECLSPEC int SDLCALL SDL_SetRenderVSync(SDL_Renderer *renderer, int v
  *
  * \param renderer The renderer to toggle
  * \param vsync an int filled with the current vertical refresh sync interval.
- *              See SDL_SetRenderVSync for the meaning of the value.
+ *              See SDL_SetRenderVSync() for the meaning of the value.
  * \returns 0 on success or a negative error code on failure; call
  *          SDL_GetError() for more information.
  *
diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h
index 9efc10c855960..84d0173327c98 100644
--- a/include/SDL3/SDL_video.h
+++ b/include/SDL3/SDL_video.h
@@ -1801,6 +1801,40 @@ extern SDL_DECLSPEC SDL_bool SDLCALL SDL_WindowHasSurface(SDL_Window *window);
  */
 extern SDL_DECLSPEC SDL_Surface *SDLCALL SDL_GetWindowSurface(SDL_Window *window);
 
+/**
+ * Toggle VSync for the window surface.
+ *
+ * When a window surface is created, vsync defaults to SDL_WINDOW_SURFACE_VSYNC_DISABLED.
+ *
+ * \param window the window
+ * \param vsync the vertical refresh sync interval, 1 to synchronize present with every vertical refresh, 2 to synchronize present with every second vertical refresh, etc., SDL_WINDOW_SURFACE_VSYNC_ADAPTIVE for late swap tearing (adaptive vsync), or SDL_WINDOW_SURFACE_VSYNC_DISABLED to disable. Not every value is supported by every driver, so you should check the return value to see whether the requested setting is supported.
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetWindowSurfaceVSync
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetWindowSurfaceVSync(SDL_Window *window, int vsync);
+
+#define SDL_WINDOW_SURFACE_VSYNC_DISABLED 0
+#define SDL_WINDOW_SURFACE_VSYNC_ADAPTIVE (-1)
+
+/**
+ * Get VSync for the window surface.
+ *
+ * \param window the window to query
+ * \param vsync an int filled with the current vertical refresh sync interval.
+ *              See SDL_SetWindowSurfaceVSync() for the meaning of the value.
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetWindowSurfaceVSync
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_GetWindowSurfaceVSync(SDL_Window *window, int *vsync);
+
 /**
  * Copy the window surface to the screen.
  *
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index f670900605a43..d91b6f14b665a 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -474,6 +474,7 @@ SDL3_0.0.0 {
     SDL_GetWindowSize;
     SDL_GetWindowSizeInPixels;
     SDL_GetWindowSurface;
+    SDL_GetWindowSurfaceVSync;
     SDL_GetWindowTitle;
     SDL_GlobDirectory;
     SDL_GlobStorageDirectory;
@@ -765,6 +766,7 @@ SDL3_0.0.0 {
     SDL_SetWindowResizable;
     SDL_SetWindowShape;
     SDL_SetWindowSize;
+    SDL_SetWindowSurfaceVSync;
     SDL_SetWindowTitle;
     SDL_SetWindowsMessageHook;
     SDL_SetX11EventHook;
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 629a12d8d31a2..967b6bfb2649a 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -499,6 +499,7 @@
 #define SDL_GetWindowSize SDL_GetWindowSize_REAL
 #define SDL_GetWindowSizeInPixels SDL_GetWindowSizeInPixels_REAL
 #define SDL_GetWindowSurface SDL_GetWindowSurface_REAL
+#define SDL_GetWindowSurfaceVSync SDL_GetWindowSurfaceVSync_REAL
 #define SDL_GetWindowTitle SDL_GetWindowTitle_REAL
 #define SDL_GlobDirectory SDL_GlobDirectory_REAL
 #define SDL_GlobStorageDirectory SDL_GlobStorageDirectory_REAL
@@ -790,6 +791,7 @@
 #define SDL_SetWindowResizable SDL_SetWindowResizable_REAL
 #define SDL_SetWindowShape SDL_SetWindowShape_REAL
 #define SDL_SetWindowSize SDL_SetWindowSize_REAL
+#define SDL_SetWindowSurfaceVSync SDL_SetWindowSurfaceVSync_REAL
 #define SDL_SetWindowTitle SDL_SetWindowTitle_REAL
 #define SDL_SetWindowsMessageHook SDL_SetWindowsMessageHook_REAL
 #define SDL_SetX11EventHook SDL_SetX11EventHook_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 1147fe02ff305..251660ff987d4 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -519,6 +519,7 @@ SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetWindowProperties,(SDL_Window *a),(a),ret
 SDL_DYNAPI_PROC(int,SDL_GetWindowSize,(SDL_Window *a, int *b, int *c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_GetWindowSizeInPixels,(SDL_Window *a, int *b, int *c),(a,b,c),return)
 SDL_DYNAPI_PROC(SDL_Surface*,SDL_GetWindowSurface,(SDL_Window *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_GetWindowSurfaceVSync,(SDL_Window *a, int *b),(a,b),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetWindowTitle,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(char**,SDL_GlobDirectory,(const char *a, const char *b, SDL_GlobFlags c, int *d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(char**,SDL_GlobStorageDirectory,(SDL_Storage *a, const char *b, const char *c, SDL_GlobFlags d, int *e),(a,b,c,d,e),return)
@@ -800,6 +801,7 @@ SDL_DYNAPI_PROC(int,SDL_SetWindowPosition,(SDL_Window *a, int b, int c),(a,b,c),
 SDL_DYNAPI_PROC(int,SDL_SetWindowResizable,(SDL_Window *a, SDL_bool b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetWindowShape,(SDL_Window *a, SDL_Surface *b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetWindowSize,(SDL_Window *a, int b, int c),(a,b,c),return)
+SDL_DYNAPI_PROC(int,SDL_SetWindowSurfaceVSync,(SDL_Window *a, int b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetWindowTitle,(SDL_Window *a, const char *b),(a,b),return)
 SDL_DYNAPI_PROC(void,SDL_SetWindowsMessageHook,(SDL_WindowsMessageHook a, void *b),(a,b),)
 SDL_DYNAPI_PROC(void,SDL_SetX11EventHook,(SDL_X11EventHook a, void *b),(a,b),)
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index d5701d8b3d814..24daf9a0b1b2c 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -4727,10 +4727,13 @@ int SDL_SetRenderVSync(SDL_Renderer *renderer, int vsync)
 
     renderer->wanted_vsync = vsync ? SDL_TRUE : SDL_FALSE;
 
-    /* for the software renderer, forward eventually the call to the WindowTexture renderer */
+    /* for the software renderer, forward the call to the WindowTexture renderer */
 #if SDL_VIDEO_RENDER_SW
     if (renderer->software) {
-        if (SDL_SetWindowTextureVSync(renderer->window, vsync) == 0) {
+        if (!renderer->window) {
+            return SDL_Unsupported();
+        }
+        if (SDL_SetWindowTextureVSync(NULL, renderer->window, vsync) == 0) {
             renderer->simulate_vsync = SDL_FALSE;
             return 0;
         }
diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h
index 9353bc1975bb6..1de2c3d74af29 100644
--- a/src/video/SDL_sysvideo.h
+++ b/src/video/SDL_sysvideo.h
@@ -262,6 +262,8 @@ struct SDL_VideoDevice
     int (*SetWindowKeyboardGrab)(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool grabbed);
     void (*DestroyWindow)(SDL_VideoDevice *_this, SDL_Window *window);
     int (*CreateWindowFramebuffer)(SDL_VideoDevice *_this, SDL_Window *window, SDL_PixelFormatEnum *format, void **pixels, int *pitch);
+    int (*SetWindowFramebufferVSync)(SDL_VideoDevice *_this, SDL_Window *window, int vsync);
+    int (*GetWindowFramebufferVSync)(SDL_VideoDevice *_this, SDL_Window *window, int *vsync);
     int (*UpdateWindowFramebuffer)(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rects, int numrects);
     void (*DestroyWindowFramebuffer)(SDL_VideoDevice *_this, SDL_Window *window);
     void (*OnWindowEnter)(SDL_VideoDevice *_this, SDL_Window *window);
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 29658b6a8c027..7f30b46ffa1b6 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -395,10 +395,35 @@ static int SDL_CreateWindowTexture(SDL_VideoDevice *_this, SDL_Window *window, S
     return 0;
 }
 
-static SDL_VideoDevice *_this = NULL;
-static SDL_AtomicInt SDL_messagebox_count;
+int SDL_SetWindowTextureVSync(SDL_VideoDevice *_this, SDL_Window *window, int vsync)
+{
+    SDL_WindowTextureData *data;
+
+    data = (SDL_WindowTextureData *)SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_TEXTUREDATA_POINTER, NULL);
+    if (!data) {
+        return -1;
+    }
+    if (!data->renderer) {
+        return -1;
+    }
+    return SDL_SetRenderVSync(data->renderer, vsync);
+}
 
-static int SDL_UpdateWindowTexture(SDL_VideoDevice *unused, SDL_Window *window, const SDL_Rect *rects, int numrects)
+static int SDL_GetWindowTextureVSync(SDL_VideoDevice *_this, SDL_Window *window, int *vsync)
+{
+    SDL_WindowTextureData *data;
+
+    data = (SDL_WindowTextureData *)SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_TEXTUREDATA_POINTER, NULL);
+    if (!data) {
+        return -1;
+    }
+    if (!data->renderer) {
+        return -1;
+    }
+    return SDL_GetRenderVSync(data->renderer, vsync);
+}
+
+static int SDL_UpdateWindowTexture(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rects, int numrects)
 {
     SDL_WindowTextureData *data;
     SDL_Rect rect;
@@ -430,24 +455,13 @@ static int SDL_UpdateWindowTexture(SDL_VideoDevice *unused, SDL_Window *window,
     return 0;
 }
 
-static void SDL_DestroyWindowTexture(SDL_VideoDevice *unused, SDL_Window *window)
+static void SDL_DestroyWindowTexture(SDL_VideoDevice *_this, SDL_Window *window)
 {
     SDL_ClearProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_TEXTUREDATA_POINTER);
 }
 
-int SDL_SetWindowTextureVSync(SDL_Window *window, int vsync)
-{
-    SDL_WindowTextureData *data;
-
-    data = (SDL_WindowTextureData *)SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_TEXTUREDATA_POINTER, NULL);
-    if (!data) {
-        return -1;
-    }
-    if (!data->renderer) {
-        return -1;
-    }
-    return SDL_SetRenderVSync(data->renderer, vsync);
-}
+static SDL_VideoDevice *_this = NULL;
+static SDL_AtomicInt SDL_messagebox_count;
 
 static int SDLCALL cmpmodes(const void *A, const void *B)
 {
@@ -3202,6 +3216,8 @@ static SDL_Surface *SDL_CreateWindowFramebuffer(SDL_Window *window)
                    !!! FIXME:  framebuffer at the right places; is it feasible we could have an
                    !!! FIXME:  accelerated OpenGL window and a second ends up in software? */
                 _this->CreateWindowFramebuffer = SDL_CreateWindowTexture;
+                _this->SetWindowFramebufferVSync = SDL_SetWindowTextureVSync;
+                _this->GetWindowFramebufferVSync = SDL_GetWindowTextureVSync;
                 _this->UpdateWindowFramebuffer = SDL_UpdateWindowTexture;
                 _this->DestroyWindowFramebuffer = SDL_DestroyWindowTexture;
                 created_framebuffer = SDL_TRUE;
@@ -3250,6 +3266,26 @@ SDL_Surface *SDL_GetWindowSurface(SDL_Window *window)
     return window->surface;
 }
 
+int SDL_SetWindowSurfaceVSync(SDL_Window *window, int vsync)
+{
+    CHECK_WINDOW_MAGIC(window, -1);
+
+    if (!_this->SetWindowFramebufferVSync) {
+        return SDL_Unsupported();
+    }
+    return _this->SetWindowFramebufferVSync(_this, window, vsync);
+}
+
+int SDL_GetWindowSurfaceVSync(SDL_Window *window, int *vsync)
+{
+    CHECK_WINDOW_MAGIC(window, -1);
+
+    if (!_this->GetWindowFramebufferVSync) {
+        return SDL_Unsupported();
+    }
+    return _this->GetWindowFramebufferVSync(_this, window, vsync);
+}
+
 int SDL_UpdateWindowSurface(SDL_Window *window)
 {
     SDL_Rect full_rect;
diff --git a/src/video/SDL_video_c.h b/src/video/SDL_video_c.h
index 7e06bde8d77be..3e8a694443d2d 100644
--- a/src/video/SDL_video_c.h
+++ b/src/video/SDL_video_c.h
@@ -24,6 +24,8 @@
 
 #include "SDL_internal.h"
 
+typedef struct SDL_VideoDevice SDL_VideoDevice;
+
 /**
  * Initialize the video subsystem, optionally specifying a video driver.
  *
@@ -55,7 +57,7 @@ extern int SDL_VideoInit(const char *driver_name);
  */
 extern void SDL_VideoQuit(void);
 
-extern int SDL_SetWindowTextureVSync(SDL_Window *window, int vsync);
+extern int SDL_SetWindowTextureVSync(SDL_VideoDevice *_this, SDL_Window *window, int vsync);
 
 extern int SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a);