SDL: Handle partial OpenGL shader availability

From 7a49ce71a1e508eb59b296175731913192d0a24a Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 13 Oct 2025 14:34:24 -0700
Subject: [PATCH] Handle partial OpenGL shader availability

The pixelart shaders are not supported on OpenGL 2.1 (GLSL 130 isn't available)

Fixes the OpenGL renderer on macOS
---
 src/render/opengl/SDL_render_gl.c  | 49 +++++++++++++++++++++++++-----
 src/render/opengl/SDL_shaders_gl.c | 36 ++++++++++++++++------
 src/render/opengl/SDL_shaders_gl.h |  1 +
 3 files changed, 69 insertions(+), 17 deletions(-)

diff --git a/src/render/opengl/SDL_render_gl.c b/src/render/opengl/SDL_render_gl.c
index 38d46b8c894ec..55aaca6a39558 100644
--- a/src/render/opengl/SDL_render_gl.c
+++ b/src/render/opengl/SDL_render_gl.c
@@ -91,6 +91,7 @@ typedef struct
 
     bool debug_enabled;
     bool GL_ARB_debug_output_supported;
+    bool pixelart_supported;
     int errors;
     char **error_messages;
     GLDEBUGPROCARB next_error_callback;
@@ -455,7 +456,13 @@ static bool SetTextureScaleMode(GL_RenderData *data, GLenum textype, SDL_PixelFo
         data->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
         data->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
         break;
-    case SDL_SCALEMODE_PIXELART:    // Uses linear sampling
+    case SDL_SCALEMODE_PIXELART:    // Uses linear sampling if supported
+        if (!data->pixelart_supported) {
+            data->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+            data->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+            break;
+        }
+        SDL_FALLTHROUGH;
     case SDL_SCALEMODE_LINEAR:
         if (format == SDL_PIXELFORMAT_INDEX8) {
             // We'll do linear sampling in the shader
@@ -1210,19 +1217,22 @@ static bool SetCopyState(GL_RenderData *data, const SDL_RenderCommand *cmd)
         if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_LINEAR) {
             shader = SHADER_PALETTE_LINEAR;
             shader_params = texturedata->texel_size;
-        } else if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART) {
+        } else if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART &&
+                   data->pixelart_supported) {
             shader = SHADER_PALETTE_PIXELART;
             shader_params = texturedata->texel_size;
         }
         break;
     case SHADER_RGB:
-        if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART) {
+        if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART &&
+            data->pixelart_supported) {
             shader = SHADER_RGB_PIXELART;
             shader_params = texturedata->texel_size;
         }
         break;
     case SHADER_RGBA:
-        if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART) {
+        if (cmd->data.draw.texture_scale_mode == SDL_SCALEMODE_PIXELART &&
+            data->pixelart_supported) {
             shader = SHADER_RGBA_PIXELART;
             shader_params = texturedata->texel_size;
         }
@@ -1928,27 +1938,50 @@ static bool GL_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_Pr
     data->shaders = GL_CreateShaderContext();
     SDL_LogInfo(SDL_LOG_CATEGORY_RENDER, "OpenGL shaders: %s",
                 data->shaders ? "ENABLED" : "DISABLED");
-    if (data->shaders) {
+    if (GL_SupportsShader(data->shaders, SHADER_RGB)) {
         SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBX32);
         if (bgra_supported) {
             SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_BGRX32);
         }
+    } else {
+        SDL_LogInfo(SDL_LOG_CATEGORY_RENDER, "OpenGL RGB shaders not supported");
+    }
+    // We support PIXELART mode using a shader
+    if (GL_SupportsShader(data->shaders, SHADER_RGB_PIXELART) &&
+        GL_SupportsShader(data->shaders, SHADER_RGBA_PIXELART)) {
+        data->pixelart_supported = true;
+    } else {
+        SDL_LogInfo(SDL_LOG_CATEGORY_RENDER, "OpenGL PIXELART shaders not supported");
     }
     // We support INDEX8 textures using 2 textures and a shader
-    if (data->shaders && data->num_texture_units >= 2) {
+    if (GL_SupportsShader(data->shaders, SHADER_PALETTE_NEAREST) &&
+        GL_SupportsShader(data->shaders, SHADER_PALETTE_LINEAR) &&
+        (!data->pixelart_supported || GL_SupportsShader(data->shaders, SHADER_PALETTE_PIXELART)) &&
+        data->num_texture_units >= 2) {
         SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_INDEX8);
+    } else {
+        SDL_LogInfo(SDL_LOG_CATEGORY_RENDER, "OpenGL palette shaders not supported");
     }
 #ifdef SDL_HAVE_YUV
     // We support YV12 textures using 3 textures and a shader
-    if (data->shaders && data->num_texture_units >= 3) {
+    if (GL_SupportsShader(data->shaders, SHADER_YUV) &&
+        data->num_texture_units >= 3) {
         SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_YV12);
         SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_IYUV);
+    } else {
+        SDL_LogInfo(SDL_LOG_CATEGORY_RENDER, "OpenGL YUV not supported");
     }
 
     // We support NV12 textures using 2 textures and a shader
-    if (data->shaders && data->num_texture_units >= 2) {
+    if (GL_SupportsShader(data->shaders, SHADER_NV12_RA) &&
+        GL_SupportsShader(data->shaders, SHADER_NV12_RG) &&
+        GL_SupportsShader(data->shaders, SHADER_NV21_RA) &&
+        GL_SupportsShader(data->shaders, SHADER_NV21_RG) &&
+        data->num_texture_units >= 2) {
         SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_NV12);
         SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_NV21);
+    } else {
+        SDL_LogInfo(SDL_LOG_CATEGORY_RENDER, "OpenGL NV12/NV21 not supported");
     }
 #endif
 #ifdef SDL_PLATFORM_MACOS
diff --git a/src/render/opengl/SDL_shaders_gl.c b/src/render/opengl/SDL_shaders_gl.c
index 069123b24ed5f..164875127172e 100644
--- a/src/render/opengl/SDL_shaders_gl.c
+++ b/src/render/opengl/SDL_shaders_gl.c
@@ -520,10 +520,15 @@ static bool CompileShader(GL_ShaderContext *ctx, GLhandleARB shader, const char
         info = SDL_small_alloc(char, length + 1, &isstack);
         if (info) {
             ctx->glGetInfoLogARB(shader, length, NULL, info);
-            SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Failed to compile shader:");
-            SDL_LogError(SDL_LOG_CATEGORY_RENDER, "%s", defines);
-            SDL_LogError(SDL_LOG_CATEGORY_RENDER, "%s", source);
-            SDL_LogError(SDL_LOG_CATEGORY_RENDER, "%s", info);
+            SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Failed to compile shader:");
+            if (version) {
+                SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "%s", version);
+            }
+            if (defines) {
+                SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "%s", defines);
+            }
+            SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "%s", source);
+            SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "%s", info);
             SDL_small_free(info, isstack);
         }
         return false;
@@ -598,9 +603,18 @@ static bool CompileShaderProgram(GL_ShaderContext *ctx, int index, GL_ShaderData
 
 static void DestroyShaderProgram(GL_ShaderContext *ctx, GL_ShaderData *data)
 {
-    ctx->glDeleteObjectARB(data->vert_shader);
-    ctx->glDeleteObjectARB(data->frag_shader);
-    ctx->glDeleteObjectARB(data->program);
+    if (data->vert_shader) {
+        ctx->glDeleteObjectARB(data->vert_shader);
+        data->vert_shader = 0;
+    }
+    if (data->frag_shader) {
+        ctx->glDeleteObjectARB(data->frag_shader);
+        data->frag_shader = 0;
+    }
+    if (data->program) {
+        ctx->glDeleteObjectARB(data->program);
+        data->program = 0;
+    }
 }
 
 GL_ShaderContext *GL_CreateShaderContext(void)
@@ -669,8 +683,7 @@ GL_ShaderContext *GL_CreateShaderContext(void)
     // Compile all the shaders
     for (i = 0; i < NUM_SHADERS; ++i) {
         if (!CompileShaderProgram(ctx, i, &ctx->shaders[i])) {
-            GL_DestroyShaderContext(ctx);
-            return NULL;
+            DestroyShaderProgram(ctx, &ctx->shaders[i]);
         }
     }
 
@@ -678,6 +691,11 @@ GL_ShaderContext *GL_CreateShaderContext(void)
     return ctx;
 }
 
+bool GL_SupportsShader(GL_ShaderContext *ctx, GL_Shader shader)
+{
+    return ctx && ctx->shaders[shader].program;
+}
+
 void GL_SelectShader(GL_ShaderContext *ctx, GL_Shader shader, const float *shader_params)
 {
     GLint location;
diff --git a/src/render/opengl/SDL_shaders_gl.h b/src/render/opengl/SDL_shaders_gl.h
index 350018b9a946e..438418472cbb7 100644
--- a/src/render/opengl/SDL_shaders_gl.h
+++ b/src/render/opengl/SDL_shaders_gl.h
@@ -51,6 +51,7 @@ typedef enum
 typedef struct GL_ShaderContext GL_ShaderContext;
 
 extern GL_ShaderContext *GL_CreateShaderContext(void);
+extern bool GL_SupportsShader(GL_ShaderContext *ctx, GL_Shader shader);
 extern void GL_SelectShader(GL_ShaderContext *ctx, GL_Shader shader, const float *shader_params);
 extern void GL_DestroyShaderContext(GL_ShaderContext *ctx);