# SDL: GPU: Validate that textures are not bound for both read and write on render passes (#12925)

**URL:** <https://discourse.libsdl.org/t/sdl-gpu-validate-that-textures-are-not-bound-for-both-read-and-write-on-render-passes-12925/59454>\
**Category:** SDL Commits\
**Created:** [April 29, 2025, 11:52pm UTC](https://discourse.libsdl.org/t/sdl-gpu-validate-that-textures-are-not-bound-for-both-read-and-write-on-render-passes-12925/59454 "2025-04-29T23:52:54Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![SDLPushPostBot](https://discourse.libsdl.org/user_avatar/discourse.libsdl.org/sdlpushpostbot/32/4370_2.png) [@SDLPushPostBot](https://discourse.libsdl.org/u/SDLPushPostBot)\
**Post date:** [April 29, 2025, 11:52pm UTC](https://discourse.libsdl.org/t/sdl-gpu-validate-that-textures-are-not-bound-for-both-read-and-write-on-render-passes-12925/59454/1 "2025-04-29T23:52:55Z")

</div>

> <https://github.com/libsdl-org/SDL/commit/a1632572955f1a47c3981f41d98533d07fd59779>

```diff
From a1632572955f1a47c3981f41d98533d07fd59779 Mon Sep 17 00:00:00 2001
From: Evan Hemsley <[EMAIL REDACTED]>
Date: Tue, 29 Apr 2025 16:52:52 -0700
Subject: [PATCH] GPU: Validate that textures are not bound for both read and
 write on render passes (#12925)

---
 src/gpu/SDL_gpu.c | 53 ++++++++++++++++++++++++++++++++++++++++++--
 src/gpu/SDL_sysgpu.h | 41 +++++++++++++++++++++-------------
 2 files changed, 76 insertions(+), 18 deletions(-)

diff --git a/src/gpu/SDL_gpu.c b/src/gpu/SDL_gpu.c
index 7532c3a64de7a..41ca388553375 100644
--- a/src/gpu/SDL_gpu.c
+++ b/src/gpu/SDL_gpu.c
@@ -56,11 +56,43 @@
     }
 
 #define CHECK_RENDERPASS \
- if (!((Pass *)render_pass)->in_progress) { \
+ if (!((RenderPass *)render_pass)->in_progress) { \
         SDL_assert_release(!"Render pass not in progress!"); \
         return; \
     }
 
+#define CHECK_SAMPLER_TEXTURES \
+ RenderPass *rp = (RenderPass *)render_pass; \
+ for (Uint32 color_target_index = 0; color_target_index < rp->num_color_targets; color_target_index += 1) { \
+ for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \
+ if (rp->color_targets[color_target_index] == texture_sampler_bindings[texture_sampler_index].texture) { \
+ SDL_assert_release(!"Texture cannot be simultaneously bound as a color target and a sampler!"); \
+ } \
+ } \
+ } \
+ \
+ for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \
+ if (rp->depth_stencil_target != NULL && rp->depth_stencil_target == texture_sampler_bindings[texture_sampler_index].texture) { \
+ SDL_assert_release(!"Texture cannot be simultaneously bound as a depth stencil target and a sampler!"); \
+ } \
+ }
+
+#define CHECK_STORAGE_TEXTURES \
+ RenderPass *rp = (RenderPass *)render_pass; \
+ for (Uint32 color_target_index = 0; color_target_index < rp->num_color_targets; color_target_index += 1) { \
+ for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \
+ if (rp->color_targets[color_target_index] == storage_textures[texture_sampler_index]) { \
+ SDL_assert_release(!"Texture cannot be simultaneously bound as a color target and a storage texture!"); \
+ } \
+ } \
+ } \
+ \
+ for (Uint32 texture_sampler_index = 0; texture_sampler_index < num_bindings; texture_sampler_index += 1) { \
+ if (rp->depth_stencil_target != NULL && rp->depth_stencil_target == storage_textures[texture_sampler_index]) { \
+ SDL_assert_release(!"Texture cannot be simultaneously bound as a depth stencil target and a storage texture!"); \
+ } \
+ }
+
 #define CHECK_GRAPHICS_PIPELINE_BOUND \
     if (!((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->graphics_pipeline_bound) { \
         SDL_assert_release(!"Graphics pipeline not bound!"); \
@@ -137,7 +169,7 @@
     ((CommandBufferCommonHeader *)command_buffer)->device
 
 #define RENDERPASS_COMMAND_BUFFER \
- ((Pass *)render_pass)->command_buffer
+ ((RenderPass *)render_pass)->command_buffer
 
 #define RENDERPASS_DEVICE \
     ((CommandBufferCommonHeader *)RENDERPASS_COMMAND_BUFFER)->device
@@ -1544,6 +1576,13 @@ SDL_GPURenderPass *SDL_BeginGPURenderPass(
 
     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;
+ }
     return (SDL_GPURenderPass *)&(commandBufferHeader->render_pass);
 }
 
@@ -1717,6 +1756,7 @@ void SDL_BindGPUVertexSamplers(
 
     if (RENDERPASS_DEVICE->debug_mode) {
         CHECK_RENDERPASS
+ CHECK_SAMPLER_TEXTURES
     }
 
     RENDERPASS_DEVICE->BindVertexSamplers(
@@ -1743,6 +1783,7 @@ void SDL_BindGPUVertexStorageTextures(
 
     if (RENDERPASS_DEVICE->debug_mode) {
         CHECK_RENDERPASS
+ CHECK_STORAGE_TEXTURES
     }
 
     RENDERPASS_DEVICE->BindVertexStorageTextures(
@@ -1795,6 +1836,7 @@ void SDL_BindGPUFragmentSamplers(
 
     if (RENDERPASS_DEVICE->debug_mode) {
         CHECK_RENDERPASS
+ CHECK_SAMPLER_TEXTURES
     }
 
     RENDERPASS_DEVICE->BindFragmentSamplers(
@@ -1821,6 +1863,7 @@ void SDL_BindGPUFragmentStorageTextures(
 
     if (RENDERPASS_DEVICE->debug_mode) {
         CHECK_RENDERPASS
+ CHECK_STORAGE_TEXTURES
     }
 
     RENDERPASS_DEVICE->BindFragmentStorageTextures(
@@ -1981,6 +2024,12 @@ void SDL_EndGPURenderPass(
 
     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;
+ }
+ commandBufferCommonHeader->render_pass.num_color_targets = 0;
+ commandBufferCommonHeader->render_pass.depth_stencil_target = NULL;
     commandBufferCommonHeader->graphics_pipeline_bound = false;
 }
 
diff --git a/src/gpu/SDL_sysgpu.h b/src/gpu/SDL_sysgpu.h
index ddcfe57e77ad3..ebc195c29becc 100644
--- a/src/gpu/SDL_sysgpu.h
+++ b/src/gpu/SDL_sysgpu.h
@@ -24,6 +24,21 @@
 #ifndef SDL_GPU_DRIVER_H
 #define SDL_GPU_DRIVER_H
 
+// GraphicsDevice Limits
+
+#define MAX_TEXTURE_SAMPLERS_PER_STAGE 16
+#define MAX_STORAGE_TEXTURES_PER_STAGE 8
+#define MAX_STORAGE_BUFFERS_PER_STAGE 8
+#define MAX_UNIFORM_BUFFERS_PER_STAGE 4
+#define MAX_COMPUTE_WRITE_TEXTURES 8
+#define MAX_COMPUTE_WRITE_BUFFERS 8
+#define UNIFORM_BUFFER_SIZE 32768
+#define MAX_VERTEX_BUFFERS 16
+#define MAX_VERTEX_ATTRIBUTES 16
+#define MAX_COLOR_TARGET_BINDINGS 4
+#define MAX_PRESENT_COUNT 16
+#define MAX_FRAMES_IN_FLIGHT 3
+
 // Common Structs
 
 typedef struct Pass
@@ -32,10 +47,19 @@ typedef struct Pass
     bool in_progress;
 } Pass;
 
+typedef struct RenderPass
+{
+ SDL_GPUCommandBuffer *command_buffer;
+ bool in_progress;
+ SDL_GPUTexture *color_targets[MAX_COLOR_TARGET_BINDINGS];
+ Uint32 num_color_targets;
+ SDL_GPUTexture *depth_stencil_target;
+} RenderPass;
+
 typedef struct CommandBufferCommonHeader
 {
     SDL_GPUDevice *device;
- Pass render_pass;
+ RenderPass render_pass;
     bool graphics_pipeline_bound;
     Pass compute_pass;
     bool compute_pipeline_bound;
@@ -535,21 +559,6 @@ static inline Uint32 BytesPerRow(
     return blocksPerRow * SDL_GPUTextureFormatTexelBlockSize(format);
 }
 
-// GraphicsDevice Limits
-
-#define MAX_TEXTURE_SAMPLERS_PER_STAGE 16
-#define MAX_STORAGE_TEXTURES_PER_STAGE 8
-#define MAX_STORAGE_BUFFERS_PER_STAGE 8
-#define MAX_UNIFORM_BUFFERS_PER_STAGE 4
-#define MAX_COMPUTE_WRITE_TEXTURES 8
-#define MAX_COMPUTE_WRITE_BUFFERS 8
-#define UNIFORM_BUFFER_SIZE 32768
-#define MAX_VERTEX_BUFFERS 16
-#define MAX_VERTEX_ATTRIBUTES 16
-#define MAX_COLOR_TARGET_BINDINGS 4
-#define MAX_PRESENT_COUNT 16
-#define MAX_FRAMES_IN_FLIGHT 3
-
 // Internal Macros
 
 #define EXPAND_ARRAY_IF_NEEDED(arr, elementType, newCount, capacity, newCapacity) \

```
