summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan C. Gordon <icculus@icculus.org>2018-09-20 15:46:02 -0400
committerRyan C. Gordon <icculus@icculus.org>2018-09-20 15:46:02 -0400
commitecf8c4239b457ebca3b638c71a710b90be7b125d (patch)
treec4c10f3fa396a4696bacfe6ca7d1377986c0266f
parentbc9aeec4513f9d8189e89c7179bf01a7faf57972 (diff)
downloadsdl-ecf8c4239b457ebca3b638c71a710b90be7b125d.tar.gz
render: Move to a batching system for rendering (work in progress).
-rw-r--r--include/SDL_hints.h25
-rw-r--r--src/render/SDL_render.c414
-rw-r--r--src/render/SDL_sysrender.h86
3 files changed, 455 insertions, 70 deletions
diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index 07a911338..b3c0c154f 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -1030,6 +1030,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
*/
typedef enum
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index b1bcc46c1..3d28360fc 100644
--- a/src/render/SDL_render.c
+++ b/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;
diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h
index 940bebcc1..32b0aab49 100644
--- a/src/render/SDL_sysrender.h
+++ b/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: */