SDL: Copy shader params instead of caching a pointer to them

From aafbf7183f9fc91d17bc87f14cd6dac2dd6a3b05 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 1 Nov 2025 10:24:01 -0700
Subject: [PATCH] Copy shader params instead of caching a pointer to them

It's possible for a new texture to be allocated with the same address as a previous one, so we can't just cache the pointer value.

Fixes https://github.com/libsdl-org/SDL/issues/14369
---
 src/render/opengl/SDL_render_gl.c       |  1 +
 src/render/opengl/SDL_shaders_gl.c      | 27 ++++++++++++++--
 src/render/opengles2/SDL_render_gles2.c | 42 +++++++++++++++++++------
 3 files changed, 57 insertions(+), 13 deletions(-)

diff --git a/src/render/opengl/SDL_render_gl.c b/src/render/opengl/SDL_render_gl.c
index 1b195d53121c5..9a868a6a69033 100644
--- a/src/render/opengl/SDL_render_gl.c
+++ b/src/render/opengl/SDL_render_gl.c
@@ -1671,6 +1671,7 @@ static void GL_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture)
 
     if (renderdata->drawstate.texture == texture) {
         renderdata->drawstate.texture = NULL;
+        renderdata->drawstate.shader_params = NULL;
     }
     if (renderdata->drawstate.target == texture) {
         renderdata->drawstate.target = NULL;
diff --git a/src/render/opengl/SDL_shaders_gl.c b/src/render/opengl/SDL_shaders_gl.c
index 164875127172e..d518703e28441 100644
--- a/src/render/opengl/SDL_shaders_gl.c
+++ b/src/render/opengl/SDL_shaders_gl.c
@@ -59,7 +59,7 @@ struct GL_ShaderContext
     bool GL_ARB_texture_rectangle_supported;
 
     GL_ShaderData shaders[NUM_SHADERS];
-    const float *shader_params[NUM_SHADERS];
+    float *shader_params[NUM_SHADERS];
 };
 
 /* *INDENT-OFF* */ // clang-format off
@@ -703,7 +703,22 @@ void GL_SelectShader(GL_ShaderContext *ctx, GL_Shader shader, const float *shade
 
     ctx->glUseProgramObjectARB(program);
 
-    if (shader_params && shader_params != ctx->shader_params[shader]) {
+    int shader_params_len = 0;
+    if (shader == SHADER_PALETTE_LINEAR ||
+        shader == SHADER_PALETTE_PIXELART ||
+        shader == SHADER_RGB_PIXELART ||
+        shader == SHADER_RGBA_PIXELART) {
+        shader_params_len = 4 * sizeof(float);
+#ifdef SDL_HAVE_YUV
+    } else if (shader >= SHADER_YUV) {
+        shader_params_len = 16 * sizeof(float);
+#endif
+    }
+    SDL_assert(!shader_params || shader_params_len > 0);
+
+    if (shader_params &&
+        (!ctx->shader_params[shader] ||
+         SDL_memcmp(shader_params, ctx->shader_params[shader], shader_params_len) != 0)) {
         if (shader == SHADER_PALETTE_LINEAR ||
             shader == SHADER_PALETTE_PIXELART ||
             shader == SHADER_RGB_PIXELART ||
@@ -736,7 +751,12 @@ void GL_SelectShader(GL_ShaderContext *ctx, GL_Shader shader, const float *shade
         }
 #endif // SDL_HAVE_YUV
 
-        ctx->shader_params[shader] = shader_params;
+        if (!ctx->shader_params[shader]) {
+            ctx->shader_params[shader] = (float *)SDL_malloc(shader_params_len);
+        }
+        if (ctx->shader_params[shader]) {
+            SDL_memcpy(ctx->shader_params[shader], shader_params, shader_params_len);
+        }
     }
 }
 
@@ -746,6 +766,7 @@ void GL_DestroyShaderContext(GL_ShaderContext *ctx)
 
     for (i = 0; i < NUM_SHADERS; ++i) {
         DestroyShaderProgram(ctx, &ctx->shaders[i]);
+        SDL_free(ctx->shader_params[i]);
     }
     SDL_free(ctx);
 }
diff --git a/src/render/opengles2/SDL_render_gles2.c b/src/render/opengles2/SDL_render_gles2.c
index 3f7e37f1fbb9b..4ea7151503f1f 100644
--- a/src/render/opengles2/SDL_render_gles2.c
+++ b/src/render/opengles2/SDL_render_gles2.c
@@ -127,7 +127,7 @@ typedef struct GLES2_ProgramCacheEntry
     GLuint fragment_shader;
     GLuint uniform_locations[NUM_GLES2_UNIFORMS];
     GLfloat projection[4][4];
-    const float *shader_params;
+    float *shader_params;
     struct GLES2_ProgramCacheEntry *prev;
     struct GLES2_ProgramCacheEntry *next;
 } GLES2_ProgramCacheEntry;
@@ -466,6 +466,7 @@ static GLES2_ProgramCacheEntry *GLES2_CacheProgram(GLES2_RenderData *data, GLuin
     data->glGetProgramiv(entry->id, GL_LINK_STATUS, &linkSuccessful);
     if (!linkSuccessful) {
         data->glDeleteProgram(entry->id);
+        SDL_free(entry->shader_params);
         SDL_free(entry);
         SDL_SetError("Failed to link shader program");
         return NULL;
@@ -505,12 +506,11 @@ static GLES2_ProgramCacheEntry *GLES2_CacheProgram(GLES2_RenderData *data, GLuin
 
     // Evict the last entry from the cache if we exceed the limit
     if (data->program_cache.count > GLES2_MAX_CACHED_PROGRAMS) {
-        data->glDeleteProgram(data->program_cache.tail->id);
-        data->program_cache.tail = data->program_cache.tail->prev;
-        if (data->program_cache.tail) {
-            SDL_free(data->program_cache.tail->next);
-            data->program_cache.tail->next = NULL;
-        }
+        GLES2_ProgramCacheEntry *oldest = data->program_cache.tail;
+        data->program_cache.tail = oldest->prev;
+        data->glDeleteProgram(oldest->id);
+        SDL_free(oldest->shader_params);
+        SDL_free(oldest);
         --data->program_cache.count;
     }
     return entry;
@@ -629,6 +629,7 @@ static bool GLES2_SelectProgram(GLES2_RenderData *data, SDL_Texture *texture, GL
     GLES2_ProgramCacheEntry *program;
     GLES2_TextureData *tdata = texture ? (GLES2_TextureData *)texture->internal : NULL;
     const float *shader_params = NULL;
+    int shader_params_len = 0;
 
     // Select an appropriate shader pair for the specified modes
     vtype = GLES2_SHADER_VERTEX_DEFAULT;
@@ -644,10 +645,12 @@ static bool GLES2_SelectProgram(GLES2_RenderData *data, SDL_Texture *texture, GL
         case SDL_SCALEMODE_LINEAR:
             ftype = GLES2_SHADER_FRAGMENT_TEXTURE_PALETTE_LINEAR;
             shader_params = tdata->texel_size;
+            shader_params_len = 4 * sizeof(float);
             break;
         case SDL_SCALEMODE_PIXELART:
             ftype = GLES2_SHADER_FRAGMENT_TEXTURE_PALETTE_PIXELART;
             shader_params = tdata->texel_size;
+            shader_params_len = 4 * sizeof(float);
             break;
         default:
             SDL_assert(!"Unknown scale mode");
@@ -658,6 +661,7 @@ static bool GLES2_SelectProgram(GLES2_RenderData *data, SDL_Texture *texture, GL
         if (scale_mode == SDL_SCALEMODE_PIXELART) {
             ftype = GLES2_SHADER_FRAGMENT_TEXTURE_ABGR_PIXELART;
             shader_params = tdata->texel_size;
+            shader_params_len = 4 * sizeof(float);
         } else {
             ftype = GLES2_SHADER_FRAGMENT_TEXTURE_ABGR;
         }
@@ -666,6 +670,7 @@ static bool GLES2_SelectProgram(GLES2_RenderData *data, SDL_Texture *texture, GL
         if (scale_mode == SDL_SCALEMODE_PIXELART) {
             ftype = GLES2_SHADER_FRAGMENT_TEXTURE_ARGB_PIXELART;
             shader_params = tdata->texel_size;
+            shader_params_len = 4 * sizeof(float);
         } else {
             ftype = GLES2_SHADER_FRAGMENT_TEXTURE_ARGB;
         }
@@ -674,6 +679,7 @@ static bool GLES2_SelectProgram(GLES2_RenderData *data, SDL_Texture *texture, GL
         if (scale_mode == SDL_SCALEMODE_PIXELART) {
             ftype = GLES2_SHADER_FRAGMENT_TEXTURE_RGB_PIXELART;
             shader_params = tdata->texel_size;
+            shader_params_len = 4 * sizeof(float);
         } else {
             ftype = GLES2_SHADER_FRAGMENT_TEXTURE_RGB;
         }
@@ -682,6 +688,7 @@ static bool GLES2_SelectProgram(GLES2_RenderData *data, SDL_Texture *texture, GL
         if (scale_mode == SDL_SCALEMODE_PIXELART) {
             ftype = GLES2_SHADER_FRAGMENT_TEXTURE_BGR_PIXELART;
             shader_params = tdata->texel_size;
+            shader_params_len = 4 * sizeof(float);
         } else {
             ftype = GLES2_SHADER_FRAGMENT_TEXTURE_BGR;
         }
@@ -694,6 +701,7 @@ static bool GLES2_SelectProgram(GLES2_RenderData *data, SDL_Texture *texture, GL
             SDL_SetError("Unsupported YUV colorspace");
             goto fault;
         }
+        shader_params_len = 16 * sizeof(float);
         break;
     case GLES2_IMAGESOURCE_TEXTURE_NV12:
         if (SDL_GetHintBoolean("SDL_RENDER_OPENGL_NV12_RG_SHADER", false)) {
@@ -706,6 +714,7 @@ static bool GLES2_SelectProgram(GLES2_RenderData *data, SDL_Texture *texture, GL
             SDL_SetError("Unsupported YUV colorspace");
             goto fault;
         }
+        shader_params_len = 16 * sizeof(float);
         break;
     case GLES2_IMAGESOURCE_TEXTURE_NV21:
         if (SDL_GetHintBoolean("SDL_RENDER_OPENGL_NV12_RG_SHADER", false)) {
@@ -718,6 +727,7 @@ static bool GLES2_SelectProgram(GLES2_RenderData *data, SDL_Texture *texture, GL
             SDL_SetError("Unsupported YUV colorspace");
             goto fault;
         }
+        shader_params_len = 16 * sizeof(float);
         break;
 #endif // SDL_HAVE_YUV
     case GLES2_IMAGESOURCE_TEXTURE_EXTERNAL_OES:
@@ -762,7 +772,11 @@ static bool GLES2_SelectProgram(GLES2_RenderData *data, SDL_Texture *texture, GL
     // Select that program in OpenGL
     data->glUseProgram(program->id);
 
-    if (shader_params && shader_params != program->shader_params) {
+    SDL_assert(!shader_params || shader_params_len > 0);
+
+    if (shader_params &&
+        (!program->shader_params ||
+         SDL_memcmp(shader_params,  program->shader_params, shader_params_len) != 0)) {
 #ifdef SDL_HAVE_YUV
         if (ftype >= GLES2_SHADER_FRAGMENT_TEXTURE_YUV) {
             // YUV shader params are Yoffset, 0, Rcoeff, 0, Gcoeff, 0, Bcoeff, 0
@@ -789,7 +803,13 @@ static bool GLES2_SelectProgram(GLES2_RenderData *data, SDL_Texture *texture, GL
         if (shader_params) {
             data->glUniform4f(program->uniform_locations[GLES2_UNIFORM_TEXEL_SIZE], shader_params[0], shader_params[1], shader_params[2], shader_params[3]);
         }
-        program->shader_params = shader_params;
+
+        if (!program->shader_params) {
+            program->shader_params = (float *)SDL_malloc(shader_params_len);
+        }
+        if (program->shader_params) {
+            SDL_memcpy(program->shader_params, shader_params, shader_params_len);
+        }
     }
 
     // Set the current program
@@ -1636,8 +1656,9 @@ static void GLES2_DestroyRenderer(SDL_Renderer *renderer)
             GLES2_ProgramCacheEntry *next;
             entry = data->program_cache.head;
             while (entry) {
-                data->glDeleteProgram(entry->id);
                 next = entry->next;
+                data->glDeleteProgram(entry->id);
+                SDL_free(entry->shader_params);
                 SDL_free(entry);
                 entry = next;
             }
@@ -2196,6 +2217,7 @@ static void GLES2_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture)
 
     if (data->drawstate.texture == texture) {
         data->drawstate.texture = NULL;
+        data->drawstate.shader_params = NULL;
     }
     if (data->drawstate.target == texture) {
         data->drawstate.target = NULL;