SDL: Added support for using the GPU renderer as an offscreen renderer

From a864dcac25f8d6aa1991a24642ca04d9a90c5fc6 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 1 Oct 2025 14:36:56 -0700
Subject: [PATCH] Added support for using the GPU renderer as an offscreen
 renderer

SDL_CreateGPURenderer() now allows passing in an existing GPU device and passing in a NULL window to create an offscreen renderer.

Also renamed SDL_SetRenderGPUState() to SDL_SetGPURenderState().
---
 include/SDL3/SDL_render.h         |   54 +-
 src/dynapi/SDL_dynapi.sym         |    3 +-
 src/dynapi/SDL_dynapi_overrides.h |    3 +-
 src/dynapi/SDL_dynapi_procs.h     |    5 +-
 src/render/SDL_render.c           |   55 +-
 src/render/gpu/SDL_render_gpu.c   |  185 ++-
 test/CMakeLists.txt               |    2 +-
 test/testgpu/build-shaders.sh     |  119 +-
 test/testgpu/cube.frag.dxil.h     |  238 +++
 test/testgpu/cube.frag.hlsl       |   17 +
 test/testgpu/cube.frag.msl.h      |   33 +
 test/testgpu/cube.frag.spv.h      |   34 +
 test/testgpu/cube.glsl            |   31 -
 test/testgpu/cube.hlsl            |   31 -
 test/testgpu/cube.hlsli           |   13 +
 test/testgpu/cube.metal           |   38 -
 test/testgpu/cube.vert.dxil.h     |  339 ++++
 test/testgpu/cube.vert.hlsl       |   15 +
 test/testgpu/cube.vert.msl.h      |   54 +
 test/testgpu/cube.vert.spv.h      |   88 +
 test/testgpu/overlay.frag.dxil.h  |  307 ++++
 test/testgpu/overlay.frag.hlsl    |   10 +
 test/testgpu/overlay.frag.msl.h   |   39 +
 test/testgpu/overlay.frag.spv.h   |   66 +
 test/testgpu/overlay.hlsli        |    5 +
 test/testgpu/overlay.vert.dxil.h  |  288 ++++
 test/testgpu/overlay.vert.hlsl    |   25 +
 test/testgpu/overlay.vert.msl.h   |  146 ++
 test/testgpu/overlay.vert.spv.h   |  103 ++
 test/testgpu/testgpu_dxil.h       |  876 ----------
 test/testgpu/testgpu_metallib.h   | 2584 -----------------------------
 test/testgpu/testgpu_spirv.h      |  150 --
 test/testgpu_spinning_cube.c      |  392 ++++-
 test/testgpurender_effects.c      |   14 +-
 test/testgpurender_msdf.c         |    8 +-
 test/testsymbols.c                |    3 +-
 36 files changed, 2391 insertions(+), 3982 deletions(-)
 create mode 100644 test/testgpu/cube.frag.dxil.h
 create mode 100644 test/testgpu/cube.frag.hlsl
 create mode 100644 test/testgpu/cube.frag.msl.h
 create mode 100644 test/testgpu/cube.frag.spv.h
 delete mode 100644 test/testgpu/cube.glsl
 delete mode 100644 test/testgpu/cube.hlsl
 create mode 100644 test/testgpu/cube.hlsli
 delete mode 100644 test/testgpu/cube.metal
 create mode 100644 test/testgpu/cube.vert.dxil.h
 create mode 100644 test/testgpu/cube.vert.hlsl
 create mode 100644 test/testgpu/cube.vert.msl.h
 create mode 100644 test/testgpu/cube.vert.spv.h
 create mode 100644 test/testgpu/overlay.frag.dxil.h
 create mode 100644 test/testgpu/overlay.frag.hlsl
 create mode 100644 test/testgpu/overlay.frag.msl.h
 create mode 100644 test/testgpu/overlay.frag.spv.h
 create mode 100644 test/testgpu/overlay.hlsli
 create mode 100644 test/testgpu/overlay.vert.dxil.h
 create mode 100644 test/testgpu/overlay.vert.hlsl
 create mode 100644 test/testgpu/overlay.vert.msl.h
 create mode 100644 test/testgpu/overlay.vert.spv.h
 delete mode 100644 test/testgpu/testgpu_dxil.h
 delete mode 100644 test/testgpu/testgpu_metallib.h
 delete mode 100644 test/testgpu/testgpu_spirv.h

diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h
index 6023b40b64b3d..84c6d647718bd 100644
--- a/include/SDL3/SDL_render.h
+++ b/include/SDL3/SDL_render.h
@@ -74,6 +74,13 @@ extern "C" {
  */
 #define SDL_SOFTWARE_RENDERER   "software"
 
+/**
+ * The name of the GPU renderer.
+ *
+ * \since This macro is available since SDL 3.4.0.
+ */
+#define SDL_GPU_RENDERER        "gpu"
+
 /**
  * Vertex structure.
  *
@@ -285,6 +292,7 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window *window
  *
  * With the SDL GPU renderer (since SDL 3.4.0):
  *
+ * - `SDL_PROP_RENDERER_CREATE_GPU_DEVICE_POINTER`: the device to use with the renderer, optional.
  * - `SDL_PROP_RENDERER_CREATE_GPU_SHADERS_SPIRV_BOOLEAN`: the app is able to
  *   provide SPIR-V shaders to SDL_GPURenderState, optional.
  * - `SDL_PROP_RENDERER_CREATE_GPU_SHADERS_DXIL_BOOLEAN`: the app is able to
@@ -328,6 +336,7 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRendererWithProperties(SDL_
 #define SDL_PROP_RENDERER_CREATE_SURFACE_POINTER                            "SDL.renderer.create.surface"
 #define SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER                   "SDL.renderer.create.output_colorspace"
 #define SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER                       "SDL.renderer.create.present_vsync"
+#define SDL_PROP_RENDERER_CREATE_GPU_DEVICE_POINTER                         "SDL.renderer.create.gpu.device"
 #define SDL_PROP_RENDERER_CREATE_GPU_SHADERS_SPIRV_BOOLEAN                  "SDL.renderer.create.gpu.shaders_spirv"
 #define SDL_PROP_RENDERER_CREATE_GPU_SHADERS_DXIL_BOOLEAN                   "SDL.renderer.create.gpu.shaders_dxil"
 #define SDL_PROP_RENDERER_CREATE_GPU_SHADERS_MSL_BOOLEAN                    "SDL.renderer.create.gpu.shaders_msl"
@@ -339,35 +348,40 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRendererWithProperties(SDL_
 #define SDL_PROP_RENDERER_CREATE_VULKAN_PRESENT_QUEUE_FAMILY_INDEX_NUMBER   "SDL.renderer.create.vulkan.present_queue_family_index"
 
 /**
- * Create a 2D GPU rendering context for a window, with support for the
- * specified shader format.
+ * Create a 2D GPU rendering context.
  *
- * This is a convenience function to create a SDL GPU backed renderer,
- * intended to be used with SDL_GPURenderState. The resulting renderer will
- * support shaders in one of the specified shader formats.
+ * The GPU device to use is passed in as a parameter. If this is NULL, then a device will be created normally and can be retrieved using SDL_GetGPURendererDevice().
  *
- * If no available GPU driver supports any of the specified shader formats,
- * this function will fail.
+ * The window to use is passed in as a parameter. If this is NULL, then this will become an offscreen renderer. In that case, you should call SDL_SetRenderTarget() to setup rendering to a texture, and then call SDL_RenderPresent() normally to complete drawing a frame.
  *
- * \param window the window where rendering is displayed.
- * \param format_flags a bitflag indicating which shader formats the app is
- *                     able to provide.
- * \param device a pointer filled with the associated GPU device, or NULL on
- *               error.
+ * \param device the GPU device to use with the renderer, or NULL to create a device.
+ * \param window the window where rendering is displayed, or NULL to create an offscreen renderer.
  * \returns a valid rendering context or NULL if there was an error; call
  *          SDL_GetError() for more information.
  *
- * \threadsafety This function should only be called on the main thread.
+ * \threadsafety If this function is called with a valid GPU device, it should be called on the thread that created the device. If this function is called with a valid window, it should be called on the thread that created the window.
  *
  * \since This function is available since SDL 3.4.0.
  *
  * \sa SDL_CreateRendererWithProperties
- * \sa SDL_GetGPUShaderFormats
+ * \sa SDL_GetGPURendererDevice
  * \sa SDL_CreateGPUShader
  * \sa SDL_CreateGPURenderState
- * \sa SDL_SetRenderGPUState
+ * \sa SDL_SetGPURenderState
  */
-extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateGPURenderer(SDL_Window *window, SDL_GPUShaderFormat format_flags, SDL_GPUDevice **device);
+extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateGPURenderer(SDL_GPUDevice *device, SDL_Window *window);
+
+/**
+ * Return the GPU device used by a renderer.
+ *
+ * \param renderer the rendering context.
+ * \returns the GPU device used by the renderer, or NULL if the renderer is not a GPU renderer; call SDL_GetError() for more information.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.4.0.
+ */
+extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_GetGPURendererDevice(SDL_Renderer *renderer);
 
 /**
  * Create a 2D software rendering context for a surface.
@@ -382,7 +396,7 @@ extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateGPURenderer(SDL_Window *win
  * \returns a valid rendering context or NULL if there was an error; call
  *          SDL_GetError() for more information.
  *
- * \threadsafety This function should only be called on the main thread.
+ * \threadsafety It is safe to call this function from any thread.
  *
  * \since This function is available since SDL 3.2.0.
  *
@@ -2888,7 +2902,7 @@ typedef struct SDL_GPURenderStateCreateInfo
  *
  * \sa SDL_CreateGPURenderState
  * \sa SDL_SetGPURenderStateFragmentUniforms
- * \sa SDL_SetRenderGPUState
+ * \sa SDL_SetGPURenderState
  * \sa SDL_DestroyGPURenderState
  */
 typedef struct SDL_GPURenderState SDL_GPURenderState;
@@ -2907,7 +2921,7 @@ typedef struct SDL_GPURenderState SDL_GPURenderState;
  * \since This function is available since SDL 3.4.0.
  *
  * \sa SDL_SetGPURenderStateFragmentUniforms
- * \sa SDL_SetRenderGPUState
+ * \sa SDL_SetGPURenderState
  * \sa SDL_DestroyGPURenderState
  */
 extern SDL_DECLSPEC SDL_GPURenderState * SDLCALL SDL_CreateGPURenderState(SDL_Renderer *renderer, SDL_GPURenderStateCreateInfo *createinfo);
@@ -2948,7 +2962,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetGPURenderStateFragmentUniforms(SDL_GPURe
  *
  * \since This function is available since SDL 3.4.0.
  */
-extern SDL_DECLSPEC bool SDLCALL SDL_SetRenderGPUState(SDL_Renderer *renderer, SDL_GPURenderState *state);
+extern SDL_DECLSPEC bool SDLCALL SDL_SetGPURenderState(SDL_Renderer *renderer, SDL_GPURenderState *state);
 
 /**
  * Destroy custom GPU render state.
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 99dd2411f5392..967a424970b79 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1240,7 +1240,7 @@ SDL3_0.0.0 {
     SDL_GetDefaultTextureScaleMode;
     SDL_CreateGPURenderState;
     SDL_SetGPURenderStateFragmentUniforms;
-    SDL_SetRenderGPUState;
+    SDL_SetGPURenderState;
     SDL_DestroyGPURenderState;
     SDL_SetWindowProgressState;
     SDL_SetWindowProgressValue;
@@ -1259,6 +1259,7 @@ SDL3_0.0.0 {
     SDL_GetGPUTextureFormatFromPixelFormat;
     SDL_SetTexturePalette;
     SDL_GetTexturePalette;
+    SDL_GetGPURendererDevice;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 2785c06441567..b0693c5471d73 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1265,7 +1265,7 @@
 #define SDL_GetDefaultTextureScaleMode SDL_GetDefaultTextureScaleMode_REAL
 #define SDL_CreateGPURenderState SDL_CreateGPURenderState_REAL
 #define SDL_SetGPURenderStateFragmentUniforms SDL_SetGPURenderStateFragmentUniforms_REAL
-#define SDL_SetRenderGPUState SDL_SetRenderGPUState_REAL
+#define SDL_SetGPURenderState SDL_SetGPURenderState_REAL
 #define SDL_DestroyGPURenderState SDL_DestroyGPURenderState_REAL
 #define SDL_SetWindowProgressState SDL_SetWindowProgressState_REAL
 #define SDL_SetWindowProgressValue SDL_SetWindowProgressValue_REAL
@@ -1285,3 +1285,4 @@
 #define JNI_OnLoad JNI_OnLoad_REAL
 #define SDL_SetTexturePalette SDL_SetTexturePalette_REAL
 #define SDL_GetTexturePalette SDL_GetTexturePalette_REAL
+#define SDL_GetGPURendererDevice SDL_GetGPURendererDevice_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index bf8bb35029c27..18195db8a8f63 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1273,7 +1273,7 @@ SDL_DYNAPI_PROC(bool,SDL_SetDefaultTextureScaleMode,(SDL_Renderer *a,SDL_ScaleMo
 SDL_DYNAPI_PROC(bool,SDL_GetDefaultTextureScaleMode,(SDL_Renderer *a,SDL_ScaleMode *b),(a,b),return)
 SDL_DYNAPI_PROC(SDL_GPURenderState*,SDL_CreateGPURenderState,(SDL_Renderer *a,SDL_GPURenderStateCreateInfo *b),(a,b),return)
 SDL_DYNAPI_PROC(bool,SDL_SetGPURenderStateFragmentUniforms,(SDL_GPURenderState *a,Uint32 b,const void *c,Uint32 d),(a,b,c,d),return)
-SDL_DYNAPI_PROC(bool,SDL_SetRenderGPUState,(SDL_Renderer *a,SDL_GPURenderState *b),(a,b),return)
+SDL_DYNAPI_PROC(bool,SDL_SetGPURenderState,(SDL_Renderer *a,SDL_GPURenderState *b),(a,b),return)
 SDL_DYNAPI_PROC(void,SDL_DestroyGPURenderState,(SDL_GPURenderState *a),(a),)
 SDL_DYNAPI_PROC(bool,SDL_SetWindowProgressState,(SDL_Window *a,SDL_ProgressState b),(a,b),return)
 SDL_DYNAPI_PROC(bool,SDL_SetWindowProgressValue,(SDL_Window *a,float b),(a,b),return)
@@ -1282,7 +1282,7 @@ SDL_DYNAPI_PROC(float,SDL_GetWindowProgressValue,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(bool,SDL_SetRenderTextureAddressMode,(SDL_Renderer *a,SDL_TextureAddressMode b,SDL_TextureAddressMode c),(a,b,c),return)
 SDL_DYNAPI_PROC(bool,SDL_GetRenderTextureAddressMode,(SDL_Renderer *a,SDL_TextureAddressMode *b,SDL_TextureAddressMode *c),(a,b,c),return)
 SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetGPUDeviceProperties,(SDL_GPUDevice *a),(a),return)
-SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateGPURenderer,(SDL_Window *a,SDL_GPUShaderFormat b,SDL_GPUDevice **c),(a,b,c),return)
+SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateGPURenderer,(SDL_GPUDevice *a,SDL_Window *b),(a,b),return)
 SDL_DYNAPI_PROC(bool,SDL_PutAudioStreamPlanarData,(SDL_AudioStream *a,const void * const*b,int c,int d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(int,SDL_GetEventDescription,(const SDL_Event *a,char *b,int c),(a,b,c),return)
 SDL_DYNAPI_PROC(bool,SDL_PutAudioStreamDataNoCopy,(SDL_AudioStream *a,const void *b,int c,SDL_AudioStreamDataCompleteCallback d,void *e),(a,b,c,d,e),return)
@@ -1293,3 +1293,4 @@ SDL_DYNAPI_PROC(SDL_GPUTextureFormat,SDL_GetGPUTextureFormatFromPixelFormat,(SDL
 SDL_DYNAPI_PROC(Sint32,JNI_OnLoad,(JavaVM *a, void *b),(a,b),return)
 SDL_DYNAPI_PROC(bool,SDL_SetTexturePalette,(SDL_Texture *a,SDL_Palette *b),(a,b),return)
 SDL_DYNAPI_PROC(SDL_Palette*,SDL_GetTexturePalette,(SDL_Texture *a),(a),return)
+SDL_DYNAPI_PROC(SDL_GPUDevice*,SDL_GetGPURendererDevice,(SDL_Renderer *a),(a),return)
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index ebe5fd4008bbf..513076f1aeb9c 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -1047,11 +1047,17 @@ SDL_Renderer *SDL_CreateRendererWithProperties(SDL_PropertiesID props)
     const char *hint;
     SDL_PropertiesID new_props;
 
-    CHECK_PARAM((!window && !surface) || (window && surface)) {
+    // The GPU renderer is the only one that can be created without a window or surface
+    CHECK_PARAM(!window && !surface && (!driver_name || SDL_strcmp(driver_name, SDL_GPU_RENDERER) != 0)) {
         SDL_InvalidParamError("window");
         return NULL;
     }
 
+    CHECK_PARAM(window && surface) {
+        SDL_SetError("A renderer can't target both a window and surface");
+        return NULL;
+    }
+
     CHECK_PARAM(window && SDL_WindowHasSurface(window)) {
         SDL_SetError("Surface already associated with window");
         return NULL;
@@ -1269,37 +1275,32 @@ SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, const char *name)
     return renderer;
 }
 
-SDL_Renderer *SDL_CreateGPURenderer(SDL_Window *window, SDL_GPUShaderFormat format_flags, SDL_GPUDevice **device)
+SDL_Renderer *SDL_CreateGPURenderer(SDL_GPUDevice *device, SDL_Window *window)
 {
-    CHECK_PARAM(!device) {
-        SDL_InvalidParamError("device");
-        return NULL;
-    }
-
-    *device = NULL;
     SDL_Renderer *renderer;
 
     SDL_PropertiesID props = SDL_CreateProperties();
+    SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_GPU_DEVICE_POINTER, device);
     SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, window);
-    if (format_flags & SDL_GPU_SHADERFORMAT_SPIRV) {
-        SDL_SetBooleanProperty(props, SDL_PROP_RENDERER_CREATE_GPU_SHADERS_SPIRV_BOOLEAN, true);
-    }
-    if (format_flags & SDL_GPU_SHADERFORMAT_DXIL) {
-        SDL_SetBooleanProperty(props, SDL_PROP_RENDERER_CREATE_GPU_SHADERS_DXIL_BOOLEAN, true);
-    }
-    if (format_flags & SDL_GPU_SHADERFORMAT_MSL) {
-        SDL_SetBooleanProperty(props, SDL_PROP_RENDERER_CREATE_GPU_SHADERS_MSL_BOOLEAN, true);
-    }
-    SDL_SetStringProperty(props, SDL_PROP_RENDERER_CREATE_NAME_STRING, "gpu");
+    SDL_SetStringProperty(props, SDL_PROP_RENDERER_CREATE_NAME_STRING, SDL_GPU_RENDERER);
 
     renderer = SDL_CreateRendererWithProperties(props);
-    if (renderer) {
-        *device = (SDL_GPUDevice *)SDL_GetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_GPU_DEVICE_POINTER, NULL);
-    }
     SDL_DestroyProperties(props);
     return renderer;
 }
 
+SDL_GPUDevice *SDL_GetGPURendererDevice(SDL_Renderer *renderer)
+{
+    CHECK_RENDERER_MAGIC(renderer, NULL);
+
+    SDL_GPUDevice *device = SDL_GetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_GPU_DEVICE_POINTER, NULL);
+    if (!device) {
+        SDL_SetError("Renderer isn't a GPU renderer");
+        return NULL;
+    }
+    return device;
+}
+
 SDL_Renderer *SDL_CreateSoftwareRenderer(SDL_Surface *surface)
 {
 #ifdef SDL_VIDEO_RENDER_SW
@@ -1365,8 +1366,8 @@ bool SDL_GetRenderOutputSize(SDL_Renderer *renderer, int *w, int *h)
     } else if (renderer->window) {
         return SDL_GetWindowSizeInPixels(renderer->window, w, h);
     } else {
-        SDL_assert(!"This should never happen");
-        return SDL_SetError("Renderer doesn't support querying output size");
+        // We don't have any output size, this might be an offscreen-only renderer
+        return true;
     }
 }
 
@@ -5548,7 +5549,11 @@ bool SDL_RenderPresent(SDL_Renderer *renderer)
     CHECK_RENDERER_MAGIC(renderer, false);
 
     CHECK_PARAM(renderer->target) {
-        return SDL_SetError("You can't present on a render target");
+        if (!renderer->window && SDL_strcmp(renderer->name, SDL_GPU_RENDERER) == 0) {
+            // We're an offscreen renderer, we must submit the command queue
+        } else {
+            return SDL_SetError("You can't present on a render target");
+        }
     }
 
     if (renderer->transparent_window) {
@@ -6218,7 +6223,7 @@ bool SDL_SetGPURenderStateFragmentUniforms(SDL_GPURenderState *state, Uint32 slo
     return true;
 }
 
-bool SDL_SetRenderGPUState(SDL_Renderer *renderer, SDL_GPURenderState *state)
+bool SDL_SetGPURenderState(SDL_Renderer *renderer, SDL_GPURenderState *state)
 {
     CHECK_RENDERER_MAGIC(renderer, false);
 
diff --git a/src/render/gpu/SDL_render_gpu.c b/src/render/gpu/SDL_render_gpu.c
index 7fe9848df8078..1d13cf016d54a 100644
--- a/src/render/gpu/SDL_render_gpu.c
+++ b/src/render/gpu/SDL_render_gpu.c
@@ -83,6 +83,7 @@ static const float INPUTTYPE_HDR10 = 3;
 
 typedef struct GPU_RenderData
 {
+    bool external_device;
     SDL_GPUDevice *device;
     GPU_Shaders shaders;
     GPU_PipelineCache pipeline_cache;
@@ -1415,34 +1416,37 @@ static bool GPU_RenderPresent(SDL_Renderer *renderer)
 {
     GPU_RenderData *data = (GPU_RenderData *)renderer->internal;
 
-    SDL_GPUTexture *swapchain;
-    Uint32 swapchain_texture_width, swapchain_texture_height;
-    bool result = SDL_WaitAndAcquireGPUSwapchainTexture(data->state.command_buffer, renderer->window, &swapchain, &swapchain_texture_width, &swapchain_texture_height);
+    if (renderer->window) {
+        SDL_GPUTexture *swapchain;
+        Uint32 swapchain_texture_width, swapchain_texture_height;
+        bool result = SDL_WaitAndAcquireGPUSwapchainTexture(data->state.command_buffer, renderer->window, &swapchain, &swapchain_texture_width, &swapchain_texture_height);
 
-    if (!result) {
-        SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Failed to acquire swapchain texture: %s", SDL_GetError());
-    }
+        if (!result) {
+            SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Failed to acquire swapchain texture: %s", SDL_GetError());
+        }
 
-    if (swapchain != NULL) {
-        SDL_GPUBlitInfo blit_info;
-        SDL_zero(blit_info);
+        if (swapchain != NULL) {
+            SDL_GPUBlitInfo blit_info;
+            SDL_zero(blit_info);
 
-        blit_info.source.texture = data->backbuffer.texture;
-        blit_info.source.w = data->backbuffer.width;
-        blit_info.source.h = data->backbuffer.height;
-        blit_info.destination.texture = swapchain;
-        blit_info.destination.w = swapchain_texture_width;
-        blit_info.destination.h = swapchain_texture_height;
-        blit_info.load_op = SDL_GPU_LOADOP_DONT_CARE;
-        blit_info.filter = SDL_GPU_FILTER_LINEAR;
+            blit_info.source.texture = data->backbuffer.texture;
+            blit_info.source.w = data->backbuffer.width;
+            blit_info.source.h = data->backbuffer.height;
+            blit_info.destination.texture = swapchain;
+            blit_info.destination.w = swapchain_texture_width;
+            blit_info.destination.h = swapchain_texture_height;
+            blit_info.load_op = SDL_GPU_LOADOP_DONT_CARE;
+            blit_info.filter = SDL_GPU_FILTER_LINEAR;
 
-        SDL_BlitGPUTexture(data->state.command_buffer, &blit_info);
+            SDL_BlitGPUTexture(data->state.command_buffer, &blit_info);
 
-        SDL_SubmitGPUCommandBuffer(data->state.command_buffer);
+            SDL_SubmitGPUCommandBuffer(data->state.command_buffer);
 
-        if (swapchain_texture_width != data->backbuffer.width || swapchain_texture_height != data->backbuffer.height) {
-            SDL_ReleaseGPUTexture(data->device, data->backbuffer.texture);
-            CreateBackbuffer(data, swapchain_texture_width, swapchain_texture_height, SDL_GetGPUSwapchainTextureFormat(data->device, renderer->window));
+            if (swapchain_texture_width != data->backbuffer.width || swapchain_texture_height != data->backbuffer.height) {
+                CreateBackbuffer(data, swapchain_texture_width, swapchain_texture_height, SDL_GetGPUSwapchainTextureFormat(data->device, renderer->window));
+            }
+        } else {
+            SDL_SubmitGPUCommandBuffer(data->state.command_buffer);
         }
     } else {
         SDL_SubmitGPUCommandBuffer(data->state.command_buffer);
@@ -1486,7 +1490,7 @@ static void GPU_DestroyRenderer(SDL_Renderer *renderer)
     }
 
     if (data->state.command_buffer) {
-        SDL_SubmitGPUCommandBuffer(data->state.command_buffer);
+        SDL_CancelGPUCommandBuffer(data->state.command_buffer);
         data->state.command_buffer = NULL;
     }
 
@@ -1509,7 +1513,9 @@ static void GPU_DestroyRenderer(SDL_Renderer *renderer)
 
     if (data->device) {
         GPU_ReleaseShaders(&data->shaders, data->device);
-        SDL_DestroyGPUDevice(data->device);
+        if (!data->external_device) {
+            SDL_DestroyGPUDevice(data->device);
+        }
     }
 
     SDL_free(data);
@@ -1551,6 +1557,14 @@ static bool GPU_SetVSync(SDL_Renderer *renderer, const int vsync)
     GPU_RenderData *data = (GPU_RenderData *)renderer->internal;
     SDL_GPUPresentMode mode = SDL_GPU_PRESENTMODE_VSYNC;
 
+    if (!renderer->window) {
+        if (!vsync) {
+            return true;
+        } else {
+            return SDL_Unsupported();
+        }
+    }
+
     if (!ChoosePresentMode(data->device, renderer->window, vsync, &mode)) {
         return false;
     }
@@ -1575,8 +1589,8 @@ static bool GPU_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P
     SDL_SetupRendererColorspace(renderer, create_props);
 
     if (renderer->output_colorspace != SDL_COLORSPACE_SRGB &&
-        renderer->output_colorspace != SDL_COLORSPACE_SRGB_LINEAR &&
-        renderer->output_colorspace != SDL_COLORSPACE_HDR10) {
+        renderer->output_colorspace != SDL_COLORSPACE_SRGB_LINEAR
+        /*&& renderer->output_colorspace != SDL_COLORSPACE_HDR10*/) {
         return SDL_SetError("Unsupported output colorspace");
     }
 
@@ -1614,40 +1628,45 @@ static bool GPU_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P
     renderer->window = window;
     renderer->name = GPU_RenderDriver.name;
 
-    bool debug = SDL_GetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, false);
-    bool lowpower = SDL_GetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, false);
+    data->device = SDL_GetPointerProperty(create_props, SDL_PROP_RENDERER_CREATE_GPU_DEVICE_POINTER, NULL);
+    if (data->device) {
+        data->external_device = true;
+    } else {
+        bool debug = SDL_GetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, false);
+        bool lowpower = SDL_GetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, false);
 
-    // Prefer environment variables/hints if they exist, otherwise defer to properties
-    debug = SDL_GetHintBoolean(SDL_HINT_RENDER_GPU_DEBUG, debug);
-    lowpower = SDL_GetHintBoolean(SDL_HINT_RENDER_GPU_LOW_POWER, lowpower);
+        // Prefer environment variables/hints if they exist, otherwise defer to properties
+        debug = SDL_GetHintBoolean(SDL_HINT_RENDER_GPU_DEBUG, debug);
+        lowpower = SDL_GetHintBoolean(SDL_HINT_RENDER_GPU_LOW_POWER, lowpower);
 
-    SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, debug);
-    SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, lowpower);
+        SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, debug);
+        SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, lowpower);
 
-    // Set hints for the greatest hardware compatibility
-    // This property allows using the renderer on Intel Haswell and Broadwell GPUs.
-    if (!SDL_HasProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN)) {
-        SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN, true);
-    }
-    // These properties allow using the renderer on more Android devices.
-    if (!SDL_HasProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN)) {
-        SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, false);
-    }
-    if (!SDL_HasProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN)) {
-        SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, false);
-    }
-    if (!SDL_HasProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN)) {
-        SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN, false);
-    }
-    if (!SDL_HasProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN)) {
-        SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN, false);
-    }
+        // Set hints for the greatest hardware compatibility
+        // This property allows using the renderer on Intel Haswell and Broadwell GPUs.
+        if (!SDL_HasProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN)) {
+            SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN, true);
+        }
+        // These properties allow using the renderer on more Android devices.
+        if (!SDL_HasProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN)) {
+            SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, false);
+        }
+        if (!SDL_HasProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN)) {
+            SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, false);
+        }
+        if (!SDL_HasProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN)) {
+            SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN, false);
+        }
+        if (!SDL_HasProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN)) {
+            SDL_SetBooleanProperty(create_props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN, false);
+        }
 
-    GPU_FillSupportedShaderFormats(create_props);
-    data->device = SDL_CreateGPUDeviceWithProperties(create_props);
+        GPU_FillSupportedShaderFormats(create_props);
+        data->device = SDL_CreateGPUDeviceWithProperties(create_props);
 
-    if (!data->device) {
-        return false;
+        if (!data->device) {
+            return false;
+        }
     }
 
     if (!GPU_InitShaders(&data->shaders, data->device)) {
@@ -1663,30 +1682,39 @@ static bool GPU_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P
         return false;
     }
 
-    if (!SDL_ClaimWindowForGPUDevice(data->device, window)) {
-        return false;
-    }
+    if (window) {
+        if (!SDL_ClaimWindowForGPUDevice(data->device, window)) {
+            return false;
+        }
 
-    switch (renderer->output_colorspace) {
-    case SDL_COLORSPACE_SRGB_LINEAR:
-        data->swapchain.composition = SDL_GPU_SWAPCHAINCOMPOSITION_HDR_EXTENDED_LINEAR;
-        break;
-    case SDL_COLORSPACE_HDR10:
-        data->swapchain.composition = SDL_GPU_SWAPCHAINCOMPOSITION_HDR10_ST2084;
-        break;
-    case SDL_COLORSPACE_SRGB:
-    default:
-        data->swapchain.composition = SDL_GPU_SWAPCHAINCOMPOSITION_SDR;
-        break;
-    }
-    data->swapchain.present_mode = SDL_GPU_PRESENTMODE_VSYNC;
+        switch (renderer->output_colorspace) {
+        case SDL_COLORSPACE_SRGB_LINEAR:
+            data->swapchain.composition = SDL_GPU_SWAPCHAINCOMPOSITION_HDR_EXTENDED_LINEAR;
+            break;
+        case SDL_COLORSPACE_HDR10:
+            data->swapchain.composition = SDL_GPU_SWAPCHAINCOMPOSITION_HDR10_ST2084;
+            break;
+        case SDL_COLORSPACE_SRGB:
+        default:
+            data->swapchain.composition = SDL_GPU_SWAPCHAINCOMPOSITION_SDR;
+            break;
+        }
+        data->swapchain.present_mode = SDL_GPU_PRESENTMODE_VSYNC;
 
-    int vsync = (int)SDL_GetNumberProperty(create_props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, 0);
-    ChoosePresentMode(data->device, window, vsync, &data->swapchain.present_mode);
+        int vsync = (int)SDL_GetNumberProperty(create_props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, 0);
+        ChoosePresentMode(data->device, window, vsync, &data->swapchain.present_mode);
 
-    SDL_SetGPUSwapchainParameters(data->device, window, data->swapchain.composition, data->swapchain.present_mode);
+        SDL_SetGPUSwapchainParameters(data->device, window, data->swapchain.composition, data->swapchain.present_mode);
 
-    SDL_SetGPUAllowedFramesInFlight(data->device, 1);
+        SDL_SetGPUAllowedFramesInFlight(data->device, 1);
+
+        int w, h;
+        SDL_GetWindowSizeInPixels(window, &w, &h);
+
+        if (!CreateBackbuffer(data, w, h, SDL_GetGPUSwapchainTextureFormat(data->device, window))) {
+            return false;
+        }
+    }
 
     SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_BGRA32);
     SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA32);
@@ -1711,13 +1739,6 @@ static bool GPU_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P
     data->state.viewport.max_depth = 1;
     data->state.command_buffer = SDL_AcquireGPUCommandBuffer(data->device);
 
-    int w, h;
-    SDL_GetWindowSizeInPixels(window, &w, &h);
-
-    if (

(Patch may be truncated, please check the link at the top of this post.)