SDL: SDL_Render OpenGL: do batching like in the GLES2 backend (with no VBO)

From b0dd4c7d368e9106bcdee237bd83215a6e190a70 Mon Sep 17 00:00:00 2001
From: Sylvain <[EMAIL REDACTED]>
Date: Mon, 10 Jan 2022 09:22:07 +0100
Subject: [PATCH] SDL_Render OpenGL: do batching like in the GLES2 backend
 (with no VBO)

---
 src/render/opengl/SDL_render_gl.c | 150 ++++++++++++++++++++----------
 1 file changed, 101 insertions(+), 49 deletions(-)

diff --git a/src/render/opengl/SDL_render_gl.c b/src/render/opengl/SDL_render_gl.c
index dd34b2744c7..9464f8ef034 100644
--- a/src/render/opengl/SDL_render_gl.c
+++ b/src/render/opengl/SDL_render_gl.c
@@ -1038,7 +1038,7 @@ GL_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *te
     return 0;
 }
 
-static void
+static int
 SetDrawState(GL_RenderData *data, const SDL_RenderCommand *cmd, const GL_Shader shader)
 {
     const SDL_BlendMode blend = cmd->data.draw.blend;
@@ -1145,9 +1145,11 @@ SetDrawState(GL_RenderData *data, const SDL_RenderCommand *cmd, const GL_Shader
         }
         data->drawstate.texture_array = texture_array;
     }
+
+    return 0;
 }
 
-static void
+static int
 SetCopyState(GL_RenderData *data, const SDL_RenderCommand *cmd)
 {
     SDL_Texture *texture = cmd->data.draw.texture;
@@ -1183,6 +1185,8 @@ SetCopyState(GL_RenderData *data, const SDL_RenderCommand *cmd)
 
         data->drawstate.texture = texture;
     }
+
+    return 0;
 }
 
 static int
@@ -1244,6 +1248,7 @@ GL_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *vertic
                     data->drawstate.cliprect_enabled = cmd->data.cliprect.enabled;
                     data->drawstate.cliprect_enabled_dirty = SDL_TRUE;
                 }
+
                 if (SDL_memcmp(&data->drawstate.cliprect, rect, sizeof (SDL_Rect)) != 0) {
                     SDL_memcpy(&data->drawstate.cliprect, rect, sizeof (SDL_Rect));
                     data->drawstate.cliprect_dirty = SDL_TRUE;
@@ -1272,30 +1277,6 @@ GL_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *vertic
                 }
 
                 data->glClear(GL_COLOR_BUFFER_BIT);
-
-                break;
-            }
-
-            case SDL_RENDERCMD_DRAW_POINTS: {
-                const size_t count = cmd->data.draw.count;
-                const GLfloat *verts = (GLfloat *) (((Uint8 *) vertices) + cmd->data.draw.first);
-                SetDrawState(data, cmd, SHADER_SOLID);
-
-                /* SetDrawState handles glEnableClientState. */
-                data->glVertexPointer(2, GL_FLOAT, sizeof(float) * 2, verts);
-                data->glDrawArrays(GL_POINTS, 0, (GLsizei) count);
-                break;
-            }
-
-            case SDL_RENDERCMD_DRAW_LINES: {
-                const GLfloat *verts = (GLfloat *) (((Uint8 *) vertices) + cmd->data.draw.first);
-                const size_t count = cmd->data.draw.count;
-                SDL_assert(count >= 2);
-                SetDrawState(data, cmd, SHADER_SOLID);
-
-                /* SetDrawState handles glEnableClientState. */
-                data->glVertexPointer(2, GL_FLOAT, sizeof(float) * 2, verts);
-                data->glDrawArrays(GL_LINE_STRIP, 0, (GLsizei) count);
                 break;
             }
 
@@ -1308,42 +1289,113 @@ GL_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *vertic
             case SDL_RENDERCMD_COPY_EX: /* unused */
                 break;
 
+            case SDL_RENDERCMD_DRAW_LINES: {
+                if (SetDrawState(data, cmd, SHADER_SOLID) == 0) {
+                    size_t count = cmd->data.draw.count;
+                    const GLfloat *verts = (GLfloat *) (((Uint8 *) vertices) + cmd->data.draw.first);
+
+                    /* SetDrawState handles glEnableClientState. */
+                    data->glVertexPointer(2, GL_FLOAT, sizeof(float) * 2, verts);
+
+                    if (count > 2) {
+                        /* joined lines cannot be grouped */
+                        data->glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)count);
+                    } else {
+                        /* let's group non joined lines */
+                        SDL_RenderCommand *finalcmd = cmd;
+                        SDL_RenderCommand *nextcmd = cmd->next;
+                        SDL_BlendMode thisblend = cmd->data.draw.blend;
+
+                        while (nextcmd != NULL) {
+                            const SDL_RenderCommandType nextcmdtype = nextcmd->command;
+                            if (nextcmdtype != SDL_RENDERCMD_DRAW_LINES) {
+                                break;  /* can't go any further on this draw call, different render command up next. */
+                            } else if (nextcmd->data.draw.count != 2) {
+                                break;  /* can't go any further on this draw call, those are joined lines */
+                            } else if (nextcmd->data.draw.blend != thisblend) {
+                                break;  /* can't go any further on this draw call, different blendmode copy up next. */
+                            } else {
+                                finalcmd = nextcmd;  /* we can combine copy operations here. Mark this one as the furthest okay command. */
+                                count += nextcmd->data.draw.count;
+                            }
+                            nextcmd = nextcmd->next;
+                        }
+
+                        data->glDrawArrays(GL_LINES, 0, (GLsizei)count);
+                        cmd = finalcmd;  /* skip any copy commands we just combined in here. */
+                    }
+                }
+                break;
+            }
+
+            case SDL_RENDERCMD_DRAW_POINTS:
             case SDL_RENDERCMD_GEOMETRY: {
-                const GLfloat *verts = (GLfloat *) (((Uint8 *) vertices) + cmd->data.draw.first);
-                SDL_Texture *texture = cmd->data.draw.texture;
-                const size_t count = cmd->data.draw.count;
+                /* as long as we have the same copy command in a row, with the
+                   same texture, we can combine them all into a single draw call. */
+                SDL_Texture *thistexture = cmd->data.draw.texture;
+                SDL_BlendMode thisblend = cmd->data.draw.blend;
+                const SDL_RenderCommandType thiscmdtype = cmd->command;
+                SDL_RenderCommand *finalcmd = cmd;
+                SDL_RenderCommand *nextcmd = cmd->next;
+                size_t count = cmd->data.draw.count;
+                int ret;
+                while (nextcmd != NULL) {
+                    const SDL_RenderCommandType nextcmdtype = nextcmd->command;
+                    if (nextcmdtype != thiscmdtype) {
+                        break;  /* can't go any further on this draw call, different render command up next. */
+                    } else if (nextcmd->data.draw.texture != thistexture || nextcmd->data.draw.blend != thisblend) {
+                        break;  /* can't go any further on this draw call, different texture/blendmode copy up next. */
+                    } else {
+                        finalcmd = nextcmd;  /* we can combine copy operations here. Mark this one as the furthest okay command. */
+                        count += nextcmd->data.draw.count;
+                    }
+                    nextcmd = nextcmd->next;
+                }
 
-                if (texture) {
-                    SetCopyState(data, cmd);
+                if (thistexture) {
+                    ret = SetCopyState(data, cmd);
                 } else {
-                    SetDrawState(data, cmd, SHADER_SOLID);
+                    ret = SetDrawState(data, cmd, SHADER_SOLID);
                 }
 
-                {
-                    Uint32 color = data->drawstate.color;
-                    GLubyte a = (GLubyte)((color >> 24) & 0xFF);
-                    GLubyte r = (GLubyte)((color >> 16) & 0xFF);
-                    GLubyte g = (GLubyte)((color >> 8) & 0xFF);
-                    GLubyte b = (GLubyte)((color >> 0) & 0xFF);
+                if (ret == 0) {
+                    const GLfloat *verts = (GLfloat *) (((Uint8 *) vertices) + cmd->data.draw.first);
+                    int op = GL_TRIANGLES; /* SDL_RENDERCMD_GEOMETRY */
+                    if (thiscmdtype == SDL_RENDERCMD_DRAW_POINTS) {
+                        op = GL_POINTS;
+                    }
 
-                    /* SetDrawState handles glEnableClientState. */
-                    if (texture) {
-                        data->glVertexPointer(2, GL_FLOAT, sizeof(float) * 5, verts + 0);
-                        data->glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(float) * 5, verts + 2);
-                        data->glTexCoordPointer(2, GL_FLOAT, sizeof(float) * 5, verts + 3);
+                    if (thiscmdtype == SDL_RENDERCMD_DRAW_POINTS) {
+                        /* SetDrawState handles glEnableClientState. */
+                        data->glVertexPointer(2, GL_FLOAT, sizeof(float) * 2, verts);
                     } else {
-                        data->glVertexPointer(2, GL_FLOAT, sizeof(float) * 3, verts + 0);
-                        data->glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(float) * 3, verts + 2);
+                        /* SetDrawState handles glEnableClientState. */
+                        if (thistexture) {
+                            data->glVertexPointer(2, GL_FLOAT, sizeof(float) * 5, verts + 0);
+                            data->glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(float) * 5, verts + 2);
+                            data->glTexCoordPointer(2, GL_FLOAT, sizeof(float) * 5, verts + 3);
+                        } else {
+                            data->glVertexPointer(2, GL_FLOAT, sizeof(float) * 3, verts + 0);
+                            data->glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(float) * 3, verts + 2);
+                        }
                     }
 
-                    data->glDrawArrays(GL_TRIANGLES, 0, (GLsizei) count);
+                    data->glDrawArrays(op, 0, (GLsizei) count);
 
                     /* Restore previously set color when we're done. */
-                    data->glColor4ub(r, g, b, a);
+                    if (thiscmdtype != SDL_RENDERCMD_DRAW_POINTS) {
+                        Uint32 color = data->drawstate.color;
+                        GLubyte a = (GLubyte)((color >> 24) & 0xFF);
+                        GLubyte r = (GLubyte)((color >> 16) & 0xFF);
+                        GLubyte g = (GLubyte)((color >> 8) & 0xFF);
+                        GLubyte b = (GLubyte)((color >> 0) & 0xFF);
+                        data->glColor4ub(r, g, b, a);
+                    }
                 }
-                break;
-           }
 
+                cmd = finalcmd;  /* skip any copy commands we just combined in here. */
+                break;
+            }
 
             case SDL_RENDERCMD_NO_OP:
                 break;