From d9be8b9a008770eb1870f208d4b7bf657b95d656 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sun, 9 Nov 2025 11:16:47 -0800 Subject: [PATCH] Implemented render batching for D3D11, D3D12, Metal, and Vulkan Fixes https://github.com/libsdl-org/SDL/issues/7534 --- src/render/direct3d11/SDL_render_d3d11.c | 102 +++++++++++++++---- src/render/direct3d12/SDL_render_d3d12.c | 119 +++++++++++++++++----- src/render/metal/SDL_render_metal.m | 91 ++++++++++++++--- src/render/vulkan/SDL_render_vulkan.c | 120 ++++++++++++++++++----- 4 files changed, 353 insertions(+), 79 deletions(-) diff --git a/src/render/direct3d11/SDL_render_d3d11.c b/src/render/direct3d11/SDL_render_d3d11.c index 1cfafe5453..7788ae7481 100644 --- a/src/render/direct3d11/SDL_render_d3d11.c +++ b/src/render/direct3d11/SDL_render_d3d11.c @@ -2623,26 +2623,58 @@ static bool D3D11_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; - const size_t start = first / sizeof(D3D11_VertexPositionColor); - D3D11_SetDrawState(renderer, cmd, NULL, 0, NULL, 0, NULL, NULL); - D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, start, count); - 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(D3D11_VertexPositionColor); const D3D11_VertexPositionColor *verts = (D3D11_VertexPositionColor *)(((Uint8 *)vertices) + first); + D3D11_SetDrawState(renderer, cmd, NULL, 0, NULL, 0, NULL, NULL); - D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP, start, count); - if (verts[0].pos.x != verts[count - 1].pos.x || verts[0].pos.y != verts[count - 1].pos.y) { - D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, start + (count - 1), 1); + + // Add the final point in the line + size_t line_start = 0; + size_t line_end = line_start + count - 1; + if (verts[line_start].pos.x != verts[line_end].pos.x || verts[line_start].pos.y != verts[line_end].pos.y) { + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, start + line_end, 1); + } + + if (count > 2) { + // joined lines cannot be grouped + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP, start, count); + } else { + // 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].pos.x != verts[line_end].pos.x || verts[line_start].pos.y != verts[line_end].pos.y) { + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, start + line_end, 1); + } + count += nextcmd->data.draw.count; + } + } + + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_LINELIST, start, count); + cmd = finalcmd; // skip any copy commands we just combined in here. } break; } @@ -2656,20 +2688,54 @@ static bool D3D11_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd case SDL_RENDERCMD_COPY_EX: // unused break; + case SDL_RENDERCMD_DRAW_POINTS: case SDL_RENDERCMD_GEOMETRY: { - SDL_Texture *texture = cmd->data.draw.texture; - const size_t count = cmd->data.draw.count; + /* as long as we have the same copy command in a row, with the + same texture, we can combine them all into a single draw call. */ + SDL_Texture *thistexture = cmd->data.draw.texture; + SDL_BlendMode thisblend = cmd->data.draw.blend; + 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; const size_t start = first / sizeof(D3D11_VertexPositionColor); + 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; + } + } - if (texture) { + if (thistexture) { D3D11_SetCopyState(renderer, cmd, NULL); } else { D3D11_SetDrawState(renderer, cmd, NULL, 0, NULL, 0, NULL, NULL); } - D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST, start, count); + if (thiscmdtype == SDL_RENDERCMD_GEOMETRY) { + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST, start, count); + } else { + D3D11_DrawPrimitives(renderer, D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, start, count); + } + cmd = finalcmd; // skip any copy commands we just combined in here. break; } diff --git a/src/render/direct3d12/SDL_render_d3d12.c b/src/render/direct3d12/SDL_render_d3d12.c index c34798489b..1e1951a180 100644 --- a/src/render/direct3d12/SDL_render_d3d12.c +++ b/src/render/direct3d12/SDL_render_d3d12.c @@ -3084,26 +3084,65 @@ static bool D3D12_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; - const size_t start = first / sizeof(D3D12_VertexPositionColor); - D3D12_SetDrawState(renderer, cmd, NULL, D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT, 0, NULL, 0, NULL); - D3D12_DrawPrimitives(renderer, D3D_PRIMITIVE_TOPOLOGY_POINTLIST, start, count); - 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(D3D12_VertexPositionColor); const D3D12_VertexPositionColor *verts = (D3D12_VertexPositionColor *)(((Uint8 *)vertices) + first); - D3D12_SetDrawState(renderer, cmd, NULL, D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE, 0, NULL, 0, NULL); - D3D12_DrawPrimitives(renderer, D3D_PRIMITIVE_TOPOLOGY_LINESTRIP, start, count); - if (verts[0].pos.x != verts[count - 1].pos.x || verts[0].pos.y != verts[count - 1].pos.y) { - D3D12_DrawPrimitives(renderer, D3D_PRIMITIVE_TOPOLOGY_POINTLIST, start + (count - 1), 1); + bool have_point_draw_state = false; + + // Add the final point in the line + size_t line_start = 0; + size_t line_end = line_start + count - 1; + if (verts[line_start].pos.x != verts[line_end].pos.x || verts[line_start].pos.y != verts[line_end].pos.y) { + D3D12_SetDrawState(renderer, cmd, NULL, D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT, 0, NULL, 0, NULL); + D3D12_DrawPrimitives(renderer, D3D_PRIMITIVE_TOPOLOGY_POINTLIST, start + line_end, 1); + have_point_draw_state = true; + } + + if (count > 2) { + // joined lines cannot be grouped + D3D12_SetDrawState(renderer, cmd, NULL, D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE, 0, NULL, 0, NULL); + D3D12_DrawPrimitives(renderer, D3D_PRIMITIVE_TOPOLOGY_LINESTRIP, start, count); + } else { + // 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].pos.x != verts[line_end].pos.x || verts[line_start].pos.y != verts[line_end].pos.y) { + if (!have_point_draw_state) { + D3D12_SetDrawState(renderer, cmd, NULL, D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT, 0, NULL, 0, NULL); + have_point_draw_state = true; + } + D3D12_DrawPrimitives(renderer, D3D_PRIMITIVE_TOPOLOGY_POINTLIST, start + line_end, 1); + } + count += nextcmd->data.draw.count; + } + } + + D3D12_SetDrawState(renderer, cmd, NULL, D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE, 0, NULL, 0, NULL); + D3D12_DrawPrimitives(renderer, D3D_PRIMITIVE_TOPOLOGY_LINELIST, start, count); + cmd = finalcmd; // skip any copy commands we just combined in here. } break; } @@ -3117,20 +3156,54 @@ static bool D3D12_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd case SDL_RENDERCMD_COPY_EX: // unused break; + case SDL_RENDERCMD_DRAW_POINTS: case SDL_RENDERCMD_GEOMETRY: { - SDL_Texture *texture = cmd->data.draw.texture; - const size_t count = cmd->data.draw.count; + /* as long as we have the same copy command in a row, with the + same texture, we can combine them all into a single draw call. */ + SDL_Texture *thistexture = cmd->data.draw.texture; + SDL_BlendMode thisblend = cmd->data.draw.blend; + 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; const size_t start = first / sizeof(D3D12_VertexPositionColor); - - if (texture) { - D3D12_SetCopyState(renderer, cmd); - } else { - D3D12_SetDrawState(renderer, cmd, NULL, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, 0, NULL, 0, NULL); + 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; + } } - D3D12_DrawPrimitives(renderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST, start, count); + if (thiscmdtype == SDL_RENDERCMD_GEOMETRY) { + if (thistexture) { + D3D12_SetCopyState(renderer, cmd); + } else { + D3D12_SetDrawState(renderer, cmd, NULL, D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, 0, NULL, 0, NULL); + } + D3D12_DrawPrimitives(renderer, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST, start, count); + } else { + D3D12_SetDrawState(renderer, cmd, NULL, D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT, 0, NULL, 0, NULL); + D3D12_DrawPrimitives(renderer, D3D_PRIMITIVE_TOPOLOGY_POINTLIST, start, count); + } + cmd = finalcmd; // skip any copy commands we just combined in here. break; } diff --git a/src/render/metal/SDL_render_metal.m b/src/render/metal/SDL_render_metal.m index 5b2f57a533..bd1aaf9d69 100644 --- a/src/render/metal/SDL_render_metal.m +++ b/src/render/metal/SDL_render_metal.m @@ -1250,7 +1250,7 @@ static bool METAL_QueueDrawLines(SDL_Renderer *renderer, SDL_RenderCommand *cmd, angles. Maybe !!! FIXME for later, though. */ points -= 2; // update the last line. - verts -= 2 + 1; + verts -= 6; { const float xstart = points[0].x; @@ -1784,13 +1784,40 @@ static bool METAL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd break; } - case SDL_RENDERCMD_DRAW_POINTS: case SDL_RENDERCMD_DRAW_LINES: { - const size_t count = cmd->data.draw.count; - const MTLPrimitiveType primtype = (cmd->command == SDL_RENDERCMD_DRAW_POINTS) ? MTLPrimitiveTypePoint : MTLPrimitiveTypeLineStrip; if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, NULL, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache)) { - [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count]; + size_t count = cmd->data.draw.count; + if (count > 2) { + // joined lines cannot be grouped + [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeLineStrip vertexStart:0 vertexCount:count]; + } else { + // 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. + count += nextcmd->data.draw.count; + } + } + + [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeLine vertexStart:0 vertexCount:count]; + cmd = finalcmd; // skip any copy commands we just combined in here. + } } break; } @@ -1804,20 +1831,54 @@ static bool METAL_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; - SDL_Texture *texture = cmd->data.draw.texture; - - if (texture) { - if (SetCopyState(renderer, cmd, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache)) { - [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:count]; - } - } else { - if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, NULL, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache)) { - [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:count]; + 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; + 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; } } + + if (thiscmdtype == SDL_RENDERCMD_GEOMETRY) { + if (thistexture) { + if (SetCopyState(renderer, cmd, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache)) { + [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:count]; + } + } else { + if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, NULL, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache)) { + [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:count]; + } + } + } else { + if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, NULL, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache)) { + [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypePoint vertexStart:0 vertexCount:count]; + } + } + cmd = finalcmd; // skip any copy commands we just combined in here. break; } diff --git a/src/render/vulkan/SDL_render_vulkan.c b/src/render/vulkan/SDL_render_vulkan.c index 0793ce4771..e4c53972ce 100644 --- a/src/render/vulkan/SDL_render_vulkan.c +++ b/src/render/vulkan/SDL_render_vulkan.c @@ -4068,27 +4068,66 @@ static bool VULKAN_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cm break; } - case SDL_RENDERCMD_DRAW_POINTS: - { - const size_t count = cmd->data.draw.count; - const size_t first = cmd->data.draw.first; - const size_t start = first / sizeof(VULKAN_VertexPositionColor); - VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_POINT_LIST, 0, NULL, 0, NULL, NULL, &stateCache); - VULKAN_DrawPrimitives(renderer, VK_PRIMITIVE_TOPOLOGY_POINT_LIST, start, count); - 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(VULKAN_VertexPositionColor); const VULKAN_VertexPositionColor *verts = (VULKAN_VertexPositionColor *)(((Uint8 *)vertices) + first); - VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, 0, NULL, 0, NULL, NULL, &stateCache); - VULKAN_DrawPrimitives(renderer, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, start, count); - if (verts[0].pos[0] != verts[count - 1].pos[0] || verts[0].pos[1] != verts[count - 1].pos[1]) { + bool have_point_draw_state = false; + + // Add the final point in the line + size_t line_start = 0; + size_t line_end = line_start + count - 1; + if (verts[line_start].pos[0] != verts[line_end].pos[0] || verts[line_start].pos[1] != verts[line_end].pos[1]) { VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_POINT_LIST, 0, NULL, 0, NULL, NULL, &stateCache); - VULKAN_DrawPrimitives(renderer, VK_PRIMITIVE_TOPOLOGY_POINT_LIST, start + (count - 1), 1); + VULKAN_DrawPrimitives(renderer, VK_PRIMITIVE_TOPOLOGY_POINT_LIST, start + line_end, 1); + have_point_draw_state = true; + } + + if (count > 2) { + // joined lines cannot be grouped + VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, 0, NULL, 0, NULL, NULL, &stateCache); + VULKAN_DrawPrimitives(renderer, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, start, count); + } else { + // 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].pos[0] != verts[line_end].pos[0] || verts[line_start].pos[1] != verts[line_end].pos[1]) { + if (!have_point_draw_state) { + VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_POINT_LIST, 0, NULL, 0, NULL, NULL, &stateCache); + have_point_draw_state = true; + } + VULKAN_DrawPrimitives(renderer, VK_PRIMITIVE_TOPOLOGY_POINT_LIST, start + line_end, 1); + } + count += nextcmd->data.draw.count; + } + } + + VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_LINE_LIST, 0, NULL, 0, NULL, NULL, &stateCache); + VULKAN_DrawPrimitives(renderer, VK_PRIMITIVE_TOPOLOGY_LINE_LIST, start, count); + + cmd = finalcmd; // skip any copy commands we just combined in here. } break; } @@ -4102,20 +4141,55 @@ static bool VULKAN_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cm case SDL_RENDERCMD_COPY_EX: // unused break; + case SDL_RENDERCMD_DRAW_POINTS: case SDL_RENDERCMD_GEOMETRY: { - SDL_Texture *texture = cmd->data.draw.texture; - const size_t count = cmd->data.draw.count; + /* as long as we have the same copy command in a row, with the + same texture, we can combine them all into a single draw call. */ + SDL_Texture *thistexture = cmd->data.draw.texture; + SDL_BlendMode thisblend = cmd->data.draw.blend; + 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; const size_t start = first / sizeof(VULKAN_VertexPositionColor); - - if (texture) { - VULKAN_SetCopyState(renderer, cmd, NULL, &stateCache); - } else { - VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, NULL, 0, NULL, NULL, &stateCache); + 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; + } } - VULKAN_DrawPrimitives(renderer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, start, count); + if (thiscmdtype == SDL_RENDERCMD_GEOMETRY) { + if (thistexture) { + VULKAN_SetCopyState(renderer, cmd, NULL, &stateCache); + } else { + VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, NULL, 0, NULL, NULL, &stateCache); + } + + VULKAN_DrawPrimitives(renderer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, start, count); + } else { + VULKAN_SetDrawState(renderer, cmd, rendererData->pipelineLayout, rendererData->descriptorSetLayout, NULL, VK_PRIMITIVE_TOPOLOGY_POINT_LIST, 0, NULL, 0, NULL, NULL, &stateCache); + VULKAN_DrawPrimitives(renderer, VK_PRIMITIVE_TOPOLOGY_POINT_LIST, start, count); + } + cmd = finalcmd; // skip any copy commands we just combined in here. break; }