From 5bed8ec60396117e95589128ef8909486112cd84 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Tue, 11 Nov 2025 08:01:18 -0800 Subject: [PATCH] Implemented render batching for D3D9 --- src/render/direct3d/SDL_render_d3d.c | 146 ++++++++++++++++++++------- 1 file changed, 108 insertions(+), 38 deletions(-) diff --git a/src/render/direct3d/SDL_render_d3d.c b/src/render/direct3d/SDL_render_d3d.c index 89b3a1fac6..3badaddaca 100644 --- a/src/render/direct3d/SDL_render_d3d.c +++ b/src/render/direct3d/SDL_render_d3d.c @@ -1313,10 +1313,7 @@ static bool D3D_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, switch (cmd->command) { case SDL_RENDERCMD_SETDRAWCOLOR: { - /* currently this is sent with each vertex, but if we move to - shaders, we can put this in a uniform here and reduce vertex - buffer bandwidth */ - break; + break; // this isn't currently used in this render backend. } case SDL_RENDERCMD_SETVIEWPORT: @@ -1377,43 +1374,74 @@ static bool D3D_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, break; } - case SDL_RENDERCMD_DRAW_POINTS: - { - const size_t count = cmd->data.draw.count; - const size_t first = cmd->data.draw.first; - SetDrawState(data, cmd); - if (vbo) { - IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_POINTLIST, (UINT)(first / sizeof(Vertex)), (UINT)count); - } else { - const Vertex *verts = (Vertex *)(((Uint8 *)vertices) + first); - IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_POINTLIST, (UINT)count, verts, sizeof(Vertex)); - } - break; - } - case SDL_RENDERCMD_DRAW_LINES: { - const size_t count = cmd->data.draw.count; + size_t count = cmd->data.draw.count; const size_t first = cmd->data.draw.first; + const size_t start = first / sizeof(Vertex); const Vertex *verts = (Vertex *)(((Uint8 *)vertices) + first); - /* DirectX 9 has the same line rasterization semantics as GDI, - so we need to close the endpoint of the line with a second draw call. - NOLINTNEXTLINE(clang-analyzer-core.NullDereference): FIXME: Can verts truly not be NULL ? */ - const bool close_endpoint = ((count == 2) || (verts[0].x != verts[count - 1].x) || (verts[0].y != verts[count - 1].y)); - SetDrawState(data, cmd); - if (vbo) { - IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_LINESTRIP, (UINT)(first / sizeof(Vertex)), (UINT)(count - 1)); - if (close_endpoint) { - IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_POINTLIST, (UINT)((first / sizeof(Vertex)) + (count - 1)), 1); + // Add the final point in the line + size_t line_start = 0; + size_t line_end = line_start + count - 1; + if (count == 2 || verts[line_start].x != verts[line_end].x || verts[line_start].y != verts[line_end].y) { + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_POINTLIST, (UINT)(start + line_end), 1); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_POINTLIST, 1, &verts[line_end], sizeof(Vertex)); + } + } + + if (count > 2) { + // joined lines cannot be grouped + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_LINESTRIP, (UINT)start, (UINT)(count - 1)); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_LINESTRIP, (UINT)(count - 1), verts, sizeof(Vertex)); } } else { - IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_LINESTRIP, (UINT)(count - 1), verts, sizeof(Vertex)); - if (close_endpoint) { - IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_POINTLIST, 1, &verts[count - 1], sizeof(Vertex)); + // let's group non joined lines + SDL_RenderCommand *finalcmd = cmd; + SDL_RenderCommand *nextcmd; + SDL_BlendMode thisblend = cmd->data.draw.blend; + + for (nextcmd = cmd->next; nextcmd; nextcmd = nextcmd->next) { + const SDL_RenderCommandType nextcmdtype = nextcmd->command; + if (nextcmdtype != SDL_RENDERCMD_DRAW_LINES) { + if (nextcmdtype == SDL_RENDERCMD_SETDRAWCOLOR) { + // The vertex data has the draw color built in, ignore this + continue; + } + 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. + + // Add the final point in the line + line_start = count; + line_end = line_start + nextcmd->data.draw.count - 1; + if (verts[line_start].x != verts[line_end].x || verts[line_start].y != verts[line_end].y) { + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_POINTLIST, (UINT)(start + line_end), 1); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_POINTLIST, 1, &verts[line_end], sizeof(Vertex)); + } + } + count += nextcmd->data.draw.count; + } } + + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_LINELIST, (UINT)start, (UINT)(count - 1)); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_LINELIST, (UINT)(count - 1), verts, sizeof(Vertex)); + } + cmd = finalcmd; // skip any copy commands we just combined in here. } break; } @@ -1427,17 +1455,59 @@ static bool D3D_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, case SDL_RENDERCMD_COPY_EX: // unused break; + case SDL_RENDERCMD_DRAW_POINTS: case SDL_RENDERCMD_GEOMETRY: { - 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; + SDL_ScaleMode thisscalemode = cmd->data.draw.texture_scale_mode; + SDL_TextureAddressMode thisaddressmode_u = cmd->data.draw.texture_address_mode_u; + SDL_TextureAddressMode thisaddressmode_v = cmd->data.draw.texture_address_mode_v; + const SDL_RenderCommandType thiscmdtype = cmd->command; + SDL_RenderCommand *finalcmd = cmd; + SDL_RenderCommand *nextcmd; + size_t count = cmd->data.draw.count; const size_t first = cmd->data.draw.first; - SetDrawState(data, cmd); - if (vbo) { - IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_TRIANGLELIST, (UINT)(first / sizeof(Vertex)), (UINT)count / 3); - } else { - const Vertex *verts = (Vertex *)(((Uint8 *)vertices) + first); - IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_TRIANGLELIST, (UINT)count / 3, verts, sizeof(Vertex)); + const size_t start = first / sizeof(Vertex); + const Vertex *verts = (Vertex *)(((Uint8 *)vertices) + first); + for (nextcmd = cmd->next; nextcmd; nextcmd = nextcmd->next) { + const SDL_RenderCommandType nextcmdtype = nextcmd->command; + if (nextcmdtype != thiscmdtype) { + if (nextcmdtype == SDL_RENDERCMD_SETDRAWCOLOR) { + // The vertex data has the draw color built in, ignore this + continue; + } + 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.texture_scale_mode != thisscalemode || + nextcmd->data.draw.texture_address_mode_u != thisaddressmode_u || + nextcmd->data.draw.texture_address_mode_v != thisaddressmode_v || + 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; + } } + + SetDrawState(data, cmd); + + if (thiscmdtype == SDL_RENDERCMD_GEOMETRY) { + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_TRIANGLELIST, (UINT)start, (UINT)count / 3); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_TRIANGLELIST, (UINT)count / 3, verts, sizeof(Vertex)); + } + } else { + if (vbo) { + IDirect3DDevice9_DrawPrimitive(data->device, D3DPT_POINTLIST, (UINT)start, (UINT)count); + } else { + IDirect3DDevice9_DrawPrimitiveUP(data->device, D3DPT_POINTLIST, (UINT)count, verts, sizeof(Vertex)); + } + } + cmd = finalcmd; // skip any copy commands we just combined in here. break; }