From d4236dcd852d001df4cf6175067649572a3b461d Mon Sep 17 00:00:00 2001
From: Sylvain Becker <[EMAIL REDACTED]>
Date: Tue, 26 May 2026 02:41:13 +0200
Subject: [PATCH] opengles: Readd an OpenGL ES 1 renderer to SDL3! (#15185)
This readds the "opengles" renderer, updated from SDL2 to work on SDL3.
Fixes #15661.
Co-authored-by: Ryan C. Gordon <icculus@icculus.org>
Co-authored-by: Cameron Cawley <ccawley2011@gmail.com>
---
CMakeLists.txt | 2 +
cmake/sdlchecks.cmake | 1 +
include/build_config/SDL_build_config.h.cmake | 1 +
src/render/SDL_render.c | 3 +
src/render/SDL_sysrender.h | 1 +
src/render/opengles/SDL_glesfuncs.h | 62 +
src/render/opengles/SDL_render_gles.c | 1194 +++++++++++++++++
7 files changed, 1264 insertions(+)
create mode 100644 src/render/opengles/SDL_glesfuncs.h
create mode 100644 src/render/opengles/SDL_render_gles.c
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 44d98af1ebbeb..e777e1537e7ad 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1615,6 +1615,7 @@ if(ANDROID)
set(SDL_VIDEO_OPENGL_EGL 1)
set(HAVE_OPENGLES TRUE)
set(SDL_VIDEO_OPENGL_ES 1)
+ set(SDL_VIDEO_RENDER_OGL_ES 1)
set(SDL_VIDEO_OPENGL_ES2 1)
set(SDL_VIDEO_RENDER_OGL_ES2 1)
@@ -2729,6 +2730,7 @@ elseif(APPLE)
if(IOS OR TVOS OR VISIONOS OR WATCHOS)
set(SDL_FRAMEWORK_OPENGLES 1)
set(SDL_VIDEO_OPENGL_ES 1)
+ set(SDL_VIDEO_RENDER_OGL_ES 1)
else()
set(SDL_VIDEO_OPENGL_EGL 1)
endif()
diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake
index 055781dde3804..8074c4b9338d5 100644
--- a/cmake/sdlchecks.cmake
+++ b/cmake/sdlchecks.cmake
@@ -921,6 +921,7 @@ macro(CheckOpenGLES)
if(HAVE_OPENGLES_V1)
set(HAVE_OPENGLES TRUE)
set(SDL_VIDEO_OPENGL_ES 1)
+ set(SDL_VIDEO_RENDER_OGL_ES 1)
endif()
if(HAVE_OPENGLES_V2)
set(HAVE_OPENGLES TRUE)
diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake
index a5547679cd25b..7b608323dcc3d 100644
--- a/include/build_config/SDL_build_config.h.cmake
+++ b/include/build_config/SDL_build_config.h.cmake
@@ -461,6 +461,7 @@
#cmakedefine SDL_VIDEO_RENDER_METAL 1
#cmakedefine SDL_VIDEO_RENDER_VULKAN 1
#cmakedefine SDL_VIDEO_RENDER_OGL 1
+#cmakedefine SDL_VIDEO_RENDER_OGL_ES 1
#cmakedefine SDL_VIDEO_RENDER_OGL_ES2 1
#cmakedefine SDL_VIDEO_RENDER_NGAGE 1
#cmakedefine SDL_VIDEO_RENDER_PS2 1
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index 832bd9a76b630..da1cf72040392 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -129,6 +129,9 @@ static const SDL_RenderDriver *render_drivers[] = {
#ifdef SDL_VIDEO_RENDER_OGL_ES2
&GLES2_RenderDriver,
#endif
+#ifdef SDL_VIDEO_RENDER_OGL_ES
+ &GLES_RenderDriver,
+#endif
#ifdef SDL_VIDEO_RENDER_PS2
&PS2_RenderDriver,
#endif
diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h
index c1c13074ca42a..49566067bac6f 100644
--- a/src/render/SDL_sysrender.h
+++ b/src/render/SDL_sysrender.h
@@ -380,6 +380,7 @@ extern SDL_RenderDriver D3D_RenderDriver;
extern SDL_RenderDriver D3D11_RenderDriver;
extern SDL_RenderDriver D3D12_RenderDriver;
extern SDL_RenderDriver GL_RenderDriver;
+extern SDL_RenderDriver GLES_RenderDriver;
extern SDL_RenderDriver GLES2_RenderDriver;
extern SDL_RenderDriver METAL_RenderDriver;
extern SDL_RenderDriver NGAGE_RenderDriver;
diff --git a/src/render/opengles/SDL_glesfuncs.h b/src/render/opengles/SDL_glesfuncs.h
new file mode 100644
index 0000000000000..fec9d060ffda2
--- /dev/null
+++ b/src/render/opengles/SDL_glesfuncs.h
@@ -0,0 +1,62 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+
+SDL_PROC(void, glBindTexture, (GLenum, GLuint))
+SDL_PROC(void, glBlendFunc, (GLenum, GLenum))
+SDL_PROC_OES(void, glBlendEquationOES, (GLenum))
+SDL_PROC_OES(void, glBlendEquationSeparateOES, (GLenum, GLenum))
+SDL_PROC_OES(void, glBlendFuncSeparateOES, (GLenum, GLenum, GLenum, GLenum))
+SDL_PROC(void, glClear, (GLbitfield))
+SDL_PROC(void, glClearColor, (GLclampf, GLclampf, GLclampf, GLclampf))
+SDL_PROC(void, glColor4f, (GLfloat, GLfloat, GLfloat, GLfloat))
+SDL_PROC(void, glColorPointer, (GLint, GLenum, GLsizei, const GLvoid *))
+SDL_PROC(void, glDeleteTextures, (GLsizei, const GLuint *))
+SDL_PROC(void, glDisable, (GLenum))
+SDL_PROC(void, glDisableClientState, (GLenum array))
+SDL_PROC(void, glDrawArrays, (GLenum, GLint, GLsizei))
+SDL_PROC_OES(void, glDrawTexfOES, (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat))
+SDL_PROC(void, glEnable, (GLenum))
+SDL_PROC(void, glEnableClientState, (GLenum))
+SDL_PROC(void, glFinish, (void))
+SDL_PROC_OES(void, glGenFramebuffersOES, (GLsizei, GLuint *))
+SDL_PROC(void, glGenTextures, (GLsizei, GLuint *))
+SDL_PROC(GLenum, glGetError, (void))
+SDL_PROC(void, glGetIntegerv, (GLenum, GLint *))
+SDL_PROC(void, glLoadIdentity, (void))
+SDL_PROC(void, glMatrixMode, (GLenum))
+SDL_PROC(void, glOrthof, (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat))
+SDL_PROC(void, glPixelStorei, (GLenum, GLint))
+SDL_PROC(void, glReadPixels, (GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, GLvoid *))
+SDL_PROC(void, glScissor, (GLint, GLint, GLsizei, GLsizei))
+SDL_PROC(void, glTexCoordPointer, (GLint, GLenum, GLsizei, const GLvoid *))
+SDL_PROC(void, glTexEnvf, (GLenum, GLenum, GLfloat))
+SDL_PROC(void, glTexImage2D, (GLenum, GLint, GLint, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *))
+SDL_PROC(void, glTexParameteri, (GLenum, GLenum, GLint))
+SDL_PROC(void, glTexParameteriv, (GLenum, GLenum, const GLint *))
+SDL_PROC(void, glTexSubImage2D, (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *))
+SDL_PROC(void, glVertexPointer, (GLint, GLenum, GLsizei, const GLvoid *))
+SDL_PROC(void, glViewport, (GLint, GLint, GLsizei, GLsizei))
+SDL_PROC_OES(void, glBindFramebufferOES, (GLenum, GLuint))
+SDL_PROC_OES(void, glFramebufferTexture2DOES, (GLenum, GLenum, GLenum, GLuint, GLint))
+SDL_PROC_OES(GLenum, glCheckFramebufferStatusOES, (GLenum))
+SDL_PROC_OES(void, glDeleteFramebuffersOES, (GLsizei, const GLuint *))
+
+/* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/render/opengles/SDL_render_gles.c b/src/render/opengles/SDL_render_gles.c
new file mode 100644
index 0000000000000..800991face1b5
--- /dev/null
+++ b/src/render/opengles/SDL_render_gles.c
@@ -0,0 +1,1194 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#ifdef SDL_VIDEO_RENDER_OGL_ES
+#include <SDL3/SDL_hints.h>
+#include "../../video/SDL_sysvideo.h" /* For SDL_GL_SwapWindowWithResult */
+#include <SDL3/SDL_opengles.h>
+#include "../SDL_sysrender.h"
+#include "../../SDL_utils_c.h"
+
+#define RENDERER_CONTEXT_MAJOR 1
+#define RENDERER_CONTEXT_MINOR 1
+
+/* OpenGL ES 1.1 renderer implementation, based on the OpenGL renderer */
+
+typedef struct GLES_FBOList GLES_FBOList;
+
+struct GLES_FBOList
+{
+ Uint32 w, h;
+ GLuint FBO;
+ GLES_FBOList *next;
+};
+
+typedef struct
+{
+ SDL_Rect viewport;
+ bool viewport_dirty;
+ SDL_Texture *texture;
+ SDL_Texture *target;
+ int drawablew;
+ int drawableh;
+ SDL_BlendMode blend;
+ bool cliprect_enabled_dirty;
+ bool cliprect_enabled;
+ bool cliprect_dirty;
+ SDL_Rect cliprect;
+ bool texturing;
+ bool color_dirty;
+ SDL_FColor color;
+ bool clear_color_dirty;
+ SDL_FColor clear_color;
+} GLES_DrawStateCache;
+
+typedef struct
+{
+ SDL_GLContext context;
+
+#define SDL_PROC(ret, func, params) ret (APIENTRY *func) params;
+#define SDL_PROC_OES SDL_PROC
+#include "SDL_glesfuncs.h"
+#undef SDL_PROC
+#undef SDL_PROC_OES
+ bool GL_OES_framebuffer_object_supported;
+ GLES_FBOList *framebuffers;
+ GLuint window_framebuffer;
+
+ bool GL_OES_blend_func_separate_supported;
+ bool GL_OES_blend_equation_separate_supported;
+ bool GL_OES_blend_subtract_supported;
+ bool GL_EXT_blend_minmax_supported;
+
+ GLES_DrawStateCache drawstate;
+
+ GLenum textype; // Probably GL_TEXTURE_2D, but might be other things, like TEXTURE_RECTANGLE, maybe if there's any extension...?
+
+ bool pixelart_supported;
+
+ bool debug_enabled;
+ int errors;
+ char **error_messages;
+} GLES_RenderData;
+
+typedef struct
+{
+ GLuint texture;
+} GL_PaletteData;
+
+typedef struct
+{
+ GLuint texture;
+ GLenum textype;
+ GLfloat texw;
+ GLfloat texh;
+ GLenum format;
+ GLenum formattype;
+ SDL_ScaleMode texture_scale_mode;
+ GLenum texture_address_mode_u;
+ GLenum texture_address_mode_v;
+ void *pixels;
+ int pitch;
+ GLES_FBOList *fbo;
+} GLES_TextureData;
+
+static bool GLES_SetError(const char *prefix, GLenum result)
+{
+ const char *error;
+
+ switch (result) {
+ case GL_NO_ERROR:
+ error = "GL_NO_ERROR";
+ break;
+ case GL_INVALID_ENUM:
+ error = "GL_INVALID_ENUM";
+ break;
+ case GL_INVALID_VALUE:
+ error = "GL_INVALID_VALUE";
+ break;
+ case GL_INVALID_OPERATION:
+ error = "GL_INVALID_OPERATION";
+ break;
+ case GL_STACK_OVERFLOW:
+ error = "GL_STACK_OVERFLOW";
+ break;
+ case GL_STACK_UNDERFLOW:
+ error = "GL_STACK_UNDERFLOW";
+ break;
+ case GL_OUT_OF_MEMORY:
+ error = "GL_OUT_OF_MEMORY";
+ break;
+ default:
+ error = "UNKNOWN";
+ break;
+ }
+ return SDL_SetError("%s: %s", prefix, error);
+}
+
+static bool GLES_LoadFunctions(GLES_RenderData *data)
+{
+#ifdef SDL_VIDEO_DRIVER_UIKIT
+#define __SDL_NOGETPROCADDR__
+#elif defined(SDL_VIDEO_DRIVER_ANDROID)
+#define __SDL_NOGETPROCADDR__
+#endif
+
+#ifdef __SDL_NOGETPROCADDR__
+#define SDL_PROC(ret, func, params) data->func = func;
+#define SDL_PROC_OES(ret, func, params) data->func = func;
+#else
+#define SDL_PROC(ret, func, params) \
+ do { \
+ data->func = (ret (APIENTRY *) params)SDL_GL_GetProcAddress(#func); \
+ if (!data->func) { \
+ return SDL_SetError("Couldn't load GLES function %s: %s", #func, SDL_GetError()); \
+ } \
+ } while (0);
+#define SDL_PROC_OES(ret, func, params) \
+ do { \
+ data->func = (ret (APIENTRY *) params)SDL_GL_GetProcAddress(#func); \
+ } while (0);
+#endif /* __SDL_NOGETPROCADDR__ */
+
+#include "SDL_glesfuncs.h"
+#undef SDL_PROC
+#undef SDL_PROC_OES
+ return true;
+}
+
+static GLES_FBOList *GLES_GetFBO(GLES_RenderData *data, Uint32 w, Uint32 h)
+{
+ GLES_FBOList *result = data->framebuffers;
+ while ((result) && ((result->w != w) || (result->h != h))) {
+ result = result->next;
+ }
+ if (!result) {
+ result = SDL_malloc(sizeof(GLES_FBOList));
+ result->w = w;
+ result->h = h;
+ data->glGenFramebuffersOES(1, &result->FBO);
+ result->next = data->framebuffers;
+ data->framebuffers = result;
+ }
+ return result;
+}
+
+static bool GLES_ActivateRenderer(SDL_Renderer *renderer)
+{
+ GLES_RenderData *data = (GLES_RenderData *)renderer->internal;
+
+ if (SDL_GL_GetCurrentContext() != data->context) {
+ if (!SDL_GL_MakeCurrent(renderer->window, data->context)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void GLES_WindowEvent(SDL_Renderer *renderer, const SDL_WindowEvent *event)
+{
+ GLES_RenderData *data = (GLES_RenderData *)renderer->internal;
+
+ if (event->type == SDL_EVENT_WINDOW_MINIMIZED) {
+ /* According to Apple documentation, we need to finish drawing NOW! */
+ data->glFinish();
+ }
+}
+
+static bool GLES_GetOutputSize(SDL_Renderer *renderer, int *w, int *h)
+{
+ SDL_GetWindowSizeInPixels(renderer->window, w, h);
+ return true;
+}
+
+static GLenum GetBlendFunc(SDL_BlendFactor factor)
+{
+ switch (factor) {
+ case SDL_BLENDFACTOR_ZERO:
+ return GL_ZERO;
+ case SDL_BLENDFACTOR_ONE:
+ return GL_ONE;
+ case SDL_BLENDFACTOR_SRC_COLOR:
+ return GL_SRC_COLOR;
+ case SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR:
+ return GL_ONE_MINUS_SRC_COLOR;
+ case SDL_BLENDFACTOR_SRC_ALPHA:
+ return GL_SRC_ALPHA;
+ case SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA:
+ return GL_ONE_MINUS_SRC_ALPHA;
+ case SDL_BLENDFACTOR_DST_COLOR:
+ return GL_DST_COLOR;
+ case SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR:
+ return GL_ONE_MINUS_DST_COLOR;
+ case SDL_BLENDFACTOR_DST_ALPHA:
+ return GL_DST_ALPHA;
+ case SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA:
+ return GL_ONE_MINUS_DST_ALPHA;
+ default:
+ return GL_INVALID_ENUM;
+ }
+}
+
+static GLenum GetBlendEquation(SDL_BlendOperation operation)
+{
+ switch (operation) {
+ case SDL_BLENDOPERATION_ADD:
+ return GL_FUNC_ADD_OES;
+ case SDL_BLENDOPERATION_SUBTRACT:
+ return GL_FUNC_SUBTRACT_OES;
+ case SDL_BLENDOPERATION_REV_SUBTRACT:
+ return GL_FUNC_REVERSE_SUBTRACT_OES;
+ case SDL_BLENDOPERATION_MINIMUM:
+ return GL_MIN_EXT;
+ case SDL_BLENDOPERATION_MAXIMUM:
+ return GL_MAX_EXT;
+ default:
+ return GL_INVALID_ENUM;
+ }
+}
+
+static bool GLES_SupportsBlendMode(SDL_Renderer *renderer, SDL_BlendMode blendMode)
+{
+ GLES_RenderData *data = (GLES_RenderData *)renderer->internal;
+ SDL_BlendFactor srcColorFactor = SDL_GetBlendModeSrcColorFactor(blendMode);
+ SDL_BlendFactor srcAlphaFactor = SDL_GetBlendModeSrcAlphaFactor(blendMode);
+ SDL_BlendOperation colorOperation = SDL_GetBlendModeColorOperation(blendMode);
+ SDL_BlendFactor dstColorFactor = SDL_GetBlendModeDstColorFactor(blendMode);
+ SDL_BlendFactor dstAlphaFactor = SDL_GetBlendModeDstAlphaFactor(blendMode);
+ SDL_BlendOperation alphaOperation = SDL_GetBlendModeAlphaOperation(blendMode);
+
+ if (GetBlendFunc(srcColorFactor) == GL_INVALID_ENUM ||
+ GetBlendFunc(srcAlphaFactor) == GL_INVALID_ENUM ||
+ GetBlendEquation(colorOperation) == GL_INVALID_ENUM ||
+ GetBlendFunc(dstColorFactor) == GL_INVALID_ENUM ||
+ GetBlendFunc(dstAlphaFactor) == GL_INVALID_ENUM ||
+ GetBlendEquation(alphaOperation) == GL_INVALID_ENUM) {
+ return false;
+ }
+ if ((srcColorFactor != srcAlphaFactor || dstColorFactor != dstAlphaFactor) && !data->GL_OES_blend_func_separate_supported) {
+ return false;
+ }
+ if (colorOperation != alphaOperation && !data->GL_OES_blend_equation_separate_supported) {
+ return false;
+ }
+ if (colorOperation != SDL_BLENDOPERATION_ADD && !data->GL_OES_blend_subtract_supported) {
+ return false;
+ }
+ if (colorOperation == SDL_BLENDOPERATION_MINIMUM && !data->GL_EXT_blend_minmax_supported) {
+ return false;
+ }
+ if (colorOperation == SDL_BLENDOPERATION_MAXIMUM && !data->GL_EXT_blend_minmax_supported) {
+ return false;
+ }
+ return true;
+}
+
+
+static bool SetTextureScaleMode(GLES_RenderData *data, GLenum textype, SDL_PixelFormat format, SDL_ScaleMode scaleMode)
+{
+ switch (scaleMode) {
+ case SDL_SCALEMODE_NEAREST:
+ case SDL_SCALEMODE_PIXELART:
+ data->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ data->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ break;
+ case SDL_SCALEMODE_LINEAR:
+ data->glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ data->glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ break;
+ default:
+ return SDL_SetError("Unknown texture scale mode: %d", scaleMode);
+ }
+ return true;
+}
+
+static GLint TranslateAddressMode(SDL_TextureAddressMode addressMode)
+{
+ switch (addressMode) {
+ case SDL_TEXTURE_ADDRESS_CLAMP:
+ return GL_CLAMP_TO_EDGE;
+ case SDL_TEXTURE_ADDRESS_WRAP:
+ return GL_REPEAT;
+ default:
+ SDL_assert(!"Unknown texture address mode");
+ return GL_CLAMP_TO_EDGE;
+ }
+}
+
+static void SetTextureAddressMode(GLES_RenderData *data, GLenum textype, SDL_TextureAddressMode addressModeU, SDL_TextureAddressMode addressModeV)
+{
+ data->glTexParameteri(textype, GL_TEXTURE_WRAP_S, TranslateAddressMode(addressModeU));
+ data->glTexParameteri(textype, GL_TEXTURE_WRAP_T, TranslateAddressMode(addressModeV));
+}
+
+static bool GLES_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props)
+{
+ GLES_RenderData *renderdata = (GLES_RenderData *)renderer->internal;
+ GLES_TextureData *data;
+ GLint internalFormat;
+ GLenum format, textype;
+ int texture_w, texture_h;
+ GLenum result;
+
+ GLES_ActivateRenderer(renderer);
+
+ switch (texture->format) {
+ case SDL_PIXELFORMAT_RGBA32:
+ internalFormat = GL_RGBA;
+ format = GL_RGBA;
+ textype = GL_UNSIGNED_BYTE;
+ break;
+ default:
+ return SDL_SetError("Texture format not supported");
+ }
+
+ data = (GLES_TextureData *)SDL_calloc(1, sizeof(*data));
+ if (!data) {
+ return SDL_OutOfMemory();
+ }
+
+ if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
+ data->pitch = texture->w * SDL_BYTESPERPIXEL(texture->format);
+ data->pixels = SDL_calloc(1, texture->h * data->pitch);
+ if (!data->pixels) {
+ SDL_free(data);
+ return SDL_OutOfMemory();
+ }
+ }
+
+ if (texture->access == SDL_TEXTUREACCESS_TARGET) {
+ if (!renderdata->GL_OES_framebuffer_object_supported) {
+ SDL_free(data);
+ return SDL_SetError("GL_OES_framebuffer_object not supported");
+ }
+ data->fbo = GLES_GetFBO(renderer->internal, texture->w, texture->h);
+ } else {
+ data->fbo = NULL;
+ }
+
+ renderdata->glGetError();
+ renderdata->glEnable(GL_TEXTURE_2D);
+ renderdata->glGenTextures(1, &data->texture);
+ result = renderdata->glGetError();
+ if (result != GL_NO_ERROR) {
+ if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
+ SDL_free(data->pixels);
+ }
+ SDL_free(data);
+ return GLES_SetError("glGenTextures()", result);
+ }
+
+ data->textype = GL_TEXTURE_2D;
+ /* no NPOV textures allowed in OpenGL ES (yet) */
+ texture_w = SDL_powerof2(texture->w);
+ texture_h = SDL_powerof2(texture->h);
+ data->texw = (GLfloat)texture->w / texture_w;
+ data->texh = (GLfloat)texture->h / texture_h;
+
+ data->format = format;
+ data->formattype = textype;
+ data->texture_scale_mode = texture->scaleMode;
+ data->texture_address_mode_u = SDL_TEXTURE_ADDRESS_CLAMP;
+ data->texture_address_mode_v = SDL_TEXTURE_ADDRESS_CLAMP;
+
+ renderdata->glBindTexture(data->textype, data->texture);
+ renderdata->glTexImage2D(data->textype, 0, internalFormat, texture_w,
+ texture_h, 0, format, textype, NULL);
+
+ SetTextureScaleMode(renderdata, data->textype, texture->format, texture->scaleMode);
+
+ renderdata->glDisable(GL_TEXTURE_2D);
+ renderdata->drawstate.texture = texture;
+ renderdata->drawstate.texturing = false;
+
+ result = renderdata->glGetError();
+ if (result != GL_NO_ERROR) {
+ if (texture->access == SDL_TEXTUREACCESS_STREAMING) {
+ SDL_free(data->pixels);
+ }
+ SDL_free(data);
+ return GLES_SetError("glTexImage2D()", result);
+ }
+
+ texture->internal = data;
+ return true;
+}
+
+static bool GLES_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
+ const SDL_Rect *rect, const void *pixels, int pitch)
+{
+ GLES_RenderData *renderdata = (GLES_RenderData *)renderer->internal;
+ GLES_TextureData *data = (GLES_TextureData *)texture->internal;
+ Uint8 *blob = NULL;
+ Uint8 *src;
+ int srcPitch;
+ int y;
+
+ GLES_ActivateRenderer(renderer);
+
+ /* Bail out if we're supposed to update an empty rectangle */
+ if (rect->w <= 0 || rect->h <= 0) {
+ return true;
+ }
+
+ /* Reformat the texture data into a tightly packed array */
+ srcPitch = rect->w * SDL_BYTESPERPIXEL(texture->format);
+ src = (Uint8 *)pixels;
+ if (pitch != srcPitch) {
+ blob = (Uint8 *)SDL_malloc(srcPitch * rect->h);
+ if (!blob) {
+ return SDL_OutOfMemory();
+ }
+ src = blob;
+ for (y = 0; y < rect->h; ++y) {
+ SDL_memcpy(src, pixels, srcPitch);
+ src += srcPitch;
+ pixels = (Uint8 *)pixels + pitch;
+ }
+ src = blob;
+ }
+
+ /* Create a texture subimage with the supplied data */
+ renderdata->glGetError();
+ renderdata->glEnable(data->textype);
+ renderdata->glBindTexture(data->textype, data->texture);
+ renderdata->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ renderdata->glTexSubImage2D(data->textype,
+ 0,
+ rect->x,
+ rect->y,
+ rect->w,
+ rect->h,
+ data->format,
+ data->formattype,
+ src);
+ renderdata->glDisable(data->textype);
+ SDL_free(blob);
+
+ renderdata->drawstate.texture = texture;
+ renderdata->drawstate.texturing = false;
+
+ if (renderdata->glGetError() != GL_NO_ERROR) {
+ return SDL_SetError("Failed to update texture");
+ }
+ return true;
+}
+
+static bool GLES_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture,
+ const SDL_Rect *rect, void **pixels, int *pitch)
+{
+ GLES_TextureData *data = (GLES_TextureData *)texture->internal;
+
+ *pixels =
+ (void *)((Uint8 *)data->pixels + rect->y * data->pitch +
+ rect->x * SDL_BYTESPERPIXEL(texture->format));
+ *pitch = data->pitch;
+ return true;
+}
+
+static void GLES_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
+{
+ GLES_TextureData *data = (GLES_TextureData *)texture->internal;
+ SDL_Rect rect;
+
+ /* We do whole texture updates, at least for now */
+ rect.x = 0;
+ rect.y = 0;
+ rect.w = texture->w;
+ rect.h = texture->h;
+ GLES_UpdateTexture(renderer, texture, &rect, data->pixels, data->pitch);
+}
+
+static bool GLES_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
+{
+ GLES_RenderData *data = (GLES_RenderData *)renderer->internal;
+ GLES_TextureData *texturedata = NULL;
+ GLenum status;
+
+ if (!data->GL_OES_framebuffer_object_supported) {
+ return SDL_SetError("Can't enable render target support in this renderer");
+ }
+
+ data->drawstate.viewport_dirty = true;
+
+ if (!texture) {
+ data->glBindFramebufferOES(GL_FRAMEBUFFER_OES, data->window_framebuffer);
+ return true;
+ }
+
+ texturedata = (GLES_TextureData *)texture->internal;
+ data->glBindFramebufferOES(GL_FRAMEBUFFER_OES, texturedata->fbo->FBO);
+ /* TODO: check if texture pixel format allows this operation */
+ data->glFramebufferTexture2DOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, texturedata->textype, texturedata->texture, 0);
+ /* Check FBO status */
+ status = data->glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);
+ if (status != GL_FRAMEBUFFER_COMPLETE_OES) {
+ return SDL_SetError("glFramebufferTexture2DOES() failed");
+ }
+ return true;
+}
+
+static bool GLES_QueueSetViewport(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
+{
+ return true; /* nothing to do in this backend. */
+}
+
+static bool GLES_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count)
+{
+ GLfloat *verts = (GLfloat *)SDL_AllocateRenderVertices(renderer, count * 2 * sizeof(GLfloat), 0, &cmd->data.draw.first);
+ int i;
+
+ if (!verts) {
+ return false;
+ }
+
+ cmd->data.draw.count = count;
+ for (i = 0; i < count; i++) {
+ *(verts++) = 0.5f + points[i].x;
+ *(verts++) = 0.5f + points[i].y;
+ }
+
+ return true;
+}
+
+static bool GLES_QueueDrawLines(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count)
+{
+ int i;
+ GLfloat prevx, prevy;
+ const size_t vertlen = (sizeof(GLfloat) * 2) * count;
+ GLfloat *verts = (GLfloat *)SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
+
+ if (!verts) {
+ return false;
+ }
+ cmd->data.draw.count = count;
+
+ /* 0.5f offset to hit the center of the pixel. */
+ prevx = 0.5f + points->x;
+ prevy = 0.5f + points->y;
+ *(verts++) = prevx;
+ *(verts++) = prevy;
+
+ /* bump the end of each line segment out a quarter of a pixel, to provoke
+ the diamond-exit rule. Without this, you won't just drop the last
+ pixel of the last line segment, but you might also drop pixels at the
+ edge of any given line segment along the way too. */
+ for (i = 1; i < count; i++) {
+ const GLfloat xstart = prevx;
+ const GLfloat ystart = prevy;
+ const GLfloat xend = points[i].x + 0.5f; /* 0.5f to hit pixel center. */
+ const GLfloat yend = points[i].y + 0.5f;
+ /* bump a little in the direction we are moving in. */
+ const GLfloat deltax = xend - xstart;
+ const GLfloat deltay = yend - ystart;
+ const GLfloat angle = SDL_atan2f(deltay, deltax);
+ prevx = xend + (SDL_cosf(angle) * 0.25f);
+ prevy = yend + (SDL_sinf(angle) * 0.25f);
+ *(verts++) = prevx;
+ *(verts++) = prevy;
+ }
+
+ return true;
+}
+
+static bool GLES_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture,
+ const float *xy, int xy_stride, const SDL_FColor *color, int color_stride, const float *uv, int uv_stride,
+ int num_vertices, const void *indices, int num_indices, int size_indices,
+ float scale_x, float scale_y)
+{
+ GLES_TextureData *texturedata = NULL;
+ int i;
+ int count = indices ? num_indices : num_vertices;
+ const float color_scale = cmd->data.draw.color_scale;
+ GLfloat *verts;
+ int sz = 2 + 4 + (texture ? 2 : 0);
+
+ verts = (GLfloat *)SDL_AllocateRenderVertices(renderer, count * sz * sizeof(GLfloat), 0, &cmd->data.draw.first);
+ if (!verts) {
+ return false;
+ }
+
+ if (texture) {
+ texturedata = (GLES_TextureData *)texture->internal;
+ }
+
+ cmd->data.draw.count = count;
+ size_indices = indices ? size_indices : 0;
+
+ for (i = 0; i < count; i++) {
+ int j;
+ float *xy_;
+ SDL_FColor *col_;
+ if (size_indices == 4) {
+ j = ((const Uint32 *)indices)[i];
+ } else if (size_indices == 2) {
+ j = ((const Uint16 *)indices)[i];
+ } else if (size_indices == 1) {
+ j = ((const Uint8 *)indices)[i];
+ } else {
+ j = i;
+ }
+
+ xy_ = (float *)((char *)xy + j * xy_stride);
+ col_ = (SDL_FColor *)((char *)color + j * color_stride);
+
+ *(verts++) = xy_[0] * scale_x;
+ *(verts++) = xy_[1] * scale_y;
+
+ *(verts++) = col_->r * color_scale;
+ *(verts++) = col_->g * color_scale;
+ *(verts++) = col_->b * color_scale;
+ *(verts++) = col_->a;
+
+ if (texture) {
+ float *uv_ = (float *)((char *)uv + j * uv_stride);
+ *(verts++) = uv_[0] * texturedata->texw;
+ *(verts++) = uv_[1] * texturedata->texh;
+ }
+ }
+ return true;
+}
+
+static void SetDrawState(GLES_RenderData *data, const SDL_RenderCommand *cmd)
+{
+ const SDL_BlendMode blend = cmd->data.draw.blend;
+
+ if (data->drawstate.viewport_dirty) {
+ const SDL_Rect *viewport = &data->drawstate.viewport;
+ const bool istarget = (data->drawstate.target != NULL);
+ data->glMatrixMode(GL_PROJECTION);
+ data->glLoadIdentity();
+ data->glViewport(viewport->x,
+ istarget ? viewport->y : (data->drawstate.drawableh - viewport->y - viewport->h),
+ viewport->w, viewport->h);
+ if (viewport->w && viewport->h) {
+ data->glOrthof((GLfloat)0, (GLfloat)viewport->w,
+ (GLfloat)(istarget ? 0 : viewport->h),
+ (GLfloat)(istarget ? viewport->h : 0),
+ 0.0, 1.0);
+ }
+ data->glMatrixMode(GL_MODELVIEW);
+ data->drawstate.viewport_dirty = false;
+ }
+
+ if (data->drawstate.cliprect_enabled_dirty) {
+ if (data->drawstate.cliprect_enabled) {
+ data->glEnable(GL_SCISSOR_TEST);
+ } else {
+ data->glDisable(GL_SCISSOR_TEST);
+ }
+ data->drawstate.cliprect_enabled_dirty = false;
+ }
+
+ if (data->drawstate.cliprect_enabled && data->drawstate.cliprect_dirty) {
+ const SDL_Rect *viewport = &data->drawstate.viewport;
+ const SDL_Rect *rect = &data->drawstate.cliprect;
+ const bool istarget = (data->drawstate.target != NULL);
+ data->glScissor(viewport->x + rect->x,
+ istarget ? viewport->y + rect->y : data->drawstate.drawableh - viewport->y - rect->y - rect->h,
+ rect->w, rect->h);
+ data->drawstate.cliprect_dirty = false;
+ }
+
+ if (blend != data->drawstate.blend) {
+ if (blend == SDL_BLENDMODE_NONE) {
+ data->
(Patch may be truncated, please check the link at the top of this post.)