Просмотр исходного кода

render: Move to a batching system for rendering (work in progress).

Ryan C. Gordon 6 лет назад
Родитель
Сommit
5fb67f9f55
3 измененных файлов с 455 добавлено и 70 удалено
  1. 25 0
      include/SDL_hints.h
  2. 364 50
      src/render/SDL_render.c
  3. 66 20
      src/render/SDL_sysrender.h

+ 25 - 0
include/SDL_hints.h

@@ -1029,6 +1029,31 @@ extern "C" {
  */
 #define SDL_HINT_AUDIO_CATEGORY   "SDL_AUDIO_CATEGORY"
 
+/**
+ *  \brief  A variable controlling whether the 2D render API is compatible or efficient.
+ *
+ *  This variable can be set to the following values:
+ *
+ *    "0"     - Don't use batching to make rendering more efficient.
+ *    "1"     - Use batching, but might cause problems if app makes its own direct OpenGL calls.
+ *
+ *  Up to SDL 2.0.9, the render API would draw immediately when requested. Now
+ *  it batches up draw requests and sends them all to the GPU only when forced
+ *  to (during SDL_RenderPresent, when changing render targets, by updating a
+ *  texture that the batch needs, etc). This is significantly more efficient,
+ *  but it can cause problems for apps that expect to render on top of the
+ *  render API's output. As such, SDL will disable batching if a specific
+ *  render backend is requested (since this might indicate that the app is
+ *  planning to use the underlying graphics API directly). This hint can
+ *  be used to explicitly request batching in this instance. It is a contract
+ *  that you will either never use the underlying graphics API directly, or
+ *  if you do, you will call SDL_RenderFlush() before you do so any current
+ *  batch goes to the GPU before your work begins. Not following this contract
+ *  will result in undefined behavior.
+ */
+#define SDL_HINT_RENDER_BATCHING  "SDL_RENDER_BATCHING"
+
+
 /**
  *  \brief  An enumeration of hint priorities
  */

+ 364 - 50
src/render/SDL_render.c

@@ -105,6 +105,258 @@ static const SDL_RenderDriver *render_drivers[] = {
 static char renderer_magic;
 static char texture_magic;
 
+
+static int
+FlushRenderCommands(SDL_Renderer *renderer)
+{
+    int retval;
+    SDL_assert((renderer->render_commands == NULL) == (renderer->render_commands_tail == NULL));
+
+    if (renderer->render_commands == NULL) {  /* nothing to do! */
+        SDL_assert(renderer->vertex_data_used == 0);
+        return 0;
+    }
+
+    retval = renderer->RunCommandQueue(renderer, renderer->render_commands, renderer->vertex_data, renderer->vertex_data_used);
+
+    /* Move the whole render command queue to the unused pool so we can reuse them next time. */
+    if (renderer->render_commands_tail != NULL) {
+        renderer->render_commands_tail->next = renderer->render_commands_pool;
+        renderer->render_commands_pool = renderer->render_commands;
+        renderer->render_commands_tail = NULL;
+        renderer->render_commands = NULL;
+    }
+    renderer->vertex_data_used = 0;
+    renderer->render_command_generation++;
+    return retval;
+}
+
+static int
+FlushRenderCommandsIfTextureNeeded(SDL_Texture *texture)
+{
+    SDL_Renderer *renderer = texture->renderer;
+    if (texture->last_command_generation == renderer->render_command_generation) {
+        /* the current command queue depends on this texture, flush the queue now before it changes */
+        return FlushRenderCommands(renderer);
+    }
+    return 0;
+}
+
+static SDL_INLINE int
+FlushRenderCommandsIfNotBatching(SDL_Renderer *renderer)
+{
+    return renderer->batching ? 0 : FlushRenderCommands(renderer);
+}
+
+void *
+SDL_AllocateRenderVertices(SDL_Renderer *renderer, const size_t numbytes, size_t *offset)
+{
+    const size_t needed = renderer->vertex_data_used + numbytes;
+    void *retval;
+
+    while (needed > renderer->vertex_data_allocation) {
+        const size_t current_allocation = renderer->vertex_data ? renderer->vertex_data_allocation : 128;
+        const size_t newsize = current_allocation * 2;
+        void *ptr = SDL_realloc(renderer->vertex_data, newsize);
+        if (ptr == NULL) {
+            SDL_OutOfMemory();
+            return NULL;
+        }
+        renderer->vertex_data = ptr;
+        renderer->vertex_data_allocation = newsize;
+    }
+
+    retval = ((Uint8 *) renderer->vertex_data) + renderer->vertex_data_used;
+    if (offset) {
+        *offset = renderer->vertex_data_used;
+    }
+
+    renderer->vertex_data_used += numbytes;
+    return retval;
+}
+
+static SDL_RenderCommand *
+AllocateRenderCommand(SDL_Renderer *renderer)
+{
+    SDL_RenderCommand *retval = NULL;
+
+    /* !!! FIXME: are there threading limitations in SDL's render API? If not, we need to mutex this. */
+    retval = renderer->render_commands_pool;
+    if (retval != NULL) {
+        renderer->render_commands_pool = retval->next;
+        retval->next = NULL;
+    } else {
+        retval = SDL_calloc(1, sizeof (*retval));
+        if (!retval) {
+            SDL_OutOfMemory();
+            return NULL;
+        }
+    }
+
+    SDL_assert((renderer->render_commands == NULL) == (renderer->render_commands_tail == NULL));
+    if (renderer->render_commands_tail != NULL) {
+        renderer->render_commands_tail->next = retval;
+    } else {
+        renderer->render_commands = retval;
+    }
+    renderer->render_commands_tail = retval;
+
+    return retval;
+}
+
+static int
+QueueCmdUpdateViewport(SDL_Renderer *renderer)
+{
+    SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
+    if (cmd == NULL) {
+        return -1;
+    }
+
+    cmd->command = SDL_RENDERCMD_SETVIEWPORT;
+    SDL_memcpy(&cmd->data.viewport, &renderer->viewport, sizeof (cmd->data.viewport));
+    return FlushRenderCommandsIfNotBatching(renderer);
+}
+
+static int
+QueueCmdUpdateClipRect(SDL_Renderer *renderer)
+{
+    SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
+    if (cmd == NULL) {
+        return -1;
+    }
+
+    cmd->command = SDL_RENDERCMD_SETCLIPRECT;
+    cmd->data.cliprect.enabled = renderer->clipping_enabled;
+    SDL_memcpy(&cmd->data.cliprect.rect, &renderer->clip_rect, sizeof (cmd->data.cliprect.rect));
+    return FlushRenderCommandsIfNotBatching(renderer);
+}
+
+static int
+QueueCmdClear(SDL_Renderer *renderer)
+{
+    SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
+    if (cmd == NULL) {
+        return -1;
+    }
+
+    cmd->command = SDL_RENDERCMD_CLEAR;
+    cmd->data.color.r = renderer->r;
+    cmd->data.color.g = renderer->g;
+    cmd->data.color.b = renderer->b;
+    cmd->data.color.a = renderer->a;
+    return FlushRenderCommandsIfNotBatching(renderer);
+}
+
+static SDL_RenderCommand *
+PrepQueueCmdDrawSolid(SDL_Renderer *renderer, const SDL_RenderCommandType cmdtype)
+{
+    SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
+    if (cmd != NULL) {
+        cmd->command = cmdtype;
+        cmd->data.draw.first = 0;  /* render backend will fill this in. */
+        cmd->data.draw.count = 0;  /* render backend will fill this in. */
+        cmd->data.draw.r = renderer->r;
+        cmd->data.draw.g = renderer->g;
+        cmd->data.draw.b = renderer->b;
+        cmd->data.draw.a = renderer->a;
+        cmd->data.draw.blend = renderer->blendMode;
+        cmd->data.draw.texture = NULL;  /* no texture. */
+    }
+    return cmd;
+}
+
+static int
+QueueCmdDrawPoints(SDL_Renderer *renderer, const SDL_FPoint * points, const int count)
+{
+    SDL_RenderCommand *cmd = PrepQueueCmdDrawSolid(renderer, SDL_RENDERCMD_DRAW_POINTS);
+    int retval = -1;
+    if (cmd != NULL) {
+        retval = renderer->QueueDrawPoints(renderer, cmd, points, count);
+        if (retval < 0) {
+            cmd->command = SDL_RENDERCMD_NO_OP;
+        }
+    }
+    return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer);
+}
+
+static int
+QueueCmdDrawLines(SDL_Renderer *renderer, const SDL_FPoint * points, const int count)
+{
+    SDL_RenderCommand *cmd = PrepQueueCmdDrawSolid(renderer, SDL_RENDERCMD_DRAW_LINES);
+    int retval = -1;
+    if (cmd != NULL) {
+        retval = renderer->QueueDrawLines(renderer, cmd, points, count);
+        if (retval < 0) {
+            cmd->command = SDL_RENDERCMD_NO_OP;
+        }
+    }
+    return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer);
+}
+
+static int
+QueueCmdFillRects(SDL_Renderer *renderer, const SDL_FRect * rects, const int count)
+{
+    SDL_RenderCommand *cmd = PrepQueueCmdDrawSolid(renderer, SDL_RENDERCMD_FILL_RECTS);
+    int retval = -1;
+    if (cmd != NULL) {
+        retval = renderer->QueueFillRects(renderer, cmd, rects, count);
+        if (retval < 0) {
+            cmd->command = SDL_RENDERCMD_NO_OP;
+        }
+    }
+    return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer);
+}
+
+static SDL_RenderCommand *
+PrepQueueCmdDrawTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_RenderCommandType cmdtype)
+{
+    SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
+    if (cmd != NULL) {
+        cmd->command = cmdtype;
+        cmd->data.draw.first = 0;  /* render backend will fill this in. */
+        cmd->data.draw.count = 0;  /* render backend will fill this in. */
+        cmd->data.draw.r = texture->r;
+        cmd->data.draw.g = texture->g;
+        cmd->data.draw.b = texture->b;
+        cmd->data.draw.a = texture->a;
+        cmd->data.draw.blend = texture->blendMode;
+        cmd->data.draw.texture = texture;
+    }
+    return cmd;
+}
+
+static int
+QueueCmdCopy(SDL_Renderer *renderer, SDL_Texture * texture, const SDL_Rect * srcrect, const SDL_FRect * dstrect)
+{
+    SDL_RenderCommand *cmd = PrepQueueCmdDrawTexture(renderer, texture, SDL_RENDERCMD_COPY);
+    int retval = -1;
+    if (cmd != NULL) {
+        retval = renderer->QueueCopy(renderer, cmd, texture, srcrect, dstrect);
+        if (retval < 0) {
+            cmd->command = SDL_RENDERCMD_NO_OP;
+        }
+    }
+    return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer);
+}
+
+static int
+QueueCmdCopyEx(SDL_Renderer *renderer, SDL_Texture * texture,
+               const SDL_Rect * srcquad, const SDL_FRect * dstrect,
+               const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
+{
+    SDL_RenderCommand *cmd = PrepQueueCmdDrawTexture(renderer, texture, SDL_RENDERCMD_COPY_EX);
+    SDL_assert(renderer->QueueCopyEx != NULL);  /* should have caught at higher level. */
+    int retval = -1;
+    if (cmd != NULL) {
+        retval = renderer->QueueCopyEx(renderer, cmd, texture, srcquad, dstrect, angle, center, flip);
+        if (retval < 0) {
+            cmd->command = SDL_RENDERCMD_NO_OP;
+        }
+    }
+    return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer);
+}
+
+
 static int UpdateLogicalSize(SDL_Renderer *renderer);
 
 int
@@ -183,7 +435,7 @@ SDL_RendererEventWatch(void *userdata, SDL_Event *event)
                         renderer->viewport.y = 0;
                         renderer->viewport.w = w;
                         renderer->viewport.h = h;
-                        renderer->UpdateViewport(renderer);
+                        QueueCmdUpdateViewport(renderer);
                     }
                 }
 
@@ -300,12 +552,25 @@ SDL_CreateWindowAndRenderer(int width, int height, Uint32 window_flags,
     return 0;
 }
 
+static SDL_INLINE
+void VerifyDrawQueueFunctions(const SDL_Renderer *renderer)
+{
+    /* all of these functions are required to be implemented, even as no-ops, so we don't
+        have to check that they aren't NULL over and over. */
+    SDL_assert(renderer->QueueDrawPoints != NULL);
+    SDL_assert(renderer->QueueDrawLines != NULL);
+    SDL_assert(renderer->QueueFillRects != NULL);
+    SDL_assert(renderer->QueueCopy != NULL);
+    SDL_assert(renderer->RunCommandQueue != NULL);
+}
+
 SDL_Renderer *
 SDL_CreateRenderer(SDL_Window * window, int index, Uint32 flags)
 {
 #if !SDL_RENDER_DISABLED
     SDL_Renderer *renderer = NULL;
     int n = SDL_GetNumRenderDrivers();
+    SDL_bool batching = SDL_TRUE;
     const char *hint;
 
     if (!window) {
@@ -335,6 +600,9 @@ SDL_CreateRenderer(SDL_Window * window, int index, Uint32 flags)
                 if (SDL_strcasecmp(hint, driver->info.name) == 0) {
                     /* Create a new renderer instance */
                     renderer = driver->CreateRenderer(window, flags);
+                    if (renderer) {
+                        batching = SDL_FALSE;
+                    }
                     break;
                 }
             }
@@ -366,9 +634,18 @@ SDL_CreateRenderer(SDL_Window * window, int index, Uint32 flags)
         }
         /* Create a new renderer instance */
         renderer = render_drivers[index]->CreateRenderer(window, flags);
+        batching = SDL_FALSE;
     }
 
     if (renderer) {
+        VerifyDrawQueueFunctions(renderer);
+
+        /* let app/user override batching decisions. */
+        if (SDL_GetHint(SDL_HINT_RENDER_BATCHING)) {
+            batching = SDL_GetHintBoolean(SDL_HINT_RENDER_BATCHING, SDL_TRUE);
+        }
+
+        renderer->batching = batching;
         renderer->magic = &renderer_magic;
         renderer->window = window;
         renderer->target_mutex = SDL_CreateMutex();
@@ -377,6 +654,9 @@ SDL_CreateRenderer(SDL_Window * window, int index, Uint32 flags)
         renderer->dpi_scale.x = 1.0f;
         renderer->dpi_scale.y = 1.0f;
 
+        /* new textures start at zero, so we start at 1 so first render doesn't flush by accident. */
+        renderer->render_command_generation = 1;
+
         if (window && renderer->GetOutputSize) {
             int window_w, window_h;
             int output_w, output_h;
@@ -418,11 +698,15 @@ SDL_CreateSoftwareRenderer(SDL_Surface * surface)
     renderer = SW_CreateRendererForSurface(surface);
 
     if (renderer) {
+        VerifyDrawQueueFunctions(renderer);
         renderer->magic = &renderer_magic;
         renderer->target_mutex = SDL_CreateMutex();
         renderer->scale.x = 1.0f;
         renderer->scale.y = 1.0f;
 
+        /* new textures start at zero, so we start at 1 so first render doesn't flush by accident. */
+        renderer->render_command_generation = 1;
+
         SDL_RenderSetViewport(renderer, NULL);
     }
     return renderer;
@@ -758,11 +1042,8 @@ SDL_SetTextureColorMod(SDL_Texture * texture, Uint8 r, Uint8 g, Uint8 b)
     texture->b = b;
     if (texture->native) {
         return SDL_SetTextureColorMod(texture->native, r, g, b);
-    } else if (renderer->SetTextureColorMod) {
-        return renderer->SetTextureColorMod(renderer, texture);
-    } else {
-        return 0;
     }
+    return 0;
 }
 
 int
@@ -799,11 +1080,8 @@ SDL_SetTextureAlphaMod(SDL_Texture * texture, Uint8 alpha)
     texture->a = alpha;
     if (texture->native) {
         return SDL_SetTextureAlphaMod(texture->native, alpha);
-    } else if (renderer->SetTextureAlphaMod) {
-        return renderer->SetTextureAlphaMod(renderer, texture);
-    } else {
-        return 0;
     }
+    return 0;
 }
 
 int
@@ -831,11 +1109,8 @@ SDL_SetTextureBlendMode(SDL_Texture * texture, SDL_BlendMode blendMode)
     texture->blendMode = blendMode;
     if (texture->native) {
         return SDL_SetTextureBlendMode(texture->native, blendMode);
-    } else if (renderer->SetTextureBlendMode) {
-        return renderer->SetTextureBlendMode(renderer, texture);
-    } else {
-        return 0;
     }
+    return 0;
 }
 
 int
@@ -940,7 +1215,6 @@ int
 SDL_UpdateTexture(SDL_Texture * texture, const SDL_Rect * rect,
                   const void *pixels, int pitch)
 {
-    SDL_Renderer *renderer;
     SDL_Rect full_rect;
 
     CHECK_TEXTURE_MAGIC(texture, -1);
@@ -967,7 +1241,10 @@ SDL_UpdateTexture(SDL_Texture * texture, const SDL_Rect * rect,
     } else if (texture->native) {
         return SDL_UpdateTextureNative(texture, rect, pixels, pitch);
     } else {
-        renderer = texture->renderer;
+        SDL_Renderer *renderer = texture->renderer;
+        if (FlushRenderCommandsIfTextureNeeded(texture) < 0) {
+            return -1;
+        }
         return renderer->UpdateTexture(renderer, texture, rect, pixels, pitch);
     }
 }
@@ -1077,6 +1354,9 @@ int SDL_UpdateYUVTexture(SDL_Texture * texture, const SDL_Rect * rect,
         renderer = texture->renderer;
         SDL_assert(renderer->UpdateTextureYUV);
         if (renderer->UpdateTextureYUV) {
+            if (FlushRenderCommandsIfTextureNeeded(texture) < 0) {
+                return -1;
+            }
             return renderer->UpdateTextureYUV(renderer, texture, rect, Yplane, Ypitch, Uplane, Upitch, Vplane, Vpitch);
         } else {
             return SDL_Unsupported();
@@ -1107,7 +1387,6 @@ int
 SDL_LockTexture(SDL_Texture * texture, const SDL_Rect * rect,
                 void **pixels, int *pitch)
 {
-    SDL_Renderer *renderer;
     SDL_Rect full_rect;
 
     CHECK_TEXTURE_MAGIC(texture, -1);
@@ -1125,11 +1404,18 @@ SDL_LockTexture(SDL_Texture * texture, const SDL_Rect * rect,
     }
 
     if (texture->yuv) {
+        if (FlushRenderCommandsIfTextureNeeded(texture) < 0) {
+            return -1;
+        }
         return SDL_LockTextureYUV(texture, rect, pixels, pitch);
     } else if (texture->native) {
+        /* Calls a real SDL_LockTexture/SDL_UnlockTexture on unlock, flushing then. */
         return SDL_LockTextureNative(texture, rect, pixels, pitch);
     } else {
-        renderer = texture->renderer;
+        SDL_Renderer *renderer = texture->renderer;
+        if (FlushRenderCommandsIfTextureNeeded(texture) < 0) {
+            return -1;
+        }
         return renderer->LockTexture(renderer, texture, rect, pixels, pitch);
     }
 }
@@ -1179,8 +1465,6 @@ SDL_UnlockTextureNative(SDL_Texture * texture)
 void
 SDL_UnlockTexture(SDL_Texture * texture)
 {
-    SDL_Renderer *renderer;
-
     CHECK_TEXTURE_MAGIC(texture, );
 
     if (texture->access != SDL_TEXTUREACCESS_STREAMING) {
@@ -1191,7 +1475,7 @@ SDL_UnlockTexture(SDL_Texture * texture)
     } else if (texture->native) {
         SDL_UnlockTextureNative(texture);
     } else {
-        renderer = texture->renderer;
+        SDL_Renderer *renderer = texture->renderer;
         renderer->UnlockTexture(renderer, texture);
     }
 }
@@ -1216,6 +1500,8 @@ SDL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
         return 0;
     }
 
+    FlushRenderCommands(renderer);  /* time to send everything to the GPU! */
+
     /* texture == NULL is valid and means reset the target to the window */
     if (texture) {
         CHECK_TEXTURE_MAGIC(texture, -1);
@@ -1271,10 +1557,10 @@ SDL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
 
     SDL_UnlockMutex(renderer->target_mutex);
 
-    if (renderer->UpdateViewport(renderer) < 0) {
+    if (QueueCmdUpdateViewport(renderer) < 0) {
         return -1;
     }
-    if (renderer->UpdateClipRect(renderer) < 0) {
+    if (QueueCmdUpdateClipRect(renderer) < 0) {
         return -1;
     }
 
@@ -1465,7 +1751,7 @@ SDL_RenderSetViewport(SDL_Renderer * renderer, const SDL_Rect * rect)
             return -1;
         }
     }
-    return renderer->UpdateViewport(renderer);
+    return QueueCmdUpdateViewport(renderer);
 }
 
 void
@@ -1496,7 +1782,7 @@ SDL_RenderSetClipRect(SDL_Renderer * renderer, const SDL_Rect * rect)
         renderer->clipping_enabled = SDL_FALSE;
         SDL_zero(renderer->clip_rect);
     }
-    return renderer->UpdateClipRect(renderer);
+    return QueueCmdUpdateClipRect(renderer);
 }
 
 void
@@ -1601,12 +1887,7 @@ int
 SDL_RenderClear(SDL_Renderer * renderer)
 {
     CHECK_RENDERER_MAGIC(renderer, -1);
-
-    /* Don't draw while we're hidden */
-    if (renderer->hidden) {
-        return 0;
-    }
-    return renderer->RenderClear(renderer);
+    return QueueCmdClear(renderer);
 }
 
 int
@@ -1625,7 +1906,7 @@ RenderDrawPointsWithRects(SDL_Renderer * renderer,
 {
     SDL_FRect *frects;
     int i;
-    int status;
+    int status = -1;
 
     frects = SDL_stack_alloc(SDL_FRect, count);
     if (!frects) {
@@ -1638,7 +1919,7 @@ RenderDrawPointsWithRects(SDL_Renderer * renderer,
         frects[i].h = renderer->scale.y;
     }
 
-    status = renderer->RenderFillRects(renderer, frects, count);
+    status = QueueCmdFillRects(renderer, frects, count);
 
     SDL_stack_free(frects);
 
@@ -1680,7 +1961,7 @@ SDL_RenderDrawPoints(SDL_Renderer * renderer,
         fpoints[i].y = points[i].y * renderer->scale.y;
     }
 
-    status = renderer->RenderDrawPoints(renderer, fpoints, count);
+    status = QueueCmdDrawPoints(renderer, fpoints, count);
 
     SDL_stack_free(fpoints);
 
@@ -1706,20 +1987,18 @@ RenderDrawLinesWithRects(SDL_Renderer * renderer,
     SDL_FRect *frect;
     SDL_FRect *frects;
     SDL_FPoint fpoints[2];
-    int i, nrects;
-    int status;
+    int i, nrects = 0;
+    int status = 0;
 
     frects = SDL_stack_alloc(SDL_FRect, count-1);
     if (!frects) {
         return SDL_OutOfMemory();
     }
 
-    status = 0;
-    nrects = 0;
     for (i = 0; i < count-1; ++i) {
         if (points[i].x == points[i+1].x) {
-            int minY = SDL_min(points[i].y, points[i+1].y);
-            int maxY = SDL_max(points[i].y, points[i+1].y);
+            const int minY = SDL_min(points[i].y, points[i+1].y);
+            const int maxY = SDL_max(points[i].y, points[i+1].y);
 
             frect = &frects[nrects++];
             frect->x = points[i].x * renderer->scale.x;
@@ -1727,8 +2006,8 @@ RenderDrawLinesWithRects(SDL_Renderer * renderer,
             frect->w = renderer->scale.x;
             frect->h = (maxY - minY + 1) * renderer->scale.y;
         } else if (points[i].y == points[i+1].y) {
-            int minX = SDL_min(points[i].x, points[i+1].x);
-            int maxX = SDL_max(points[i].x, points[i+1].x);
+            const int minX = SDL_min(points[i].x, points[i+1].x);
+            const int maxX = SDL_max(points[i].x, points[i+1].x);
 
             frect = &frects[nrects++];
             frect->x = minX * renderer->scale.x;
@@ -1741,11 +2020,11 @@ RenderDrawLinesWithRects(SDL_Renderer * renderer,
             fpoints[0].y = points[i].y * renderer->scale.y;
             fpoints[1].x = points[i+1].x * renderer->scale.x;
             fpoints[1].y = points[i+1].y * renderer->scale.y;
-            status += renderer->RenderDrawLines(renderer, fpoints, 2);
+            status += QueueCmdDrawLines(renderer, fpoints, 2);
         }
     }
 
-    status += renderer->RenderFillRects(renderer, frects, nrects);
+    status += QueueCmdFillRects(renderer, frects, nrects);
 
     SDL_stack_free(frects);
 
@@ -1790,7 +2069,7 @@ SDL_RenderDrawLines(SDL_Renderer * renderer,
         fpoints[i].y = points[i].y * renderer->scale.y;
     }
 
-    status = renderer->RenderDrawLines(renderer, fpoints, count);
+    status = QueueCmdDrawLines(renderer, fpoints, count);
 
     SDL_stack_free(fpoints);
 
@@ -1904,7 +2183,7 @@ SDL_RenderFillRects(SDL_Renderer * renderer,
         frects[i].h = rects[i].h * renderer->scale.y;
     }
 
-    status = renderer->RenderFillRects(renderer, frects, count);
+    status = QueueCmdFillRects(renderer, frects, count);
 
     SDL_stack_free(frects);
 
@@ -1960,7 +2239,9 @@ SDL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
     frect.w = real_dstrect.w * renderer->scale.x;
     frect.h = real_dstrect.h * renderer->scale.y;
 
-    return renderer->RenderCopy(renderer, texture, &real_srcrect, &frect);
+    texture->last_command_generation = renderer->render_command_generation;
+
+    return QueueCmdCopy(renderer, texture, &real_srcrect, &frect);
 }
 
 
@@ -1985,7 +2266,7 @@ SDL_RenderCopyEx(SDL_Renderer * renderer, SDL_Texture * texture,
     if (renderer != texture->renderer) {
         return SDL_SetError("Texture was not created with this renderer");
     }
-    if (!renderer->RenderCopyEx) {
+    if (!renderer->QueueCopyEx) {
         return SDL_SetError("Renderer does not support RenderCopyEx");
     }
 
@@ -2032,7 +2313,9 @@ SDL_RenderCopyEx(SDL_Renderer * renderer, SDL_Texture * texture,
     fcenter.x = real_center.x * renderer->scale.x;
     fcenter.y = real_center.y * renderer->scale.y;
 
-    return renderer->RenderCopyEx(renderer, texture, &real_srcrect, &frect, angle, &fcenter, flip);
+    texture->last_command_generation = renderer->render_command_generation;
+
+    return QueueCmdCopyEx(renderer, texture, &real_srcrect, &frect, angle, &fcenter, flip);
 }
 
 int
@@ -2047,6 +2330,8 @@ SDL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
         return SDL_Unsupported();
     }
 
+    FlushRenderCommands(renderer);  /* we need to render before we read the results. */
+
     if (!format) {
         format = SDL_GetWindowPixelFormat(renderer->window);
     }
@@ -2077,7 +2362,9 @@ SDL_RenderPresent(SDL_Renderer * renderer)
 {
     CHECK_RENDERER_MAGIC(renderer, );
 
-    /* Don't draw while we're hidden */
+    FlushRenderCommands(renderer);  /* time to send everything to the GPU! */
+
+    /* Don't present while we're hidden */
     if (renderer->hidden) {
         return;
     }
@@ -2093,7 +2380,9 @@ SDL_DestroyTexture(SDL_Texture * texture)
 
     renderer = texture->renderer;
     if (texture == renderer->target) {
-        SDL_SetRenderTarget(renderer, NULL);
+        SDL_SetRenderTarget(renderer, NULL);  /* implies command queue flush */
+    } else {
+        FlushRenderCommandsIfTextureNeeded(texture);
     }
 
     texture->magic = NULL;
@@ -2122,10 +2411,31 @@ SDL_DestroyTexture(SDL_Texture * texture)
 void
 SDL_DestroyRenderer(SDL_Renderer * renderer)
 {
+    SDL_RenderCommand *cmd;
+
     CHECK_RENDERER_MAGIC(renderer, );
 
     SDL_DelEventWatch(SDL_RendererEventWatch, renderer);
 
+    if (renderer->render_commands_tail != NULL) {
+        renderer->render_commands_tail->next = renderer->render_commands_pool;
+        cmd = renderer->render_commands;
+    } else {
+        cmd = renderer->render_commands_pool;
+    }
+
+    renderer->render_commands_pool = NULL;
+    renderer->render_commands_tail = NULL;
+    renderer->render_commands = NULL;
+
+    while (cmd != NULL) {
+        SDL_RenderCommand *next = cmd->next;
+        SDL_free(cmd);
+        cmd = next;
+    }
+
+    SDL_free(renderer->vertex_data);
+
     /* Free existing textures for this renderer */
     while (renderer->textures) {
         SDL_Texture *tex = renderer->textures; (void) tex;
@@ -2157,6 +2467,7 @@ int SDL_GL_BindTexture(SDL_Texture *texture, float *texw, float *texh)
     if (texture->native) {
         return SDL_GL_BindTexture(texture->native, texw, texh);
     } else if (renderer && renderer->GL_BindTexture) {
+        FlushRenderCommandsIfTextureNeeded(texture);  /* in case the app is going to mess with it. */
         return renderer->GL_BindTexture(renderer, texture, texw, texh);
     } else {
         return SDL_Unsupported();
@@ -2172,6 +2483,7 @@ int SDL_GL_UnbindTexture(SDL_Texture *texture)
     if (texture->native) {
         return SDL_GL_UnbindTexture(texture->native);
     } else if (renderer && renderer->GL_UnbindTexture) {
+        FlushRenderCommandsIfTextureNeeded(texture);  /* in case the app messed with it. */
         return renderer->GL_UnbindTexture(renderer, texture);
     }
 
@@ -2184,6 +2496,7 @@ SDL_RenderGetMetalLayer(SDL_Renderer * renderer)
     CHECK_RENDERER_MAGIC(renderer, NULL);
 
     if (renderer->GetMetalLayer) {
+        FlushRenderCommands(renderer);  /* in case the app is going to mess with it. */
         return renderer->GetMetalLayer(renderer);
     }
     return NULL;
@@ -2195,6 +2508,7 @@ SDL_RenderGetMetalCommandEncoder(SDL_Renderer * renderer)
     CHECK_RENDERER_MAGIC(renderer, NULL);
 
     if (renderer->GetMetalCommandEncoder) {
+        FlushRenderCommands(renderer);  /* in case the app is going to mess with it. */
         return renderer->GetMetalCommandEncoder(renderer);
     }
     return NULL;

+ 66 - 20
src/render/SDL_sysrender.h

@@ -75,12 +75,51 @@ struct SDL_Texture
     int pitch;
     SDL_Rect locked_rect;
 
+    Uint32 last_command_generation; /* last command queue generation this texture was in. */
+
     void *driverdata;           /**< Driver specific texture representation */
 
     SDL_Texture *prev;
     SDL_Texture *next;
 };
 
+typedef enum
+{
+    SDL_RENDERCMD_NO_OP,
+    SDL_RENDERCMD_SETVIEWPORT,
+    SDL_RENDERCMD_SETCLIPRECT,
+    SDL_RENDERCMD_CLEAR,
+    SDL_RENDERCMD_DRAW_POINTS,
+    SDL_RENDERCMD_DRAW_LINES,
+    SDL_RENDERCMD_FILL_RECTS,
+    SDL_RENDERCMD_COPY,
+    SDL_RENDERCMD_COPY_EX
+} SDL_RenderCommandType;
+
+typedef struct SDL_RenderCommand
+{
+    SDL_RenderCommandType command;
+    union {
+        SDL_Rect viewport;
+        struct {
+            SDL_bool enabled;
+            SDL_Rect rect;
+        } cliprect;
+        struct {
+            size_t first;
+            size_t count;
+            Uint8 r, g, b, a;
+            SDL_BlendMode blend;
+            SDL_Texture *texture;
+        } draw;
+        struct {
+            Uint8 r, g, b, a;
+        } color;
+    } data;
+    struct SDL_RenderCommand *next;
+} SDL_RenderCommand;
+
+
 /* Define the SDL renderer structure */
 struct SDL_Renderer
 {
@@ -90,12 +129,18 @@ struct SDL_Renderer
     int (*GetOutputSize) (SDL_Renderer * renderer, int *w, int *h);
     SDL_bool (*SupportsBlendMode)(SDL_Renderer * renderer, SDL_BlendMode blendMode);
     int (*CreateTexture) (SDL_Renderer * renderer, SDL_Texture * texture);
-    int (*SetTextureColorMod) (SDL_Renderer * renderer,
-                               SDL_Texture * texture);
-    int (*SetTextureAlphaMod) (SDL_Renderer * renderer,
-                               SDL_Texture * texture);
-    int (*SetTextureBlendMode) (SDL_Renderer * renderer,
-                                SDL_Texture * texture);
+    int (*QueueDrawPoints) (SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points,
+                             int count);
+    int (*QueueDrawLines) (SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points,
+                            int count);
+    int (*QueueFillRects) (SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects,
+                            int count);
+    int (*QueueCopy) (SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
+                       const SDL_Rect * srcrect, const SDL_FRect * dstrect);
+    int (*QueueCopyEx) (SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
+                        const SDL_Rect * srcquad, const SDL_FRect * dstrect,
+                        const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip);
+    int (*RunCommandQueue) (SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize);
     int (*UpdateTexture) (SDL_Renderer * renderer, SDL_Texture * texture,
                           const SDL_Rect * rect, const void *pixels,
                           int pitch);
@@ -108,20 +153,6 @@ struct SDL_Renderer
                         const SDL_Rect * rect, void **pixels, int *pitch);
     void (*UnlockTexture) (SDL_Renderer * renderer, SDL_Texture * texture);
     int (*SetRenderTarget) (SDL_Renderer * renderer, SDL_Texture * texture);
-    int (*UpdateViewport) (SDL_Renderer * renderer);
-    int (*UpdateClipRect) (SDL_Renderer * renderer);
-    int (*RenderClear) (SDL_Renderer * renderer);
-    int (*RenderDrawPoints) (SDL_Renderer * renderer, const SDL_FPoint * points,
-                             int count);
-    int (*RenderDrawLines) (SDL_Renderer * renderer, const SDL_FPoint * points,
-                            int count);
-    int (*RenderFillRects) (SDL_Renderer * renderer, const SDL_FRect * rects,
-                            int count);
-    int (*RenderCopy) (SDL_Renderer * renderer, SDL_Texture * texture,
-                       const SDL_Rect * srcrect, const SDL_FRect * dstrect);
-    int (*RenderCopyEx) (SDL_Renderer * renderer, SDL_Texture * texture,
-                       const SDL_Rect * srcquad, const SDL_FRect * dstrect,
-                       const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip);
     int (*RenderReadPixels) (SDL_Renderer * renderer, const SDL_Rect * rect,
                              Uint32 format, void * pixels, int pitch);
     void (*RenderPresent) (SDL_Renderer * renderer);
@@ -178,6 +209,16 @@ struct SDL_Renderer
     Uint8 r, g, b, a;                   /**< Color for drawing operations values */
     SDL_BlendMode blendMode;            /**< The drawing blend mode */
 
+    SDL_bool batching;
+    SDL_RenderCommand *render_commands;
+    SDL_RenderCommand *render_commands_tail;
+    SDL_RenderCommand *render_commands_pool;
+    Uint32 render_command_generation;
+
+    void *vertex_data;
+    size_t vertex_data_used;
+    size_t vertex_data_allocation;
+
     void *driverdata;
 };
 
@@ -209,6 +250,11 @@ extern SDL_BlendFactor SDL_GetBlendModeSrcAlphaFactor(SDL_BlendMode blendMode);
 extern SDL_BlendFactor SDL_GetBlendModeDstAlphaFactor(SDL_BlendMode blendMode);
 extern SDL_BlendOperation SDL_GetBlendModeAlphaOperation(SDL_BlendMode blendMode);
 
+/* drivers call this during their Queue*() methods to make space in a array that are used
+   for a vertex buffer during RunCommandQueue(). Pointers returned here are only valid until
+   the next call, because it might be in an array that gets realloc()'d. */
+extern void *SDL_AllocateRenderVertices(SDL_Renderer *renderer, const size_t numbytes, size_t *offset);
+
 #endif /* SDL_sysrender_h_ */
 
 /* vi: set ts=4 sw=4 expandtab: */