render: A bunch of high-level improvements. SDL-ryan-batching-renderer
authorRyan C. Gordon <icculus@icculus.org>
Sun, 23 Sep 2018 23:20:40 -0400
branchSDL-ryan-batching-renderer
changeset 12214d377f3ccade7
parent 12213 e4f5271ec8cc
child 12215 985191d8125c
render: A bunch of high-level improvements.

- high-level filters out duplicate render commands from the queue so
backends don't have to.
- Setting draw color is now a render command, so backends can put color
information into the vertex buffer to upload with everything else instead
of setting it with slower dynamic data later.
- backends can request that they always batch, even for legacy programs,
since the lowlevel API can deal with it (Metal, and eventually Vulkan
and such...)
- high-level makes sure the queue has at least one setdrawcolor and
setviewport command before any draw calls, so the backends don't ever have
to manage cases where this hasn't been explicitly set yet.
- backends allocating vertex buffer space can specify alignment, and the
high-level will keep track of gaps in the buffer between the last used
positions and the aligned data that can be used for later allocations
(Metal and such need to specify some constant data on 256 byte boundaries,
but we don't want to waste all that space we had to skip to meet alignment
requirements).
src/render/SDL_render.c
src/render/SDL_sysrender.h
     1.1 --- a/src/render/SDL_render.c	Thu Sep 20 16:40:04 2018 -0400
     1.2 +++ b/src/render/SDL_render.c	Sun Sep 23 23:20:40 2018 -0400
     1.3 @@ -109,7 +109,10 @@
     1.4  static int
     1.5  FlushRenderCommands(SDL_Renderer *renderer)
     1.6  {
     1.7 +    SDL_AllocVertGap *prevgap = &renderer->vertex_data_gaps;
     1.8 +    SDL_AllocVertGap *gap = prevgap;
     1.9      int retval;
    1.10 +
    1.11      SDL_assert((renderer->render_commands == NULL) == (renderer->render_commands_tail == NULL));
    1.12  
    1.13      if (renderer->render_commands == NULL) {  /* nothing to do! */
    1.14 @@ -119,6 +122,14 @@
    1.15  
    1.16      retval = renderer->RunCommandQueue(renderer, renderer->render_commands, renderer->vertex_data, renderer->vertex_data_used);
    1.17  
    1.18 +    while (gap) {
    1.19 +        prevgap = gap;
    1.20 +        gap = gap->next;
    1.21 +    }
    1.22 +    prevgap->next = renderer->vertex_data_gaps_pool;
    1.23 +    renderer->vertex_data_gaps_pool = renderer->vertex_data_gaps.next;
    1.24 +    renderer->vertex_data_gaps.next = NULL;
    1.25 +
    1.26      /* Move the whole render command queue to the unused pool so we can reuse them next time. */
    1.27      if (renderer->render_commands_tail != NULL) {
    1.28          renderer->render_commands_tail->next = renderer->render_commands_pool;
    1.29 @@ -128,6 +139,9 @@
    1.30      }
    1.31      renderer->vertex_data_used = 0;
    1.32      renderer->render_command_generation++;
    1.33 +    renderer->color_queued = SDL_FALSE;
    1.34 +    renderer->viewport_queued = SDL_FALSE;
    1.35 +    renderer->cliprect_queued = SDL_FALSE;
    1.36      return retval;
    1.37  }
    1.38  
    1.39 @@ -148,14 +162,77 @@
    1.40      return renderer->batching ? 0 : FlushRenderCommands(renderer);
    1.41  }
    1.42  
    1.43 +static SDL_AllocVertGap *
    1.44 +AllocateVertexGap(SDL_Renderer *renderer)
    1.45 +{
    1.46 +    SDL_AllocVertGap *retval = renderer->vertex_data_gaps_pool;
    1.47 +    if (retval) {
    1.48 +        renderer->vertex_data_gaps_pool = retval->next;
    1.49 +        retval->next = NULL;
    1.50 +    } else {
    1.51 +        retval = (SDL_AllocVertGap *) SDL_malloc(sizeof (SDL_AllocVertGap));
    1.52 +        if (!retval) {
    1.53 +            SDL_OutOfMemory();
    1.54 +        }
    1.55 +    }
    1.56 +    return retval;
    1.57 +}
    1.58 +
    1.59 +
    1.60  void *
    1.61 -SDL_AllocateRenderVertices(SDL_Renderer *renderer, const size_t numbytes, size_t *offset)
    1.62 +SDL_AllocateRenderVertices(SDL_Renderer *renderer, const size_t numbytes, const size_t alignment, size_t *offset)
    1.63  {
    1.64 -    const size_t needed = renderer->vertex_data_used + numbytes;
    1.65 +    const size_t needed = renderer->vertex_data_used + numbytes + alignment;
    1.66 +    size_t aligner, aligned;
    1.67      void *retval;
    1.68  
    1.69 +    SDL_AllocVertGap *prevgap = &renderer->vertex_data_gaps;
    1.70 +    SDL_AllocVertGap *gap = prevgap->next;
    1.71 +    while (gap) {
    1.72 +        const size_t gapoffset = gap->offset;
    1.73 +        aligner = (alignment && ((gap->offset % alignment) != 0)) ? (alignment - (gap->offset % alignment)) : 0;
    1.74 +        aligned = gapoffset + aligner;
    1.75 +
    1.76 +        /* Can we use this gap? */
    1.77 +        if ((aligner < gap->len) && ((gap->len - aligner) >= numbytes)) {
    1.78 +            /* we either finished this gap off, trimmed the left, trimmed the right, or split it into two gaps. */
    1.79 +            if (gap->len == numbytes) {  /* finished it off, remove it */
    1.80 +                SDL_assert(aligned == gapoffset);
    1.81 +                prevgap->next = gap->next;
    1.82 +                gap->next = renderer->vertex_data_gaps_pool;
    1.83 +                renderer->vertex_data_gaps_pool = gap;
    1.84 +            } else if (aligned == gapoffset) {  /* trimmed the left */
    1.85 +                gap->offset += numbytes;
    1.86 +                gap->len -= numbytes;
    1.87 +            } else if (((aligned - gapoffset) + numbytes) == gap->len) {  /* trimmed the right */
    1.88 +                gap->len -= numbytes;
    1.89 +            } else {  /* split into two gaps */
    1.90 +                SDL_AllocVertGap *newgap = AllocateVertexGap(renderer);
    1.91 +                if (!newgap) {
    1.92 +                    return NULL;
    1.93 +                }
    1.94 +                newgap->offset = aligned + numbytes;
    1.95 +                newgap->len = gap->len - (aligner + numbytes);
    1.96 +                newgap->next = gap->next;
    1.97 +                // gap->offset doesn't change.
    1.98 +                gap->len = aligner;
    1.99 +                gap->next = newgap;
   1.100 +            }
   1.101 +
   1.102 +            if (offset) {
   1.103 +                *offset = aligned;
   1.104 +            }
   1.105 +            return ((Uint8 *) renderer->vertex_data) + aligned;
   1.106 +        }
   1.107 +
   1.108 +        /* Try the next gap */
   1.109 +        prevgap = gap;
   1.110 +        gap = gap->next;
   1.111 +    }
   1.112 +
   1.113 +    /* no gaps with enough space; get a new piece of the vertex buffer */
   1.114      while (needed > renderer->vertex_data_allocation) {
   1.115 -        const size_t current_allocation = renderer->vertex_data ? renderer->vertex_data_allocation : 128;
   1.116 +        const size_t current_allocation = renderer->vertex_data ? renderer->vertex_data_allocation : 1024;
   1.117          const size_t newsize = current_allocation * 2;
   1.118          void *ptr = SDL_realloc(renderer->vertex_data, newsize);
   1.119          if (ptr == NULL) {
   1.120 @@ -166,12 +243,26 @@
   1.121          renderer->vertex_data_allocation = newsize;
   1.122      }
   1.123  
   1.124 -    retval = ((Uint8 *) renderer->vertex_data) + renderer->vertex_data_used;
   1.125 +    aligner = (alignment && ((renderer->vertex_data_used % alignment) != 0)) ? (alignment - (renderer->vertex_data_used % alignment)) : 0;
   1.126 +    aligned = renderer->vertex_data_used + aligner;
   1.127 +
   1.128 +    retval = ((Uint8 *) renderer->vertex_data) + aligned;
   1.129      if (offset) {
   1.130 -        *offset = renderer->vertex_data_used;
   1.131 +        *offset = aligned;
   1.132      }
   1.133  
   1.134 -    renderer->vertex_data_used += numbytes;
   1.135 +    if (aligner) {  /* made a new gap... */
   1.136 +        SDL_AllocVertGap *newgap = AllocateVertexGap(renderer);
   1.137 +        if (newgap) {  /* just let it slide as lost space if malloc fails. */
   1.138 +            newgap->offset = renderer->vertex_data_used;
   1.139 +            newgap->len = aligner;
   1.140 +            newgap->next = NULL;
   1.141 +            prevgap->next = newgap;
   1.142 +        }
   1.143 +    }
   1.144 +
   1.145 +    renderer->vertex_data_used += aligner + numbytes;
   1.146 +
   1.147      return retval;
   1.148  }
   1.149  
   1.150 @@ -205,30 +296,77 @@
   1.151  }
   1.152  
   1.153  static int
   1.154 -QueueCmdUpdateViewport(SDL_Renderer *renderer)
   1.155 +QueueCmdSetViewport(SDL_Renderer *renderer)
   1.156  {
   1.157 -    SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
   1.158 -    if (cmd == NULL) {
   1.159 -        return -1;
   1.160 +    int retval = 0;
   1.161 +    if (!renderer->viewport_queued || (SDL_memcmp(&renderer->viewport, &renderer->last_queued_viewport, sizeof (SDL_Rect)) != 0)) {
   1.162 +        SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
   1.163 +        retval = -1;
   1.164 +        if (cmd != NULL) {
   1.165 +            cmd->command = SDL_RENDERCMD_SETVIEWPORT;
   1.166 +            cmd->data.viewport.first = 0;  /* render backend will fill this in. */
   1.167 +            SDL_memcpy(&cmd->data.viewport.rect, &renderer->viewport, sizeof (renderer->viewport));
   1.168 +            retval = renderer->QueueSetViewport(renderer, cmd);
   1.169 +            if (retval < 0) {
   1.170 +                cmd->command = SDL_RENDERCMD_NO_OP;
   1.171 +            } else {
   1.172 +                SDL_memcpy(&renderer->last_queued_viewport, &renderer->viewport, sizeof (SDL_Rect));
   1.173 +                renderer->viewport_queued = SDL_TRUE;
   1.174 +            }
   1.175 +        }
   1.176      }
   1.177 -
   1.178 -    cmd->command = SDL_RENDERCMD_SETVIEWPORT;
   1.179 -    SDL_memcpy(&cmd->data.viewport, &renderer->viewport, sizeof (cmd->data.viewport));
   1.180 -    return FlushRenderCommandsIfNotBatching(renderer);
   1.181 +    return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer);
   1.182  }
   1.183  
   1.184  static int
   1.185 -QueueCmdUpdateClipRect(SDL_Renderer *renderer)
   1.186 +QueueCmdSetClipRect(SDL_Renderer *renderer)
   1.187  {
   1.188 -    SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
   1.189 -    if (cmd == NULL) {
   1.190 -        return -1;
   1.191 +    int retval = 0;
   1.192 +    if ((!renderer->cliprect_queued) ||
   1.193 +         (renderer->clipping_enabled != renderer->last_queued_cliprect_enabled) ||
   1.194 +         (SDL_memcmp(&renderer->clip_rect, &renderer->last_queued_cliprect, sizeof (SDL_Rect)) != 0)) {
   1.195 +        SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
   1.196 +        if (cmd == NULL) {
   1.197 +            retval = -1;
   1.198 +        } else {
   1.199 +            cmd->command = SDL_RENDERCMD_SETCLIPRECT;
   1.200 +            cmd->data.cliprect.enabled = renderer->clipping_enabled;
   1.201 +            SDL_memcpy(&cmd->data.cliprect.rect, &renderer->clip_rect, sizeof (cmd->data.cliprect.rect));
   1.202 +            SDL_memcpy(&renderer->last_queued_cliprect, &renderer->clip_rect, sizeof (SDL_Rect));
   1.203 +            renderer->last_queued_cliprect_enabled = renderer->clipping_enabled;
   1.204 +            renderer->cliprect_queued = SDL_TRUE;
   1.205 +        }
   1.206      }
   1.207 -
   1.208 -    cmd->command = SDL_RENDERCMD_SETCLIPRECT;
   1.209 -    cmd->data.cliprect.enabled = renderer->clipping_enabled;
   1.210 -    SDL_memcpy(&cmd->data.cliprect.rect, &renderer->clip_rect, sizeof (cmd->data.cliprect.rect));
   1.211 -    return FlushRenderCommandsIfNotBatching(renderer);
   1.212 +    return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer);
   1.213 +}
   1.214 +
   1.215 +static int
   1.216 +QueueCmdSetDrawColor(SDL_Renderer *renderer, const Uint8 r, const Uint8 g, const Uint8 b, const Uint8 a)
   1.217 +{
   1.218 +    const Uint32 color = ((a << 24) | (r << 16) | (g << 8) | b);
   1.219 +    int retval = 0;
   1.220 +    
   1.221 +    if (!renderer->color_queued || (color != renderer->last_queued_color)) {
   1.222 +        SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
   1.223 +        retval = -1;
   1.224 +
   1.225 +        if (cmd != NULL) {
   1.226 +            cmd->command = SDL_RENDERCMD_SETDRAWCOLOR;
   1.227 +            cmd->data.color.first = 0;  /* render backend will fill this in. */
   1.228 +            cmd->data.color.r = r;
   1.229 +            cmd->data.color.g = g;
   1.230 +            cmd->data.color.b = b;
   1.231 +            cmd->data.color.a = a;
   1.232 +            retval = renderer->QueueSetDrawColor(renderer, cmd);
   1.233 +            if (retval < 0) {
   1.234 +                cmd->command = SDL_RENDERCMD_NO_OP;
   1.235 +            } else {
   1.236 +                renderer->last_queued_color = color;
   1.237 +                renderer->color_queued = SDL_TRUE;
   1.238 +            }
   1.239 +        }
   1.240 +    }
   1.241 +    return retval < 0 ? retval : FlushRenderCommandsIfNotBatching(renderer);
   1.242  }
   1.243  
   1.244  static int
   1.245 @@ -240,6 +378,7 @@
   1.246      }
   1.247  
   1.248      cmd->command = SDL_RENDERCMD_CLEAR;
   1.249 +    cmd->data.color.first = 0;
   1.250      cmd->data.color.r = renderer->r;
   1.251      cmd->data.color.g = renderer->g;
   1.252      cmd->data.color.b = renderer->b;
   1.253 @@ -247,20 +386,39 @@
   1.254      return FlushRenderCommandsIfNotBatching(renderer);
   1.255  }
   1.256  
   1.257 +static int
   1.258 +PrepQueueCmdDraw(SDL_Renderer *renderer, const Uint8 r, const Uint8 g, const Uint8 b, const Uint8 a)
   1.259 +{
   1.260 +    int retval = 0;
   1.261 +    if (retval == 0) {
   1.262 +        retval = QueueCmdSetDrawColor(renderer, r, g, b, a);
   1.263 +    }
   1.264 +    if (retval == 0) {
   1.265 +        retval = QueueCmdSetViewport(renderer);
   1.266 +    }
   1.267 +    if (retval == 0) {
   1.268 +        retval = QueueCmdSetClipRect(renderer);
   1.269 +    }
   1.270 +    return retval;
   1.271 +}
   1.272 +
   1.273  static SDL_RenderCommand *
   1.274  PrepQueueCmdDrawSolid(SDL_Renderer *renderer, const SDL_RenderCommandType cmdtype)
   1.275  {
   1.276 -    SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
   1.277 -    if (cmd != NULL) {
   1.278 -        cmd->command = cmdtype;
   1.279 -        cmd->data.draw.first = 0;  /* render backend will fill this in. */
   1.280 -        cmd->data.draw.count = 0;  /* render backend will fill this in. */
   1.281 -        cmd->data.draw.r = renderer->r;
   1.282 -        cmd->data.draw.g = renderer->g;
   1.283 -        cmd->data.draw.b = renderer->b;
   1.284 -        cmd->data.draw.a = renderer->a;
   1.285 -        cmd->data.draw.blend = renderer->blendMode;
   1.286 -        cmd->data.draw.texture = NULL;  /* no texture. */
   1.287 +    SDL_RenderCommand *cmd = NULL;
   1.288 +    if (PrepQueueCmdDraw(renderer, renderer->r, renderer->g, renderer->b, renderer->a) == 0) {
   1.289 +        cmd = AllocateRenderCommand(renderer);
   1.290 +        if (cmd != NULL) {
   1.291 +            cmd->command = cmdtype;
   1.292 +            cmd->data.draw.first = 0;  /* render backend will fill this in. */
   1.293 +            cmd->data.draw.count = 0;  /* render backend will fill this in. */
   1.294 +            cmd->data.draw.r = renderer->r;
   1.295 +            cmd->data.draw.g = renderer->g;
   1.296 +            cmd->data.draw.b = renderer->b;
   1.297 +            cmd->data.draw.a = renderer->a;
   1.298 +            cmd->data.draw.blend = renderer->blendMode;
   1.299 +            cmd->data.draw.texture = NULL;  /* no texture. */
   1.300 +        }
   1.301      }
   1.302      return cmd;
   1.303  }
   1.304 @@ -310,17 +468,20 @@
   1.305  static SDL_RenderCommand *
   1.306  PrepQueueCmdDrawTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_RenderCommandType cmdtype)
   1.307  {
   1.308 -    SDL_RenderCommand *cmd = AllocateRenderCommand(renderer);
   1.309 -    if (cmd != NULL) {
   1.310 -        cmd->command = cmdtype;
   1.311 -        cmd->data.draw.first = 0;  /* render backend will fill this in. */
   1.312 -        cmd->data.draw.count = 0;  /* render backend will fill this in. */
   1.313 -        cmd->data.draw.r = texture->r;
   1.314 -        cmd->data.draw.g = texture->g;
   1.315 -        cmd->data.draw.b = texture->b;
   1.316 -        cmd->data.draw.a = texture->a;
   1.317 -        cmd->data.draw.blend = texture->blendMode;
   1.318 -        cmd->data.draw.texture = texture;
   1.319 +    SDL_RenderCommand *cmd = NULL;
   1.320 +    if (PrepQueueCmdDraw(renderer, texture->r, texture->g, texture->b, texture->a) == 0) {
   1.321 +        cmd = AllocateRenderCommand(renderer);
   1.322 +        if (cmd != NULL) {
   1.323 +            cmd->command = cmdtype;
   1.324 +            cmd->data.draw.first = 0;  /* render backend will fill this in. */
   1.325 +            cmd->data.draw.count = 0;  /* render backend will fill this in. */
   1.326 +            cmd->data.draw.r = texture->r;
   1.327 +            cmd->data.draw.g = texture->g;
   1.328 +            cmd->data.draw.b = texture->b;
   1.329 +            cmd->data.draw.a = texture->a;
   1.330 +            cmd->data.draw.blend = texture->blendMode;
   1.331 +            cmd->data.draw.texture = texture;
   1.332 +        }
   1.333      }
   1.334      return cmd;
   1.335  }
   1.336 @@ -435,7 +596,7 @@
   1.337                          renderer->viewport.y = 0;
   1.338                          renderer->viewport.w = w;
   1.339                          renderer->viewport.h = h;
   1.340 -                        QueueCmdUpdateViewport(renderer);
   1.341 +                        QueueCmdSetViewport(renderer);
   1.342                      }
   1.343                  }
   1.344  
   1.345 @@ -557,6 +718,8 @@
   1.346  {
   1.347      /* all of these functions are required to be implemented, even as no-ops, so we don't
   1.348          have to check that they aren't NULL over and over. */
   1.349 +    SDL_assert(renderer->QueueSetViewport != NULL);
   1.350 +    SDL_assert(renderer->QueueSetDrawColor != NULL);
   1.351      SDL_assert(renderer->QueueDrawPoints != NULL);
   1.352      SDL_assert(renderer->QueueDrawLines != NULL);
   1.353      SDL_assert(renderer->QueueFillRects != NULL);
   1.354 @@ -641,7 +804,9 @@
   1.355          VerifyDrawQueueFunctions(renderer);
   1.356  
   1.357          /* let app/user override batching decisions. */
   1.358 -        if (SDL_GetHint(SDL_HINT_RENDER_BATCHING)) {
   1.359 +        if (renderer->always_batch) {
   1.360 +            batching = SDL_TRUE;
   1.361 +        } else if (SDL_GetHint(SDL_HINT_RENDER_BATCHING)) {
   1.362              batching = SDL_GetHintBoolean(SDL_HINT_RENDER_BATCHING, SDL_TRUE);
   1.363          }
   1.364  
   1.365 @@ -1557,10 +1722,10 @@
   1.366  
   1.367      SDL_UnlockMutex(renderer->target_mutex);
   1.368  
   1.369 -    if (QueueCmdUpdateViewport(renderer) < 0) {
   1.370 +    if (QueueCmdSetViewport(renderer) < 0) {
   1.371          return -1;
   1.372      }
   1.373 -    if (QueueCmdUpdateClipRect(renderer) < 0) {
   1.374 +    if (QueueCmdSetClipRect(renderer) < 0) {
   1.375          return -1;
   1.376      }
   1.377  
   1.378 @@ -1751,7 +1916,7 @@
   1.379              return -1;
   1.380          }
   1.381      }
   1.382 -    return QueueCmdUpdateViewport(renderer);
   1.383 +    return QueueCmdSetViewport(renderer);
   1.384  }
   1.385  
   1.386  void
   1.387 @@ -1782,7 +1947,7 @@
   1.388          renderer->clipping_enabled = SDL_FALSE;
   1.389          SDL_zero(renderer->clip_rect);
   1.390      }
   1.391 -    return QueueCmdUpdateClipRect(renderer);
   1.392 +    return QueueCmdSetClipRect(renderer);
   1.393  }
   1.394  
   1.395  void
   1.396 @@ -2412,6 +2577,8 @@
   1.397  SDL_DestroyRenderer(SDL_Renderer * renderer)
   1.398  {
   1.399      SDL_RenderCommand *cmd;
   1.400 +    SDL_AllocVertGap *gap;
   1.401 +    SDL_AllocVertGap *nextgap;
   1.402  
   1.403      CHECK_RENDERER_MAGIC(renderer, );
   1.404  
   1.405 @@ -2436,6 +2603,16 @@
   1.406  
   1.407      SDL_free(renderer->vertex_data);
   1.408  
   1.409 +    for (gap = renderer->vertex_data_gaps.next; gap; gap = nextgap) {
   1.410 +        nextgap = gap->next;
   1.411 +        SDL_free(gap);
   1.412 +    }
   1.413 +
   1.414 +    for (gap = renderer->vertex_data_gaps_pool; gap; gap = nextgap) {
   1.415 +        nextgap = gap->next;
   1.416 +        SDL_free(gap);
   1.417 +    }
   1.418 +
   1.419      /* Free existing textures for this renderer */
   1.420      while (renderer->textures) {
   1.421          SDL_Texture *tex = renderer->textures; (void) tex;
     2.1 --- a/src/render/SDL_sysrender.h	Thu Sep 20 16:40:04 2018 -0400
     2.2 +++ b/src/render/SDL_sysrender.h	Sun Sep 23 23:20:40 2018 -0400
     2.3 @@ -88,6 +88,7 @@
     2.4      SDL_RENDERCMD_NO_OP,
     2.5      SDL_RENDERCMD_SETVIEWPORT,
     2.6      SDL_RENDERCMD_SETCLIPRECT,
     2.7 +    SDL_RENDERCMD_SETDRAWCOLOR,
     2.8      SDL_RENDERCMD_CLEAR,
     2.9      SDL_RENDERCMD_DRAW_POINTS,
    2.10      SDL_RENDERCMD_DRAW_LINES,
    2.11 @@ -100,7 +101,10 @@
    2.12  {
    2.13      SDL_RenderCommandType command;
    2.14      union {
    2.15 -        SDL_Rect viewport;
    2.16 +        struct {
    2.17 +            size_t first;
    2.18 +            SDL_Rect rect;
    2.19 +        } viewport;
    2.20          struct {
    2.21              SDL_bool enabled;
    2.22              SDL_Rect rect;
    2.23 @@ -113,12 +117,20 @@
    2.24              SDL_Texture *texture;
    2.25          } draw;
    2.26          struct {
    2.27 +            size_t first;
    2.28              Uint8 r, g, b, a;
    2.29          } color;
    2.30      } data;
    2.31      struct SDL_RenderCommand *next;
    2.32  } SDL_RenderCommand;
    2.33  
    2.34 +typedef struct SDL_AllocVertGap
    2.35 +{
    2.36 +    size_t offset;
    2.37 +    size_t len;
    2.38 +    struct SDL_AllocVertGap *next;
    2.39 +} SDL_AllocVertGap;
    2.40 +
    2.41  
    2.42  /* Define the SDL renderer structure */
    2.43  struct SDL_Renderer
    2.44 @@ -129,6 +141,8 @@
    2.45      int (*GetOutputSize) (SDL_Renderer * renderer, int *w, int *h);
    2.46      SDL_bool (*SupportsBlendMode)(SDL_Renderer * renderer, SDL_BlendMode blendMode);
    2.47      int (*CreateTexture) (SDL_Renderer * renderer, SDL_Texture * texture);
    2.48 +    int (*QueueSetViewport) (SDL_Renderer * renderer, SDL_RenderCommand *cmd);
    2.49 +    int (*QueueSetDrawColor) (SDL_Renderer * renderer, SDL_RenderCommand *cmd);
    2.50      int (*QueueDrawPoints) (SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points,
    2.51                               int count);
    2.52      int (*QueueDrawLines) (SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points,
    2.53 @@ -209,15 +223,25 @@
    2.54      Uint8 r, g, b, a;                   /**< Color for drawing operations values */
    2.55      SDL_BlendMode blendMode;            /**< The drawing blend mode */
    2.56  
    2.57 +    SDL_bool always_batch;
    2.58      SDL_bool batching;
    2.59      SDL_RenderCommand *render_commands;
    2.60      SDL_RenderCommand *render_commands_tail;
    2.61      SDL_RenderCommand *render_commands_pool;
    2.62      Uint32 render_command_generation;
    2.63 +    Uint32 last_queued_color;
    2.64 +    SDL_Rect last_queued_viewport;
    2.65 +    SDL_Rect last_queued_cliprect;
    2.66 +    SDL_bool last_queued_cliprect_enabled;
    2.67 +    SDL_bool color_queued;
    2.68 +    SDL_bool viewport_queued;
    2.69 +    SDL_bool cliprect_queued;
    2.70  
    2.71      void *vertex_data;
    2.72      size_t vertex_data_used;
    2.73      size_t vertex_data_allocation;
    2.74 +    SDL_AllocVertGap vertex_data_gaps;
    2.75 +    SDL_AllocVertGap *vertex_data_gaps_pool;
    2.76  
    2.77      void *driverdata;
    2.78  };
    2.79 @@ -253,7 +277,7 @@
    2.80  /* drivers call this during their Queue*() methods to make space in a array that are used
    2.81     for a vertex buffer during RunCommandQueue(). Pointers returned here are only valid until
    2.82     the next call, because it might be in an array that gets realloc()'d. */
    2.83 -extern void *SDL_AllocateRenderVertices(SDL_Renderer *renderer, const size_t numbytes, size_t *offset);
    2.84 +extern void *SDL_AllocateRenderVertices(SDL_Renderer *renderer, const size_t numbytes, const size_t alignment, size_t *offset);
    2.85  
    2.86  #endif /* SDL_sysrender_h_ */
    2.87