From 2e3300e87232bd6473a7b22e30161d153df49d1d Mon Sep 17 00:00:00 2001
From: Void Star Caster <[EMAIL REDACTED]>
Date: Mon, 2 Mar 2026 21:50:58 +0100
Subject: [PATCH] Functions to bind GPURenderState storage textures, buffers
and sampler bindings (#15150)
---
include/SDL3/SDL_render.h | 57 ++++++++++++++++++++++++++
src/dynapi/SDL_dynapi.sym | 3 ++
src/dynapi/SDL_dynapi_overrides.h | 3 ++
src/dynapi/SDL_dynapi_procs.h | 3 ++
src/render/SDL_render.c | 66 +++++++++++++++++++++++++++++++
5 files changed, 132 insertions(+)
diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h
index 986130ce46b46..4f39e19e6ba88 100644
--- a/include/SDL3/SDL_render.h
+++ b/include/SDL3/SDL_render.h
@@ -2967,6 +2967,63 @@ typedef struct SDL_GPURenderState SDL_GPURenderState;
*/
extern SDL_DECLSPEC SDL_GPURenderState * SDLCALL SDL_CreateGPURenderState(SDL_Renderer *renderer, const SDL_GPURenderStateCreateInfo *createinfo);
+/**
+ * Set sampler bindings variables in a custom GPU render state.
+ *
+ * The data is copied and will be binded using
+ * SDL_BindGPUFragmentSamplers() during draw call execution.
+ *
+ * \param state the state to modify.
+ * \param num_sampler_bindings The number of additional fragment samplers to bind
+ * \param sampler_bindings Additional fragment samplers to bind
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ * information.
+ *
+ * \threadsafety This function should be called on the thread that created the
+ * renderer.
+ *
+ * \since This function is available since SDL 3.4.x.
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_SetGPURenderStateSamplerBindings(SDL_GPURenderState *state, int num_sampler_bindings, const SDL_GPUTextureSamplerBinding *sampler_bindings);
+
+/**
+ * Set storage textures variables in a custom GPU render state.
+ *
+ * The data is copied and will be binded using
+ * SDL_BindGPUFragmentStorageTextures() during draw call execution.
+ *
+ * \param state the state to modify.
+ * \param num_storage_textures The number of storage textures to bind
+ * \param storage_textures Storage textures to bind
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ * information.
+ *
+ * \threadsafety This function should be called on the thread that created the
+ * renderer.
+ *
+ * \since This function is available since SDL 3.4.x.
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_SetGPURenderStateStorageTextures(SDL_GPURenderState *state, int num_storage_textures, SDL_GPUTexture *const *storage_textures);
+
+/**
+ * Set storage buffers variables in a custom GPU render state.
+ *
+ * The data is copied and will be binded using
+ * SDL_BindGPUFragmentStorageBuffers() during draw call execution.
+ *
+ * \param state the state to modify.
+ * \param num_storage_buffers The number of storage buffers to bind
+ * \param storage_buffers Storage buffers to bind
+ * \returns true on success or false on failure; call SDL_GetError() for more
+ * information.
+ *
+ * \threadsafety This function should be called on the thread that created the
+ * renderer.
+ *
+ * \since This function is available since SDL 3.4.x.
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_SetGPURenderStateStorageBuffers(SDL_GPURenderState *state, int num_storage_buffers, SDL_GPUBuffer *const *storage_buffers);
+
/**
* Set fragment shader uniform variables in a custom GPU render state.
*
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 86ff0a684b276..184f8708eabb9 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1280,6 +1280,9 @@ SDL3_0.0.0 {
SDL_OpenXR_UnloadLibrary;
SDL_OpenXR_GetXrGetInstanceProcAddr;
SDL_CreateTrayWithProperties;
+ SDL_SetGPURenderStateSamplerBindings;
+ SDL_SetGPURenderStateStorageTextures;
+ SDL_SetGPURenderStateStorageBuffers;
# 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 acee86998e2ce..87762e6d7a133 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1306,3 +1306,6 @@
#define SDL_OpenXR_UnloadLibrary SDL_OpenXR_UnloadLibrary_REAL
#define SDL_OpenXR_GetXrGetInstanceProcAddr SDL_OpenXR_GetXrGetInstanceProcAddr_REAL
#define SDL_CreateTrayWithProperties SDL_CreateTrayWithProperties_REAL
+#define SDL_SetGPURenderStateSamplerBindings SDL_SetGPURenderStateSamplerBindings_REAL
+#define SDL_SetGPURenderStateStorageTextures SDL_SetGPURenderStateStorageTextures_REAL
+#define SDL_SetGPURenderStateStorageBuffers SDL_SetGPURenderStateStorageBuffers_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 68dca5a19233b..d965c7b628d1b 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1314,3 +1314,6 @@ SDL_DYNAPI_PROC(bool,SDL_OpenXR_LoadLibrary,(void),(),return)
SDL_DYNAPI_PROC(void,SDL_OpenXR_UnloadLibrary,(void),(),)
SDL_DYNAPI_PROC(PFN_xrGetInstanceProcAddr,SDL_OpenXR_GetXrGetInstanceProcAddr,(void),(),return)
SDL_DYNAPI_PROC(SDL_Tray*,SDL_CreateTrayWithProperties,(SDL_PropertiesID a),(a),return)
+SDL_DYNAPI_PROC(bool,SDL_SetGPURenderStateSamplerBindings,(SDL_GPURenderState *a,int b,const SDL_GPUTextureSamplerBinding *c),(a,b,c),return)
+SDL_DYNAPI_PROC(bool,SDL_SetGPURenderStateStorageTextures,(SDL_GPURenderState *a,int b,SDL_GPUTexture *const*c),(a,b,c),return)
+SDL_DYNAPI_PROC(bool,SDL_SetGPURenderStateStorageBuffers,(SDL_GPURenderState *a,int b,SDL_GPUBuffer *const*c),(a,b,c),return)
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index 8338255250193..684ad7b82d533 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -6210,6 +6210,72 @@ SDL_GPURenderState *SDL_CreateGPURenderState(SDL_Renderer *renderer, const SDL_G
return state;
}
+bool SDL_SetGPURenderStateSamplerBindings(SDL_GPURenderState *state, int num_sampler_bindings, const SDL_GPUTextureSamplerBinding *sampler_bindings)
+{
+ if (!state) {
+ return SDL_InvalidParamError("state");
+ }
+
+ if (!FlushRenderCommandsIfGPURenderStateNeeded(state)) {
+ return false;
+ }
+
+ Sint32 length = sizeof(SDL_GPUTextureSamplerBinding) * num_sampler_bindings;
+ SDL_GPUTextureSamplerBinding *new_sampler_bindings = (SDL_GPUTextureSamplerBinding *)SDL_realloc(state->sampler_bindings, length);
+ if (!new_sampler_bindings) {
+ return false;
+ }
+ SDL_memcpy(new_sampler_bindings, sampler_bindings, length);
+ state->num_sampler_bindings = num_sampler_bindings;
+ state->sampler_bindings = new_sampler_bindings;
+
+ return true;
+}
+
+bool SDL_SetGPURenderStateStorageTextures(SDL_GPURenderState *state, int num_storage_textures, SDL_GPUTexture *const *storage_textures)
+{
+ if (!state) {
+ return SDL_InvalidParamError("state");
+ }
+
+ if (!FlushRenderCommandsIfGPURenderStateNeeded(state)) {
+ return false;
+ }
+
+ Sint32 length = sizeof(SDL_GPUTexture *) * num_storage_textures;
+ SDL_GPUTexture **new_storage_textures = (SDL_GPUTexture **)SDL_realloc(state->storage_textures, length);
+ if (!new_storage_textures) {
+ return false;
+ }
+ SDL_memcpy(new_storage_textures, storage_textures, length);
+ state->num_storage_textures = num_storage_textures;
+ state->storage_textures = new_storage_textures;
+
+ return true;
+}
+
+bool SDL_SetGPURenderStateStorageBuffers(SDL_GPURenderState *state, int num_storage_buffers, SDL_GPUBuffer *const *storage_buffers)
+{
+ if (!state) {
+ return SDL_InvalidParamError("state");
+ }
+
+ if (!FlushRenderCommandsIfGPURenderStateNeeded(state)) {
+ return false;
+ }
+
+ Sint32 length = sizeof(SDL_GPUBuffer *) * num_storage_buffers;
+ SDL_GPUBuffer **new_storage_buffers = (SDL_GPUBuffer **)SDL_realloc(state->storage_buffers, length);
+ if (!new_storage_buffers) {
+ return false;
+ }
+ SDL_memcpy(new_storage_buffers, storage_buffers, length);
+ state->num_storage_buffers = num_storage_buffers;
+ state->storage_buffers = new_storage_buffers;
+
+ return true;
+}
+
bool SDL_SetGPURenderStateFragmentUniforms(SDL_GPURenderState *state, Uint32 slot_index, const void *data, Uint32 length)
{
if (!state) {