SDL: Add `SDL_RenderSetVSync()`

From 4549769d7de1780686cc6cef0c613af9a072015f Mon Sep 17 00:00:00 2001
From: Misa <[EMAIL REDACTED]>
Date: Sun, 7 Mar 2021 15:20:45 -0800
Subject: [PATCH] Add `SDL_RenderSetVSync()`

Currently, if an application wants to toggle VSync, they'd have to tear
down the renderer and recreate it. This patch fixes that by letting
applications call SDL_RenderSetVSync().

This is the same as the patch in #3673, except it applies to all
renderers (including PSP, even thought it seems that the VSync flag is
disabled for that renderer). Furthermore, the renderer flags also change
as well, which #3673 didn't do. It is also an API instead of using hint
callbacks (which could be potentially dangerous).

Closes #3673.
---
 include/SDL_render.h                     |  9 +++++++++
 src/dynapi/SDL_dynapi_overrides.h        |  1 +
 src/dynapi/SDL_dynapi_procs.h            |  1 +
 src/render/SDL_render.c                  | 11 +++++++++++
 src/render/SDL_sysrender.h               |  2 ++
 src/render/direct3d/SDL_render_d3d.c     | 19 +++++++++++++++++++
 src/render/direct3d11/SDL_render_d3d11.c | 16 ++++++++++++++++
 src/render/metal/SDL_render_metal.m      | 21 +++++++++++++++++++++
 src/render/opengl/SDL_render_gl.c        | 21 +++++++++++++++++++++
 src/render/opengles/SDL_render_gles.c    | 22 ++++++++++++++++++++++
 src/render/opengles2/SDL_render_gles2.c  | 21 +++++++++++++++++++++
 src/render/psp/SDL_render_psp.c          |  9 +++++++++
 src/render/vitagxm/SDL_render_vita_gxm.c | 15 +++++++++++++++
 13 files changed, 168 insertions(+)

diff --git a/include/SDL_render.h b/include/SDL_render.h
index 8898c78396..f8e5e6ca96 100644
--- a/include/SDL_render.h
+++ b/include/SDL_render.h
@@ -1694,6 +1694,15 @@ extern DECLSPEC void *SDLCALL SDL_RenderGetMetalLayer(SDL_Renderer * renderer);
  */
 extern DECLSPEC void *SDLCALL SDL_RenderGetMetalCommandEncoder(SDL_Renderer * renderer);
 
+/**
+ * Toggle VSync of the given renderer.
+ *
+ * \param renderer The renderer to toggle
+ * \param vsync Non-zero for on, zero for off
+ * \returns a 0 int on success, or non-zero on failure
+ */
+extern DECLSPEC int SDLCALL SDL_RenderSetVSync(SDL_Renderer* renderer, int vsync);
+
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 6a849c4fd4..b28b5b8d1e 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -819,3 +819,4 @@
 #define SDL_GetTextureUserData SDL_GetTextureUserData_REAL
 #define SDL_RenderGeometry SDL_RenderGeometry_REAL
 #define SDL_RenderGeometryRaw SDL_RenderGeometryRaw_REAL
+#define SDL_RenderSetVSync SDL_RenderSetVSync_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 5a913c360c..9887e623c4 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -884,3 +884,4 @@ SDL_DYNAPI_PROC(int,SDL_SetTextureUserData,(SDL_Texture *a, void *b),(a,b),retur
 SDL_DYNAPI_PROC(void*,SDL_GetTextureUserData,(SDL_Texture *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_RenderGeometry,(SDL_Renderer *a, SDL_Texture *b, const SDL_Vertex *c, int d, const int *e, int f),(a,b,c,d,e,f),return)
 SDL_DYNAPI_PROC(int,SDL_RenderGeometryRaw,(SDL_Renderer *a, SDL_Texture *b, const float *c, int d, const int *e, int f, const float *g, int h, int i, const void *j, int k, int l),(a,b,c,d,e,f,g,h,i,j,k,l),return)
+SDL_DYNAPI_PROC(int,SDL_RenderSetVSync,(SDL_Renderer *a, int b),(a,b),return)
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index ec545701d8..eec6dc9bb7 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -4124,4 +4124,15 @@ SDL_GetBlendModeAlphaOperation(SDL_BlendMode blendMode)
     return (SDL_BlendOperation)(((Uint32)blendMode >> 16) & 0xF);
 }
 
+int
+SDL_RenderSetVSync(SDL_Renderer * renderer, int vsync)
+{
+    CHECK_RENDERER_MAGIC(renderer, -1);
+
+    if (renderer->SetVSync) {
+        return renderer->SetVSync(renderer, vsync);
+    }
+    return SDL_Unsupported();
+}
+
 /* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h
index f569b51ed4..8a640055b8 100644
--- a/src/render/SDL_sysrender.h
+++ b/src/render/SDL_sysrender.h
@@ -163,6 +163,8 @@ struct SDL_Renderer
 
     void (*DestroyRenderer) (SDL_Renderer * renderer);
 
+    int (*SetVSync) (SDL_Renderer * renderer, int vsync);
+
     int (*GL_BindTexture) (SDL_Renderer * renderer, SDL_Texture *texture, float *texw, float *texh);
     int (*GL_UnbindTexture) (SDL_Renderer * renderer, SDL_Texture *texture);
 
diff --git a/src/render/direct3d/SDL_render_d3d.c b/src/render/direct3d/SDL_render_d3d.c
index a847631dfd..a567b78b78 100644
--- a/src/render/direct3d/SDL_render_d3d.c
+++ b/src/render/direct3d/SDL_render_d3d.c
@@ -1743,6 +1743,24 @@ D3D_Reset(SDL_Renderer * renderer)
     return 0;
 }
 
+static int
+D3D_SetVSync(SDL_Renderer * renderer, const int vsync)
+{
+    D3D_RenderData *data = renderer->driverdata;
+    if (vsync) {
+        data->pparams.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
+        renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
+    } else {
+        data->pparams.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
+        renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC;
+    }
+    if (D3D_Reset(renderer) < 0) {
+        /* D3D_Reset will call SDL_SetError() */
+        return -1;
+    }
+    return 0;
+}
+
 SDL_Renderer *
 D3D_CreateRenderer(SDL_Window * window, Uint32 flags)
 {
@@ -1805,6 +1823,7 @@ D3D_CreateRenderer(SDL_Window * window, Uint32 flags)
     renderer->RenderPresent = D3D_RenderPresent;
     renderer->DestroyTexture = D3D_DestroyTexture;
     renderer->DestroyRenderer = D3D_DestroyRenderer;
+    renderer->SetVSync = D3D_SetVSync;
     renderer->info = D3D_RenderDriver.info;
     renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
     renderer->driverdata = data;
diff --git a/src/render/direct3d11/SDL_render_d3d11.c b/src/render/direct3d11/SDL_render_d3d11.c
index 979296b6a2..d07a23e2b7 100644
--- a/src/render/direct3d11/SDL_render_d3d11.c
+++ b/src/render/direct3d11/SDL_render_d3d11.c
@@ -2598,6 +2598,21 @@ D3D11_RenderPresent(SDL_Renderer * renderer)
     }
 }
 
+#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
+    /* no-op. */
+#else
+static int
+D3D11_SetVSync(SDL_Renderer * renderer, const int vsync)
+{
+    if (vsync) {
+        renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
+    } else {
+        renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC;
+    }
+    return 0;
+}
+#endif
+
 SDL_Renderer *
 D3D11_CreateRenderer(SDL_Window * window, Uint32 flags)
 {
@@ -2666,6 +2681,7 @@ D3D11_CreateRenderer(SDL_Window * window, Uint32 flags)
     if ((flags & SDL_RENDERER_PRESENTVSYNC)) {
         renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
     }
+    renderer->SetVSync = D3D11_SetVSync;
 #endif
 
     /* HACK: make sure the SDL_Renderer references the SDL_Window data now, in
diff --git a/src/render/metal/SDL_render_metal.m b/src/render/metal/SDL_render_metal.m
index 217ffdce4f..ca6ff2a196 100644
--- a/src/render/metal/SDL_render_metal.m
+++ b/src/render/metal/SDL_render_metal.m
@@ -1760,6 +1760,26 @@ - (void)dealloc
     return (__bridge void*)data.mtlcmdencoder;
 }}
 
+static int
+METAL_SetVSync(SDL_Renderer * renderer, const int vsync)
+{
+#if (defined(__MACOSX__) && defined(MAC_OS_X_VERSION_10_13)) || TARGET_OS_MACCATALYST
+    if (@available(macOS 10.13, *)) {
+        METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
+        if (vsync) {
+            data.mtllayer.displaySyncEnabled = YES;
+            renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
+        } else {
+            data.mtllayer.displaySyncEnabled = NO;
+            renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC;
+        }
+        return 0;
+    }
+#endif
+    return SDL_SetError("This Apple OS does not support displaySyncEnabled!");
+}
+
+
 static SDL_Renderer *
 METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
 { @autoreleasepool {
@@ -2010,6 +2030,7 @@ - (void)dealloc
     renderer->RenderPresent = METAL_RenderPresent;
     renderer->DestroyTexture = METAL_DestroyTexture;
     renderer->DestroyRenderer = METAL_DestroyRenderer;
+    renderer->SetVSync = METAL_SetVSync;
     renderer->GetMetalLayer = METAL_GetMetalLayer;
     renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
 
diff --git a/src/render/opengl/SDL_render_gl.c b/src/render/opengl/SDL_render_gl.c
index 591f68fb43..d7f7a44068 100644
--- a/src/render/opengl/SDL_render_gl.c
+++ b/src/render/opengl/SDL_render_gl.c
@@ -1716,6 +1716,26 @@ GL_UnbindTexture (SDL_Renderer * renderer, SDL_Texture *texture)
     return 0;
 }
 
+static int
+GL_SetVSync(SDL_Renderer * renderer, const int vsync)
+{
+    int retval;
+    if (vsync) {
+        retval = SDL_GL_SetSwapInterval(1);
+    } else {
+        retval = SDL_GL_SetSwapInterval(0);
+    }
+    if (retval != 0) {
+        return retval;
+    }
+    if (SDL_GL_GetSwapInterval() > 0) {
+        renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
+    } else {
+        renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC;
+    }
+    return retval;
+}
+
 
 static SDL_Renderer *
 GL_CreateRenderer(SDL_Window * window, Uint32 flags)
@@ -1785,6 +1805,7 @@ GL_CreateRenderer(SDL_Window * window, Uint32 flags)
     renderer->RenderPresent = GL_RenderPresent;
     renderer->DestroyTexture = GL_DestroyTexture;
     renderer->DestroyRenderer = GL_DestroyRenderer;
+    renderer->SetVSync = GL_SetVSync;
     renderer->GL_BindTexture = GL_BindTexture;
     renderer->GL_UnbindTexture = GL_UnbindTexture;
     renderer->info = GL_RenderDriver.info;
diff --git a/src/render/opengles/SDL_render_gles.c b/src/render/opengles/SDL_render_gles.c
index 93480140ce..017229e9cb 100644
--- a/src/render/opengles/SDL_render_gles.c
+++ b/src/render/opengles/SDL_render_gles.c
@@ -1229,6 +1229,27 @@ static int GLES_UnbindTexture (SDL_Renderer * renderer, SDL_Texture *texture)
     return 0;
 }
 
+static int
+GLES_SetVSync(SDL_Renderer * renderer, const int vsync)
+{
+    int retval;
+    if (vsync) {
+        retval = SDL_GL_SetSwapInterval(1);
+    } else {
+        retval = SDL_GL_SetSwapInterval(0);
+    }
+    if (retval != 0) {
+        return retval;
+    }
+    if (SDL_GL_GetSwapInterval() > 0) {
+        renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
+    } else {
+        renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC;
+    }
+    return retval;
+}
+
+
 static SDL_Renderer *
 GLES_CreateRenderer(SDL_Window * window, Uint32 flags)
 {
@@ -1294,6 +1315,7 @@ GLES_CreateRenderer(SDL_Window * window, Uint32 flags)
     renderer->RenderPresent = GLES_RenderPresent;
     renderer->DestroyTexture = GLES_DestroyTexture;
     renderer->DestroyRenderer = GLES_DestroyRenderer;
+    renderer->SetVSync = GLES_SetVSync;
     renderer->GL_BindTexture = GLES_BindTexture;
     renderer->GL_UnbindTexture = GLES_UnbindTexture;
     renderer->info = GLES_RenderDriver.info;
diff --git a/src/render/opengles2/SDL_render_gles2.c b/src/render/opengles2/SDL_render_gles2.c
index f342dc3096..73cca1bcbb 100644
--- a/src/render/opengles2/SDL_render_gles2.c
+++ b/src/render/opengles2/SDL_render_gles2.c
@@ -2012,6 +2012,26 @@ GLES2_RenderPresent(SDL_Renderer *renderer)
     SDL_GL_SwapWindow(renderer->window);
 }
 
+static int
+GLES2_SetVSync(SDL_Renderer * renderer, const int vsync)
+{
+    int retval;
+    if (vsync) {
+        retval = SDL_GL_SetSwapInterval(1);
+    } else {
+        retval = SDL_GL_SetSwapInterval(0);
+    }
+    if (retval != 0) {
+        return retval;
+    }
+    if (SDL_GL_GetSwapInterval() > 0) {
+        renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
+    } else {
+        renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC;
+    }
+    return retval;
+}
+
 
 /*************************************************************************************************
  * Bind/unbinding of textures
@@ -2197,6 +2217,7 @@ GLES2_CreateRenderer(SDL_Window *window, Uint32 flags)
     renderer->RenderPresent       = GLES2_RenderPresent;
     renderer->DestroyTexture      = GLES2_DestroyTexture;
     renderer->DestroyRenderer     = GLES2_DestroyRenderer;
+    renderer->SetVSync            = GLES2_SetVSync;
     renderer->GL_BindTexture      = GLES2_BindTexture;
     renderer->GL_UnbindTexture    = GLES2_UnbindTexture;
 
diff --git a/src/render/psp/SDL_render_psp.c b/src/render/psp/SDL_render_psp.c
index 6b9b3aff6a..e6627048f0 100644
--- a/src/render/psp/SDL_render_psp.c
+++ b/src/render/psp/SDL_render_psp.c
@@ -921,6 +921,14 @@ PSP_DestroyRenderer(SDL_Renderer * renderer)
     SDL_free(renderer);
 }
 
+static int
+PSP_SetVSync(SDL_Renderer * renderer, const int vsync)
+{
+    PSP_RenderData *data = renderer->driverdata;
+    data->vsync = vsync;
+    return 0;
+}
+
 SDL_Renderer *
 PSP_CreateRenderer(SDL_Window * window, Uint32 flags)
 {
@@ -962,6 +970,7 @@ PSP_CreateRenderer(SDL_Window * window, Uint32 flags)
     renderer->RenderPresent = PSP_RenderPresent;
     renderer->DestroyTexture = PSP_DestroyTexture;
     renderer->DestroyRenderer = PSP_DestroyRenderer;
+    renderer->SetVSync = PSP_SetVSync;
     renderer->info = PSP_RenderDriver.info;
     renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
     renderer->driverdata = data;
diff --git a/src/render/vitagxm/SDL_render_vita_gxm.c b/src/render/vitagxm/SDL_render_vita_gxm.c
index ee62c814f5..bf3af3962f 100644
--- a/src/render/vitagxm/SDL_render_vita_gxm.c
+++ b/src/render/vitagxm/SDL_render_vita_gxm.c
@@ -201,6 +201,20 @@ StartDrawing(SDL_Renderer *renderer)
     data->drawing = SDL_TRUE;
 }
 
+static int
+VITA_GXM_SetVSync(SDL_Renderer * renderer, const int vsync)
+{
+    VITA_GXM_RenderData *data = renderer->driverdata;
+    if (vsync) {
+        data->displayData.wait_vblank = SDL_TRUE;
+        renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
+    } else {
+        data->displayData.wait_vblank = SDL_FALSE;
+        renderer->info.flags &= ~SDL_RENDERER_PRESENTVSYNC;
+    }
+    return 0;
+}
+
 SDL_Renderer *
 VITA_GXM_CreateRenderer(SDL_Window *window, Uint32 flags)
 {
@@ -241,6 +255,7 @@ VITA_GXM_CreateRenderer(SDL_Window *window, Uint32 flags)
     renderer->RenderPresent = VITA_GXM_RenderPresent;
     renderer->DestroyTexture = VITA_GXM_DestroyTexture;
     renderer->DestroyRenderer = VITA_GXM_DestroyRenderer;
+    renderer->SetVSync = VITA_GXM_SetVSync;
 
     renderer->info = VITA_GXM_RenderDriver.info;
     renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);

1 Like