SDL: GPU: Binding validation and prevent null dereference if expected binding is missing (#13164) (25816)

From 25816bea4153231b7a5a492e9d59cc21fe32ed3e Mon Sep 17 00:00:00 2001
From: Evan Hemsley <[EMAIL REDACTED]>
Date: Mon, 2 Jun 2025 13:39:58 -0700
Subject: [PATCH] GPU: Binding validation and prevent null dereference if
 expected binding is missing (#13164)

---
 src/gpu/SDL_gpu.c               | 251 +++++++++++++++++++++-----
 src/gpu/SDL_sysgpu.h            |  53 +++++-
 src/gpu/d3d12/SDL_gpu_d3d12.c   | 306 +++++++++++++++++---------------
 src/gpu/metal/SDL_gpu_metal.m   | 134 +++++++-------
 src/gpu/vulkan/SDL_gpu_vulkan.c | 183 +++++++++++--------
 5 files changed, 589 insertions(+), 338 deletions(-)

diff --git a/src/gpu/SDL_gpu.c b/src/gpu/SDL_gpu.c
index a28346dde05ad..e1ad66c00be66 100644
--- a/src/gpu/SDL_gpu.c
+++ b/src/gpu/SDL_gpu.c
@@ -93,10 +93,10 @@
         }                                                                                                                   \
     }
 
-#define CHECK_GRAPHICS_PIPELINE_BOUND                                                       \
-    if (!((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->graphics_pipeline_bound) { \
-        SDL_assert_release(!"Graphics pipeline not bound!");                                \
-        return;                                                                             \
+#define CHECK_GRAPHICS_PIPELINE_BOUND                                                   \
+    if (!((RenderPass *)render_pass)->graphics_pipeline) { \
+        SDL_assert_release(!"Graphics pipeline not bound!");                            \
+        return;                                                                         \
     }
 
 #define CHECK_COMPUTEPASS                                     \
@@ -106,7 +106,7 @@
     }
 
 #define CHECK_COMPUTE_PIPELINE_BOUND                                                        \
-    if (!((CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER)->compute_pipeline_bound) { \
+    if (!((ComputePass *)compute_pass)->compute_pipeline) { \
         SDL_assert_release(!"Compute pipeline not bound!");                                 \
         return;                                                                             \
     }
@@ -174,12 +174,18 @@
 #define RENDERPASS_DEVICE \
     ((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->device
 
+#define RENDERPASS_BOUND_PIPELINE \
+    ((RenderPass *)render_pass)->graphics_pipeline
+
 #define COMPUTEPASS_COMMAND_BUFFER \
     ((Pass *)compute_pass)->command_buffer
 
 #define COMPUTEPASS_DEVICE \
     ((CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER)->device
 
+#define COMPUTEPASS_BOUND_PIPELINE \
+    ((ComputePass *)compute_pass)->compute_pipeline
+
 #define COPYPASS_COMMAND_BUFFER \
     ((Pass *)copy_pass)->command_buffer
 
@@ -511,6 +517,73 @@ void SDL_GPU_BlitCommon(
     SDL_EndGPURenderPass(render_pass);
 }
 
+static void SDL_GPU_CheckGraphicsBindings(SDL_GPURenderPass *render_pass)
+{
+    RenderPass *rp = (RenderPass *)render_pass;
+    GraphicsPipelineCommonHeader *pipeline = (GraphicsPipelineCommonHeader *)RENDERPASS_BOUND_PIPELINE;
+    for (Uint32 i = 0; i < pipeline->num_vertex_samplers; i += 1) {
+        if (!rp->vertex_sampler_bound[i]) {
+            SDL_assert_release(!"Missing vertex sampler binding!");
+        }
+    }
+    for (Uint32 i = 0; i < pipeline->num_vertex_storage_textures; i += 1) {
+        if (!rp->vertex_storage_texture_bound[i]) {
+            SDL_assert_release(!"Missing vertex storage texture binding!");
+        }
+    }
+    for (Uint32 i = 0; i < pipeline->num_vertex_storage_buffers; i += 1) {
+        if (!rp->vertex_storage_buffer_bound[i]) {
+            SDL_assert_release(!"Missing vertex storage buffer binding!");
+        }
+    }
+    for (Uint32 i = 0; i < pipeline->num_fragment_samplers; i += 1) {
+        if (!rp->fragment_sampler_bound[i]) {
+            SDL_assert_release(!"Missing fragment sampler binding!");
+        }
+    }
+    for (Uint32 i = 0; i < pipeline->num_fragment_storage_textures; i += 1) {
+        if (!rp->fragment_storage_texture_bound[i]) {
+            SDL_assert_release(!"Missing fragment storage texture binding!");
+        }
+    }
+    for (Uint32 i = 0; i < pipeline->num_fragment_storage_buffers; i += 1) {
+        if (!rp->fragment_storage_buffer_bound[i]) {
+            SDL_assert_release(!"Missing fragment storage buffer binding!");
+        }
+    }
+}
+
+static void SDL_GPU_CheckComputeBindings(SDL_GPUComputePass *compute_pass)
+{
+    ComputePass *cp = (ComputePass *)compute_pass;
+    ComputePipelineCommonHeader *pipeline = (ComputePipelineCommonHeader *)COMPUTEPASS_BOUND_PIPELINE;
+    for (Uint32 i = 0; i < pipeline->numSamplers; i += 1) {
+        if (!cp->sampler_bound[i]) {
+            SDL_assert_release(!"Missing compute sampler binding!");
+        }
+    }
+    for (Uint32 i = 0; i < pipeline->numReadonlyStorageTextures; i += 1) {
+        if (!cp->read_only_storage_texture_bound[i]) {
+            SDL_assert_release(!"Missing compute readonly storage texture binding!");
+        }
+    }
+    for (Uint32 i = 0; i < pipeline->numReadonlyStorageBuffers; i += 1) {
+        if (!cp->read_only_storage_buffer_bound[i]) {
+            SDL_assert_release(!"Missing compute readonly storage buffer binding!");
+        }
+    }
+    for (Uint32 i = 0; i < pipeline->numReadWriteStorageTextures; i += 1) {
+        if (!cp->read_write_storage_texture_bound[i]) {
+            SDL_assert_release(!"Missing compute read-write storage texture binding!");
+        }
+    }
+    for (Uint32 i = 0; i < pipeline->numReadWriteStorageBuffers; i += 1) {
+        if (!cp->read_write_storage_buffer_bound[i]) {
+            SDL_assert_release(!"Missing compute read-write storage buffer bbinding!");
+        }
+    }
+}
+
 // Driver Functions
 
 #ifndef SDL_GPU_DISABLED
@@ -1489,15 +1562,29 @@ SDL_GPUCommandBuffer *SDL_AcquireGPUCommandBuffer(
     commandBufferHeader = (CommandBufferCommonHeader *)command_buffer;
     commandBufferHeader->device = device;
     commandBufferHeader->render_pass.command_buffer = command_buffer;
-    commandBufferHeader->render_pass.in_progress = false;
-    commandBufferHeader->graphics_pipeline_bound = false;
     commandBufferHeader->compute_pass.command_buffer = command_buffer;
-    commandBufferHeader->compute_pass.in_progress = false;
-    commandBufferHeader->compute_pipeline_bound = false;
     commandBufferHeader->copy_pass.command_buffer = command_buffer;
-    commandBufferHeader->copy_pass.in_progress = false;
-    commandBufferHeader->swapchain_texture_acquired = false;
-    commandBufferHeader->submitted = false;
+
+    if (device->debug_mode) {
+        commandBufferHeader->render_pass.in_progress = false;
+        commandBufferHeader->render_pass.graphics_pipeline = NULL;
+        commandBufferHeader->compute_pass.in_progress = false;
+        commandBufferHeader->compute_pass.compute_pipeline = NULL;
+        commandBufferHeader->copy_pass.in_progress = false;
+        commandBufferHeader->swapchain_texture_acquired = false;
+        commandBufferHeader->submitted = false;
+        SDL_zeroa(commandBufferHeader->render_pass.vertex_sampler_bound);
+        SDL_zeroa(commandBufferHeader->render_pass.vertex_storage_texture_bound);
+        SDL_zeroa(commandBufferHeader->render_pass.vertex_storage_buffer_bound);
+        SDL_zeroa(commandBufferHeader->render_pass.fragment_sampler_bound);
+        SDL_zeroa(commandBufferHeader->render_pass.fragment_storage_texture_bound);
+        SDL_zeroa(commandBufferHeader->render_pass.fragment_storage_buffer_bound);
+        SDL_zeroa(commandBufferHeader->compute_pass.sampler_bound);
+        SDL_zeroa(commandBufferHeader->compute_pass.read_only_storage_texture_bound);
+        SDL_zeroa(commandBufferHeader->compute_pass.read_only_storage_buffer_bound);
+        SDL_zeroa(commandBufferHeader->compute_pass.read_write_storage_texture_bound);
+        SDL_zeroa(commandBufferHeader->compute_pass.read_write_storage_buffer_bound);
+    }
 
     return command_buffer;
 }
@@ -1688,14 +1775,18 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass(
         depth_stencil_target_info);
 
     commandBufferHeader = (CommandBufferCommonHeader *)command_buffer;
-    commandBufferHeader->render_pass.in_progress = true;
-    for (Uint32 i = 0; i < num_color_targets; i += 1) {
-        commandBufferHeader->render_pass.color_targets[i] = color_target_infos[i].texture;
-    }
-    commandBufferHeader->render_pass.num_color_targets = num_color_targets;
-    if (depth_stencil_target_info != NULL) {
-        commandBufferHeader->render_pass.depth_stencil_target = depth_stencil_target_info->texture;
+
+    if (COMMAND_BUFFER_DEVICE->debug_mode) {
+        commandBufferHeader->render_pass.in_progress = true;
+        for (Uint32 i = 0; i < num_color_targets; i += 1) {
+            commandBufferHeader->render_pass.color_targets[i] = color_target_infos[i].texture;
+        }
+        commandBufferHeader->render_pass.num_color_targets = num_color_targets;
+        if (depth_stencil_target_info != NULL) {
+            commandBufferHeader->render_pass.depth_stencil_target = depth_stencil_target_info->texture;
+        }
     }
+
     return (SDL_GPURenderPass *)&(commandBufferHeader->render_pass);
 }
 
@@ -1703,8 +1794,6 @@ void SDL_BindGPUGraphicsPipeline(
     SDL_GPURenderPass *render_pass,
     SDL_GPUGraphicsPipeline *graphics_pipeline)
 {
-    CommandBufferCommonHeader *commandBufferHeader;
-
     if (render_pass == NULL) {
         SDL_InvalidParamError("render_pass");
         return;
@@ -1718,8 +1807,10 @@ void SDL_BindGPUGraphicsPipeline(
         RENDERPASS_COMMAND_BUFFER,
         graphics_pipeline);
 
-    commandBufferHeader = (CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER;
-    commandBufferHeader->graphics_pipeline_bound = true;
+
+    if (RENDERPASS_DEVICE->debug_mode) {
+        RENDERPASS_BOUND_PIPELINE = graphics_pipeline;
+    }
 }
 
 void SDL_SetGPUViewport(
@@ -1874,6 +1965,10 @@ void SDL_BindGPUVertexSamplers(
         {
             CHECK_SAMPLER_TEXTURES
         }
+
+        for (Uint32 i = 0; i < num_bindings; i += 1) {
+            ((RenderPass *)render_pass)->vertex_sampler_bound[first_slot + i] = true;
+        }
     }
 
     RENDERPASS_DEVICE->BindVertexSamplers(
@@ -1901,6 +1996,10 @@ void SDL_BindGPUVertexStorageTextures(
     if (RENDERPASS_DEVICE->debug_mode) {
         CHECK_RENDERPASS
         CHECK_STORAGE_TEXTURES
+
+        for (Uint32 i = 0; i < num_bindings; i += 1) {
+            ((RenderPass *)render_pass)->vertex_storage_texture_bound[first_slot + i] = true;
+        }
     }
 
     RENDERPASS_DEVICE->BindVertexStorageTextures(
@@ -1927,6 +2026,10 @@ void SDL_BindGPUVertexStorageBuffers(
 
     if (RENDERPASS_DEVICE->debug_mode) {
         CHECK_RENDERPASS
+
+        for (Uint32 i = 0; i < num_bindings; i += 1) {
+            ((RenderPass *)render_pass)->vertex_storage_buffer_bound[first_slot + i] = true;
+        }
     }
 
     RENDERPASS_DEVICE->BindVertexStorageBuffers(
@@ -1954,10 +2057,13 @@ void SDL_BindGPUFragmentSamplers(
     if (RENDERPASS_DEVICE->debug_mode) {
         CHECK_RENDERPASS
 
-        if (!((CommandBufferCommonHeader*)RENDERPASS_COMMAND_BUFFER)->ignore_render_pass_texture_validation)
-        {
+        if (!((CommandBufferCommonHeader*)RENDERPASS_COMMAND_BUFFER)->ignore_render_pass_texture_validation) {
             CHECK_SAMPLER_TEXTURES
         }
+
+        for (Uint32 i = 0; i < num_bindings; i += 1) {
+            ((RenderPass *)render_pass)->fragment_sampler_bound[first_slot + i] = true;
+        }
     }
 
     RENDERPASS_DEVICE->BindFragmentSamplers(
@@ -1985,6 +2091,10 @@ void SDL_BindGPUFragmentStorageTextures(
     if (RENDERPASS_DEVICE->debug_mode) {
         CHECK_RENDERPASS
         CHECK_STORAGE_TEXTURES
+
+        for (Uint32 i = 0; i < num_bindings; i += 1) {
+            ((RenderPass *)render_pass)->fragment_storage_texture_bound[first_slot + i] = true;
+        }
     }
 
     RENDERPASS_DEVICE->BindFragmentStorageTextures(
@@ -2011,6 +2121,10 @@ void SDL_BindGPUFragmentStorageBuffers(
 
     if (RENDERPASS_DEVICE->debug_mode) {
         CHECK_RENDERPASS
+
+        for (Uint32 i = 0; i < num_bindings; i += 1) {
+            ((RenderPass *)render_pass)->fragment_storage_buffer_bound[first_slot + i] = true;
+        }
     }
 
     RENDERPASS_DEVICE->BindFragmentStorageBuffers(
@@ -2036,6 +2150,7 @@ void SDL_DrawGPUIndexedPrimitives(
     if (RENDERPASS_DEVICE->debug_mode) {
         CHECK_RENDERPASS
         CHECK_GRAPHICS_PIPELINE_BOUND
+        SDL_GPU_CheckGraphicsBindings(render_pass);
     }
 
     RENDERPASS_DEVICE->DrawIndexedPrimitives(
@@ -2062,6 +2177,7 @@ void SDL_DrawGPUPrimitives(
     if (RENDERPASS_DEVICE->debug_mode) {
         CHECK_RENDERPASS
         CHECK_GRAPHICS_PIPELINE_BOUND
+        SDL_GPU_CheckGraphicsBindings(render_pass);
     }
 
     RENDERPASS_DEVICE->DrawPrimitives(
@@ -2090,6 +2206,7 @@ void SDL_DrawGPUPrimitivesIndirect(
     if (RENDERPASS_DEVICE->debug_mode) {
         CHECK_RENDERPASS
         CHECK_GRAPHICS_PIPELINE_BOUND
+        SDL_GPU_CheckGraphicsBindings(render_pass);
     }
 
     RENDERPASS_DEVICE->DrawPrimitivesIndirect(
@@ -2117,6 +2234,7 @@ void SDL_DrawGPUIndexedPrimitivesIndirect(
     if (RENDERPASS_DEVICE->debug_mode) {
         CHECK_RENDERPASS
         CHECK_GRAPHICS_PIPELINE_BOUND
+        SDL_GPU_CheckGraphicsBindings(render_pass);
     }
 
     RENDERPASS_DEVICE->DrawIndexedPrimitivesIndirect(
@@ -2130,6 +2248,7 @@ void SDL_EndGPURenderPass(
     SDL_GPURenderPass *render_pass)
 {
     CommandBufferCommonHeader *commandBufferCommonHeader;
+    commandBufferCommonHeader = (CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER;
 
     if (render_pass == NULL) {
         SDL_InvalidParamError("render_pass");
@@ -2143,15 +2262,22 @@ void SDL_EndGPURenderPass(
     RENDERPASS_DEVICE->EndRenderPass(
         RENDERPASS_COMMAND_BUFFER);
 
-    commandBufferCommonHeader = (CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER;
-    commandBufferCommonHeader->render_pass.in_progress = false;
-    for (Uint32 i = 0; i < MAX_COLOR_TARGET_BINDINGS; i += 1)
-    {
-        commandBufferCommonHeader->render_pass.color_targets[i] = NULL;
+    if (RENDERPASS_DEVICE->debug_mode) {
+        commandBufferCommonHeader->render_pass.in_progress = false;
+        for (Uint32 i = 0; i < MAX_COLOR_TARGET_BINDINGS; i += 1)
+        {
+            commandBufferCommonHeader->render_pass.color_targets[i] = NULL;
+        }
+        commandBufferCommonHeader->render_pass.num_color_targets = 0;
+        commandBufferCommonHeader->render_pass.depth_stencil_target = NULL;
+        commandBufferCommonHeader->render_pass.graphics_pipeline = NULL;
+        SDL_zeroa(commandBufferCommonHeader->render_pass.vertex_sampler_bound);
+        SDL_zeroa(commandBufferCommonHeader->render_pass.vertex_storage_texture_bound);
+        SDL_zeroa(commandBufferCommonHeader->render_pass.vertex_storage_buffer_bound);
+        SDL_zeroa(commandBufferCommonHeader->render_pass.fragment_sampler_bound);
+        SDL_zeroa(commandBufferCommonHeader->render_pass.fragment_storage_texture_bound);
+        SDL_zeroa(commandBufferCommonHeader->render_pass.fragment_storage_buffer_bound);
     }
-    commandBufferCommonHeader->render_pass.num_color_targets = 0;
-    commandBufferCommonHeader->render_pass.depth_stencil_target = NULL;
-    commandBufferCommonHeader->graphics_pipeline_bound = false;
 }
 
 // Compute Pass
@@ -2218,7 +2344,19 @@ SDL_GPUComputePass *SDL_BeginGPUComputePass(
         num_storage_buffer_bindings);
 
     commandBufferHeader = (CommandBufferCommonHeader *)command_buffer;
-    commandBufferHeader->compute_pass.in_progress = true;
+
+    if (COMMAND_BUFFER_DEVICE->debug_mode) {
+        commandBufferHeader->compute_pass.in_progress = true;
+
+        for (Uint32 i = 0; i < num_storage_texture_bindings; i += 1) {
+            commandBufferHeader->compute_pass.read_write_storage_texture_bound[i] = true;
+        }
+
+        for (Uint32 i = 0; i < num_storage_buffer_bindings; i += 1) {
+            commandBufferHeader->compute_pass.read_write_storage_buffer_bound[i] = true;
+        }
+    }
+
     return (SDL_GPUComputePass *)&(commandBufferHeader->compute_pass);
 }
 
@@ -2226,8 +2364,6 @@ void SDL_BindGPUComputePipeline(
     SDL_GPUComputePass *compute_pass,
     SDL_GPUComputePipeline *compute_pipeline)
 {
-    CommandBufferCommonHeader *commandBufferHeader;
-
     if (compute_pass == NULL) {
         SDL_InvalidParamError("compute_pass");
         return;
@@ -2245,8 +2381,10 @@ void SDL_BindGPUComputePipeline(
         COMPUTEPASS_COMMAND_BUFFER,
         compute_pipeline);
 
-    commandBufferHeader = (CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER;
-    commandBufferHeader->compute_pipeline_bound = true;
+
+    if (COMPUTEPASS_DEVICE->debug_mode) {
+        COMPUTEPASS_BOUND_PIPELINE = compute_pipeline;
+    }
 }
 
 void SDL_BindGPUComputeSamplers(
@@ -2266,6 +2404,10 @@ void SDL_BindGPUComputeSamplers(
 
     if (COMPUTEPASS_DEVICE->debug_mode) {
         CHECK_COMPUTEPASS
+
+        for (Uint32 i = 0; i < num_bindings; i += 1) {
+            ((ComputePass *)compute_pass)->sampler_bound[first_slot + i] = true;
+        }
     }
 
     COMPUTEPASS_DEVICE->BindComputeSamplers(
@@ -2292,6 +2434,10 @@ void SDL_BindGPUComputeStorageTextures(
 
     if (COMPUTEPASS_DEVICE->debug_mode) {
         CHECK_COMPUTEPASS
+
+        for (Uint32 i = 0; i < num_bindings; i += 1) {
+            ((ComputePass *)compute_pass)->read_only_storage_texture_bound[first_slot + i] = true;
+        }
     }
 
     COMPUTEPASS_DEVICE->BindComputeStorageTextures(
@@ -2318,6 +2464,10 @@ void SDL_BindGPUComputeStorageBuffers(
 
     if (COMPUTEPASS_DEVICE->debug_mode) {
         CHECK_COMPUTEPASS
+
+        for (Uint32 i = 0; i < num_bindings; i += 1) {
+            ((ComputePass *)compute_pass)->read_only_storage_buffer_bound[first_slot + i] = true;
+        }
     }
 
     COMPUTEPASS_DEVICE->BindComputeStorageBuffers(
@@ -2341,6 +2491,7 @@ void SDL_DispatchGPUCompute(
     if (COMPUTEPASS_DEVICE->debug_mode) {
         CHECK_COMPUTEPASS
         CHECK_COMPUTE_PIPELINE_BOUND
+        SDL_GPU_CheckComputeBindings(compute_pass);
     }
 
     COMPUTEPASS_DEVICE->DispatchCompute(
@@ -2363,6 +2514,7 @@ void SDL_DispatchGPUComputeIndirect(
     if (COMPUTEPASS_DEVICE->debug_mode) {
         CHECK_COMPUTEPASS
         CHECK_COMPUTE_PIPELINE_BOUND
+        SDL_GPU_CheckComputeBindings(compute_pass);
     }
 
     COMPUTEPASS_DEVICE->DispatchComputeIndirect(
@@ -2388,9 +2540,16 @@ void SDL_EndGPUComputePass(
     COMPUTEPASS_DEVICE->EndComputePass(
         COMPUTEPASS_COMMAND_BUFFER);
 
-    commandBufferCommonHeader = (CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER;
-    commandBufferCommonHeader->compute_pass.in_progress = false;
-    commandBufferCommonHeader->compute_pipeline_bound = false;
+    if (COMPUTEPASS_DEVICE->debug_mode) {
+        commandBufferCommonHeader = (CommandBufferCommonHeader *)COMPUTEPASS_COMMAND_BUFFER;
+        commandBufferCommonHeader->compute_pass.in_progress = false;
+        commandBufferCommonHeader->compute_pass.compute_pipeline = false;
+        SDL_zeroa(commandBufferCommonHeader->compute_pass.sampler_bound);
+        SDL_zeroa(commandBufferCommonHeader->compute_pass.read_only_storage_texture_bound);
+        SDL_zeroa(commandBufferCommonHeader->compute_pass.read_only_storage_buffer_bound);
+        SDL_zeroa(commandBufferCommonHeader->compute_pass.read_write_storage_texture_bound);
+        SDL_zeroa(commandBufferCommonHeader->compute_pass.read_write_storage_buffer_bound);
+    }
 }
 
 // TransferBuffer Data
@@ -2448,7 +2607,11 @@ SDL_GPUCopyPass *SDL_BeginGPUCopyPass(
         command_buffer);
 
     commandBufferHeader = (CommandBufferCommonHeader *)command_buffer;
-    commandBufferHeader->copy_pass.in_progress = true;
+
+    if (COMMAND_BUFFER_DEVICE->debug_mode) {
+        commandBufferHeader->copy_pass.in_progress = true;
+    }
+
     return (SDL_GPUCopyPass *)&(commandBufferHeader->copy_pass);
 }
 
@@ -2699,7 +2862,9 @@ void SDL_EndGPUCopyPass(
     COPYPASS_DEVICE->EndCopyPass(
         COPYPASS_COMMAND_BUFFER);
 
-    ((CommandBufferCommonHeader *)COPYPASS_COMMAND_BUFFER)->copy_pass.in_progress = false;
+    if (COPYPASS_DEVICE->debug_mode) {
+        ((CommandBufferCommonHeader *)COPYPASS_COMMAND_BUFFER)->copy_pass.in_progress = false;
+    }
 }
 
 void SDL_GenerateMipmapsForGPUTexture(
diff --git a/src/gpu/SDL_sysgpu.h b/src/gpu/SDL_sysgpu.h
index 3c2c7866009ab..21dcfbd54a829 100644
--- a/src/gpu/SDL_sysgpu.h
+++ b/src/gpu/SDL_sysgpu.h
@@ -47,6 +47,20 @@ typedef struct Pass
     bool in_progress;
 } Pass;
 
+typedef struct ComputePass
+{
+    SDL_GPUCommandBuffer *command_buffer;
+    bool in_progress;
+
+    SDL_GPUComputePipeline *compute_pipeline;
+
+    bool sampler_bound[MAX_TEXTURE_SAMPLERS_PER_STAGE];
+    bool read_only_storage_texture_bound[MAX_STORAGE_TEXTURES_PER_STAGE];
+    bool read_only_storage_buffer_bound[MAX_STORAGE_BUFFERS_PER_STAGE];
+    bool read_write_storage_texture_bound[MAX_COMPUTE_WRITE_TEXTURES];
+    bool read_write_storage_buffer_bound[MAX_COMPUTE_WRITE_BUFFERS];
+} ComputePass;
+
 typedef struct RenderPass
 {
     SDL_GPUCommandBuffer *command_buffer;
@@ -54,15 +68,25 @@ typedef struct RenderPass
     SDL_GPUTexture *color_targets[MAX_COLOR_TARGET_BINDINGS];
     Uint32 num_color_targets;
     SDL_GPUTexture *depth_stencil_target;
+
+    SDL_GPUGraphicsPipeline *graphics_pipeline;
+
+    bool vertex_sampler_bound[MAX_TEXTURE_SAMPLERS_PER_STAGE];
+    bool vertex_storage_texture_bound[MAX_STORAGE_TEXTURES_PER_STAGE];
+    bool vertex_storage_buffer_bound[MAX_STORAGE_BUFFERS_PER_STAGE];
+
+    bool fragment_sampler_bound[MAX_TEXTURE_SAMPLERS_PER_STAGE];
+    bool fragment_storage_texture_bound[MAX_STORAGE_TEXTURES_PER_STAGE];
+    bool fragment_storage_buffer_bound[MAX_STORAGE_BUFFERS_PER_STAGE];
 } RenderPass;
 
 typedef struct CommandBufferCommonHeader
 {
     SDL_GPUDevice *device;
+
     RenderPass render_pass;
-    bool graphics_pipeline_bound;
-    Pass compute_pass;
-    bool compute_pipeline_bound;
+    ComputePass compute_pass;
+
     Pass copy_pass;
     bool swapchain_texture_acquired;
     bool submitted;
@@ -75,6 +99,29 @@ typedef struct TextureCommonHeader
     SDL_GPUTextureCreateInfo info;
 } TextureCommonHeader;
 
+typedef struct GraphicsPipelineCommonHeader
+{
+    Uint32 num_vertex_samplers;
+    Uint32 num_vertex_storage_textures;
+    Uint32 num_vertex_storage_buffers;
+    Uint32 num_vertex_uniform_buffers;
+
+    Uint32 num_fragment_samplers;
+    Uint32 num_fragment_storage_textures;
+    Uint32 num_fragment_storage_buffers;
+    Uint32 num_fragment_uniform_buffers;
+} GraphicsPipelineCommonHeader;
+
+typedef struct ComputePipelineCommonHeader
+{
+    Uint32 numSamplers;
+    Uint32 numReadonlyStorageTextures;
+    Uint32 numReadonlyStorageBuffers;
+    Uint32 numReadWriteStorageTextures;
+    Uint32 numReadWriteStorageBuffers;
+    Uint32 numUniformBuffers;
+} ComputePipelineCommonHeader;
+
 typedef struct BlitFragmentUniforms
 {
     // texcoord space
diff --git a/src/gpu/d3d12/SDL_gpu_d3d12.c b/src/gpu/d3d12/SDL_gpu_d3d12.c
index 96a33634dfacc..a92215910c109 100644
--- a/src/gpu/d3d12/SDL_gpu_d3d12.c
+++ b/src/gpu/d3d12/SDL_gpu_d3d12.c
@@ -1015,26 +1015,38 @@ struct D3D12CommandBuffer
     Uint32 vertexBufferOffsets[MAX_VERTEX_BUFFERS];
     Uint32 vertexBufferCount;
 
-    D3D12Texture *vertexSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE];
-    D3D12Sampler *vertexSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE];
-    D3D12Texture *vertexStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE];
-    D3D12Buffer *vertexStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE];
+    D3D12_CPU_DESCRIPTOR_HANDLE vertexSamplerTextureDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE];
+    D3D12_CPU_DESCRIPTOR_HANDLE vertexSamplerDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE];
+    D3D12_CPU_DESCRIPTOR_HANDLE vertexStorageTextureDescriptorHandles[MAX_STORAGE_TEXTURES_PER_STAGE];
+    D3D12_CPU_DESCRIPTOR_HANDLE vertexStorageBufferDescriptorHandles[MAX_STORAGE_BUFFERS_PER_STAGE];
+
     D3D12UniformBuffer *vertexUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE];
 
-    D3D12Texture *fragmentSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE];
-    D3D12Sampler *fragmentSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE];
-    D3D12Texture *fragmentStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE];
-    D3D12Buffer *fragmentStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE];
+    D3D12_CPU_DESCRIPTOR_HANDLE fragmentSamplerTextureDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE];
+    D3D12_CPU_DESCRIPTOR_HANDLE fragmentSamplerDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE];
+    D3D12_CPU_DESCRIPTOR_HANDLE fragmentStorageTextureDescriptorHandles[MAX_STORAGE_TEXTURES_PER_STAGE];
+    D3D12_CPU_DESCRIPTOR_HANDLE fragmentStorageBufferDescriptorHandles[MAX_STORAGE_BUFFERS_PER_STAGE];
+
     D3D12UniformBuffer *fragmentUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE];
 
-    D3D12Texture *computeSamplerTextures[MAX_TEXTURE_SAMPLERS_PER_STAGE];
-    D3D12Sampler *computeSamplers[MAX_TEXTURE_SAMPLERS_PER_STAGE];
+    D3D12_CPU_DESCRIPTOR_HANDLE computeSamplerTextureDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE];
+    D3D12_CPU_DESCRIPTOR_HANDLE computeSamplerDescriptorHandles[MAX_TEXTURE_SAMPLERS_PER_STAGE];
+    D3D12_CPU_DESCRIPTOR_HANDLE computeReadOnlyStorageTextureDescriptorHandles[MAX_STORAGE_TEXTURES_PER_STAGE];
+    D3D12_CPU_DESCRIPTOR_HANDLE computeReadOnlyStorageBufferDescriptorHandles[MAX_STORAGE_BUFFERS_PER_STAGE];
+
+    // Track these separately because barriers can happen mid compute pass
     D3D12Texture *computeReadOnlyStorageTextures[MAX_STORAGE_TEXTURES_PER_STAGE];
     D3D12Buffer *computeReadOnlyStorageBuffers[MAX_STORAGE_BUFFERS_PER_STAGE];
+
+    D3D12_CPU_DESCRIPTOR_HANDLE computeReadWriteStorageTextureDescriptorHandles[MAX_COMPUTE_WRITE_TEXTURES];
+    D3D12_CPU_DESCRIPTOR_HANDLE computeReadWriteStorageBufferDescriptorHandles[MAX_COMPUTE_WRITE_BUFFERS];
+
+    // Track these separately because they are bound when the compute pass begins
     D3D12TextureSubresource *computeReadWriteStorageTextureSubresources[MAX_COMPUTE_WRITE_TEXTURES];
     Uint32 computeReadWriteStorageTextureSubresourceCount;
     D3D12Buffer *computeReadWriteStorageBuffers[MAX_COMPUTE_WRITE_BUFFERS];
     Uint32 computeReadWriteStorageBufferCount;
+
     D3D12UniformBuffer *computeUniformBuffers[MAX_UNIFORM_BUFFERS_PER_STAGE];
 
     // Resource tracking
@@ -1098,22 +1110,14 @@ typedef struct D3D12GraphicsRootSignature
 
 struct D3D12GraphicsPipeline
 {
+    GraphicsPipelineCommonHeader header;
+
     ID3D12PipelineState *pipelineState;
     D3D12GraphicsRootSignature *rootSignature;
     SDL_GPUPrimitiveType primitiveType;
 
     Uint32 vertexStrides[MAX_VERTEX_BUFFERS];
 
-    Uint32 vertexSamplerCount;
-    Uint32 vertexUniformBufferCount;
-    Uint32 vertexStorageBufferCount;
-    Uint32 vertexStorageTextureCount;
-
-    Uint32 fragmentSamplerCount;
-    Uint32 fragmentUniformBufferCount;
-    Uint32 fragmentStorageBufferCount;
-    Uint32 fragmentStorageTextureCount;
-
     SDL_AtomicInt referenceCount;
 };
 
@@ -1132,16 +1136,11 @@ typedef struct D3D12ComputeRootSignature
 
 struct D3D12ComputePipeline
 {
+    ComputePipelineCommonHeader header;
+
     ID3D12PipelineState *pipelineState;
     D3D12ComputeRootSignature *rootSignature;
 
-    Uint32 numSamplers;
-    Uint32 numReadOnlyStorageTextures;
-    Uint32 numReadOnlyStorageBuffers;
-    Uint32 numReadWriteStorageTextures;
-    Uint32 numReadWriteStorageBuffers;
-    Uint32 numUniformBuffers;
-
     SDL_AtomicInt referenceCount;
 };
 
@@ -2886,12 +2885,12 @@ static SDL_GPUComputePipeline *D3D12_CreateComputePipeline(
 
     computePipeline->pipelineState = pipelineState;
     computePipeline->rootSignature = rootSignature;
-    computePipeline->numSamplers = createinfo->num_samplers;
-    computePipeline->numReadOnlyStorageTextures = createinfo->num_readonly_storage_textures;
-    computePipeline->numReadOnlyStorageBuffers = createinfo->num_readonly_storage_buffers;
-    computePipeline->numReadWriteStorageTextures = createinfo->num_readwrite_storage_textures;
-    computePipeline->numReadWriteStorageBuffers = createinfo->num_readwrite_storage_buffers;
-    computePipeline->numUniformBuffers = createinfo->num_uniform_buffers;
+    computePipeline->header.numSamplers = createinfo->num_samplers;
+    computePipeline->header.numReadonlyStorageTextures = createinfo->num_readonly_storage_textures;
+    computePipeline->header.numReadonlyStorageBuffers = createinfo->num_readonly_storage_buffers;
+    computePipeline->header.numReadWriteStorageTextures = createinfo->num_readwrite_storage_textures;
+    computePipeline->header.numReadWriteStorageBuffers = createinfo->num_readwrite_storage_buffers;
+    computePipeline->header.numUniformBuffers = createinfo->num_uniform_buffers;
     SDL_SetAtomicInt(&computePipeline->referenceCount, 0);
 
     if (renderer->debug_mode && SDL_HasProperty(createinfo->props, SDL_PROP_GPU_COMPUTEPIPELINE_CREATE_NAME_STRING)) {
@@ -3172,15 +3171,15 @@ static SDL_GPUGraphicsPipeline *D3D12_CreateGraphicsPipeline(
 
     pipeline->primitiveType = createinfo->primitive_type;
 
-    pipeline->vertexSamplerCount = vertShader->num_samplers;
-    pipeline->vertexStorageTextureCount = vertShader->numStorageTextures;
-    pipeline->vertexStorageBufferCount = vertShader->numStorageBuffers;
-    pipeline->vertexUniformBufferCount = vertShader->numUniformBuffers;
+    pipeline->header.num_vertex_samplers = vertShader->num_samplers;
+    pipeline->header.num_vertex_storage_textures = vertShader->numStorageTextures;
+    pipeline->header.num_vertex_storage_buffers = vertShader->numStorageBuffers;
+    pipeline->header.num_vertex_uniform_buffers = vertShader->numUniformBuffers;
 
-    pipeline->fragmentSamplerCount = fragShader->num_samplers;
-    pipeline->fragmentStorageTextureCount = fragShader->numStorageTextures;
-    pipeline->fragmentStorageBufferCount = fragShader->numStorageBuffers;
-    pipeline->fragmentUniformBufferCount = fragShader->numUniformBuffers;
+    pipeline->header.num_fragment_samplers = fragShader->num_samplers;
+    pipeline->header.num_fragment_storage_textures = fragShader->numStorageTextures;
+    pipeline->header.num_fragment_storage_buffers = fragShader->numStorageBuffers;
+    pipeline->header.num_fragment_uniform_buffers = fragShader->numUniformBuffers;
 
     SDL_SetAtomicInt(&pipeline->referenceCount, 0);
 
@@ -4637,14 +4636,14 @@ static void D3D12_BindGraphicsPipeline(
         d3d12CommandBuffer->needFragmentUniformBufferBind[i] = true;
     }
 
-    for (i = 0; i < pipeline->vertexUniformBufferCount; i += 1) {
+    for (i = 0; i < pipeline->header.num_vertex_uniform_buffers; i += 1) {
         if (d3d12CommandBuffer->vertexUniformBuffers[i] == NULL) {
             d3d12CommandBuffer->vertexUniformBuffers[i] = D3D12_INTERNAL_AcquireUniformBufferFromPool(
                 d3d12CommandBuffer);
         }
     }
 
-    for (i = 0; i < pipeline->fragmentUniformBufferCount; i += 1) {
+    for (i = 0; i < pipeline->header.num_fragment_uniform_buffers; i += 1) {
         if (d3d12CommandBuffer->fragmentUniformBuffers[i] == NULL) {
             d3d12CommandBuffer->fragmentUniformBuffers[i] = D3D12_INTERNAL_AcquireUniformBufferFromPool(
                 d3d12CommandBuffer);
@@ -4711,21 +4710,21 @@ static void D3D12_BindVertexSamplers(
         D3D12TextureContainer *container = (D3D12TextureContainer *)textureSamplerBindings[i].texture;
         D3D12Sampler *sampler = (D3D12Sampler 

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