diff --git a/src/render/direct3d/SDL_render_d3d.c b/src/render/direct3d/SDL_render_d3d.c index 006e012fc..deb324c7d 100644 --- a/src/render/direct3d/SDL_render_d3d.c +++ b/src/render/direct3d/SDL_render_d3d.c @@ -1207,8 +1207,30 @@ D3D_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *verti break; } - case SDL_RENDERCMD_DRAW_LINES: /* unused */ + case SDL_RENDERCMD_DRAW_LINES: { + const size_t count = cmd->data.draw.count; + const size_t first = cmd->data.draw.first; + 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. */ + const SDL_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); + } + } 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)); + } + } break; + } case SDL_RENDERCMD_FILL_RECTS: /* unused */ break; @@ -1545,6 +1567,7 @@ D3D_CreateRenderer(SDL_Window * window, Uint32 flags) renderer->QueueSetViewport = D3D_QueueSetViewport; renderer->QueueSetDrawColor = D3D_QueueSetViewport; /* SetViewport and SetDrawColor are (currently) no-ops. */ renderer->QueueDrawPoints = D3D_QueueDrawPoints; + renderer->QueueDrawLines = D3D_QueueDrawPoints; /* lines and points queue vertices the same way. */ renderer->QueueGeometry = D3D_QueueGeometry; renderer->RunCommandQueue = D3D_RunCommandQueue; renderer->RenderReadPixels = D3D_RenderReadPixels; diff --git a/src/render/direct3d11/SDL_render_d3d11.c b/src/render/direct3d11/SDL_render_d3d11.c index fb047a24b..f8d8c7cf5 100644 --- a/src/render/direct3d11/SDL_render_d3d11.c +++ b/src/render/direct3d11/SDL_render_d3d11.c @@ -2122,8 +2122,18 @@ D3D11_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *ver break; } - case SDL_RENDERCMD_DRAW_LINES: /* unused */ + case SDL_RENDERCMD_DRAW_LINES: { + const size_t count = cmd->data.draw.count; + const size_t first = cmd->data.draw.first; + const size_t start = first / sizeof (VertexPositionColor); + const VertexPositionColor *verts = (VertexPositionColor *) (((Uint8 *) vertices) + first); + D3D11_SetDrawState(renderer, cmd, rendererData->pixelShaders[SHADER_SOLID], 0, NULL, 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); + } break; + } case SDL_RENDERCMD_FILL_RECTS: /* unused */ break; @@ -2376,6 +2386,7 @@ D3D11_CreateRenderer(SDL_Window * window, Uint32 flags) renderer->QueueSetViewport = D3D11_QueueSetViewport; renderer->QueueSetDrawColor = D3D11_QueueSetViewport; /* SetViewport and SetDrawColor are (currently) no-ops. */ renderer->QueueDrawPoints = D3D11_QueueDrawPoints; + renderer->QueueDrawLines = D3D11_QueueDrawPoints; /* lines and points queue vertices the same way. */ renderer->QueueGeometry = D3D11_QueueGeometry; renderer->RunCommandQueue = D3D11_RunCommandQueue; renderer->RenderReadPixels = D3D11_RenderReadPixels; diff --git a/src/render/metal/SDL_render_metal.m b/src/render/metal/SDL_render_metal.m index 742bd3134..e093fd4d8 100644 --- a/src/render/metal/SDL_render_metal.m +++ b/src/render/metal/SDL_render_metal.m @@ -1107,6 +1107,56 @@ METAL_QueueDrawPoints(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL return 0; } +static int +METAL_QueueDrawLines(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points, int count) +{ + const SDL_Color color = { + cmd->data.draw.r, + cmd->data.draw.g, + cmd->data.draw.b, + cmd->data.draw.a + }; + + SDL_assert(count >= 2); /* should have been checked at the higher level. */ + + const size_t vertlen = (2 * sizeof (float) + sizeof (SDL_Color)) * count; + float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, DEVICE_ALIGN(8), &cmd->data.draw.first); + if (!verts) { + return -1; + } + cmd->data.draw.count = count; + + for (int i = 0; i < count; i++, points++) { + *(verts++) = points->x; + *(verts++) = points->y; + *((SDL_Color *)verts++) = color; + } + + /* If the line segment is completely horizontal or vertical, + make it one pixel longer, to satisfy the diamond-exit rule. + We should probably do this for diagonal lines too, but we'd have to + do some trigonometry to figure out the correct pixel and generally + when we have problems with pixel perfection, it's for straight lines + that are missing a pixel that frames something and not arbitrary + angles. Maybe !!! FIXME for later, though. */ + + points -= 2; /* update the last line. */ + verts -= 2 + 1; + + const float xstart = points[0].x; + const float ystart = points[0].y; + const float xend = points[1].x; + const float yend = points[1].y; + + if (ystart == yend) { /* horizontal line */ + verts[0] += (xend > xstart) ? 1.0f : -1.0f; + } else if (xstart == xend) { /* vertical line */ + verts[1] += (yend > ystart) ? 1.0f : -1.0f; + } + + return 0; +} + static int METAL_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, const float *xy, int xy_stride, const SDL_Color *color, int color_stride, const float *uv, int uv_stride, @@ -1372,17 +1422,15 @@ METAL_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *ver break; } - case SDL_RENDERCMD_DRAW_POINTS: { + case SDL_RENDERCMD_DRAW_POINTS: + case SDL_RENDERCMD_DRAW_LINES: { const size_t count = cmd->data.draw.count; - const MTLPrimitiveType primtype = MTLPrimitiveTypePoint; + const MTLPrimitiveType primtype = (cmd->command == SDL_RENDERCMD_DRAW_POINTS) ? MTLPrimitiveTypePoint : MTLPrimitiveTypeLineStrip; if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache)) { [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count]; } break; } - - case SDL_RENDERCMD_DRAW_LINES: /* unused */ - break; case SDL_RENDERCMD_FILL_RECTS: /* unused */ break; @@ -1838,6 +1886,7 @@ METAL_CreateRenderer(SDL_Window * window, Uint32 flags) renderer->QueueSetViewport = METAL_QueueSetViewport; renderer->QueueSetDrawColor = METAL_QueueSetDrawColor; renderer->QueueDrawPoints = METAL_QueueDrawPoints; + renderer->QueueDrawLines = METAL_QueueDrawLines; renderer->QueueGeometry = METAL_QueueGeometry; renderer->RunCommandQueue = METAL_RunCommandQueue; renderer->RenderReadPixels = METAL_RenderReadPixels; diff --git a/src/render/opengl/SDL_render_gl.c b/src/render/opengl/SDL_render_gl.c index 624329995..3b3d739f6 100644 --- a/src/render/opengl/SDL_render_gl.c +++ b/src/render/opengl/SDL_render_gl.c @@ -941,6 +941,47 @@ GL_QueueDrawPoints(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FP return 0; } +static int +GL_QueueDrawLines(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points, int count) +{ + int i; + GLfloat prevx, prevy; + const size_t vertlen = (sizeof (GLfloat) * 2) * count; + GLfloat *verts = (GLfloat *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first); + + if (!verts) { + return -1; + } + cmd->data.draw.count = count; + + /* 0.5f offset to hit the center of the pixel. */ + prevx = 0.5f + points->x; + prevy = 0.5f + points->y; + *(verts++) = prevx; + *(verts++) = prevy; + + /* bump the end of each line segment out a quarter of a pixel, to provoke + the diamond-exit rule. Without this, you won't just drop the last + pixel of the last line segment, but you might also drop pixels at the + edge of any given line segment along the way too. */ + for (i = 1; i < count; i++) { + const GLfloat xstart = prevx; + const GLfloat ystart = prevy; + const GLfloat xend = points[i].x + 0.5f; /* 0.5f to hit pixel center. */ + const GLfloat yend = points[i].y + 0.5f; + /* bump a little in the direction we are moving in. */ + const GLfloat deltax = xend - xstart; + const GLfloat deltay = yend - ystart; + const GLfloat angle = SDL_atan2f(deltay, deltax); + prevx = xend + (SDL_cosf(angle) * 0.25f); + prevy = yend + (SDL_sinf(angle) * 0.25f); + *(verts++) = prevx; + *(verts++) = prevy; + } + + return 0; +} + static int GL_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, const float *xy, int xy_stride, const SDL_Color *color, int color_stride, const float *uv, int uv_stride, @@ -1070,6 +1111,7 @@ SetDrawState(GL_RenderData *data, const SDL_RenderCommand *cmd, const GL_Shader } vertex_array = cmd->command == SDL_RENDERCMD_DRAW_POINTS + || cmd->command == SDL_RENDERCMD_DRAW_LINES || cmd->command == SDL_RENDERCMD_GEOMETRY; color_array = cmd->command == SDL_RENDERCMD_GEOMETRY; texture_array = cmd->data.draw.texture != NULL; @@ -1244,8 +1286,17 @@ GL_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *vertic break; } - case SDL_RENDERCMD_DRAW_LINES: /* unused */ + case SDL_RENDERCMD_DRAW_LINES: { + const GLfloat *verts = (GLfloat *) (((Uint8 *) vertices) + cmd->data.draw.first); + const size_t count = cmd->data.draw.count; + SDL_assert(count >= 2); + SetDrawState(data, cmd, SHADER_SOLID); + + /* SetDrawState handles glEnableClientState. */ + data->glVertexPointer(2, GL_FLOAT, sizeof(float) * 2, verts); + data->glDrawArrays(GL_LINE_STRIP, 0, (GLsizei) count); break; + } case SDL_RENDERCMD_FILL_RECTS: /* unused */ break; @@ -1643,6 +1694,7 @@ GL_CreateRenderer(SDL_Window * window, Uint32 flags) renderer->QueueSetViewport = GL_QueueSetViewport; renderer->QueueSetDrawColor = GL_QueueSetViewport; /* SetViewport and SetDrawColor are (currently) no-ops. */ renderer->QueueDrawPoints = GL_QueueDrawPoints; + renderer->QueueDrawLines = GL_QueueDrawLines; renderer->QueueGeometry = GL_QueueGeometry; renderer->RunCommandQueue = GL_RunCommandQueue; renderer->RenderReadPixels = GL_RenderReadPixels; diff --git a/src/render/opengles/SDL_render_gles.c b/src/render/opengles/SDL_render_gles.c index 21a3150d1..a6b58f2d7 100644 --- a/src/render/opengles/SDL_render_gles.c +++ b/src/render/opengles/SDL_render_gles.c @@ -560,6 +560,47 @@ GLES_QueueDrawPoints(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_ return 0; } +static int +GLES_QueueDrawLines(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points, int count) +{ + int i; + GLfloat prevx, prevy; + const size_t vertlen = (sizeof (GLfloat) * 2) * count; + GLfloat *verts = (GLfloat *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first); + + if (!verts) { + return -1; + } + cmd->data.draw.count = count; + + /* 0.5f offset to hit the center of the pixel. */ + prevx = 0.5f + points->x; + prevy = 0.5f + points->y; + *(verts++) = prevx; + *(verts++) = prevy; + + /* bump the end of each line segment out a quarter of a pixel, to provoke + the diamond-exit rule. Without this, you won't just drop the last + pixel of the last line segment, but you might also drop pixels at the + edge of any given line segment along the way too. */ + for (i = 1; i < count; i++) { + const GLfloat xstart = prevx; + const GLfloat ystart = prevy; + const GLfloat xend = points[i].x + 0.5f; /* 0.5f to hit pixel center. */ + const GLfloat yend = points[i].y + 0.5f; + /* bump a little in the direction we are moving in. */ + const GLfloat deltax = xend - xstart; + const GLfloat deltay = yend - ystart; + const GLfloat angle = SDL_atan2f(deltay, deltax); + prevx = xend + (SDL_cosf(angle) * 0.25f); + prevy = yend + (SDL_sinf(angle) * 0.25f); + *(verts++) = prevx; + *(verts++) = prevy; + } + + return 0; +} + static int GLES_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, const float *xy, int xy_stride, const SDL_Color *color, int color_stride, const float *uv, int uv_stride, @@ -809,8 +850,15 @@ GLES_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *vert break; } - case SDL_RENDERCMD_DRAW_LINES: /* unused */ - break; + case SDL_RENDERCMD_DRAW_LINES: { + const GLfloat *verts = (GLfloat *) (((Uint8 *) vertices) + cmd->data.draw.first); + const size_t count = cmd->data.draw.count; + SDL_assert(count >= 2); + SetDrawState(data, cmd); + data->glVertexPointer(2, GL_FLOAT, 0, verts); + data->glDrawArrays(GL_LINE_STRIP, 0, (GLsizei) count); + break; + } case SDL_RENDERCMD_FILL_RECTS: /* unused */ break; @@ -1074,6 +1122,7 @@ GLES_CreateRenderer(SDL_Window * window, Uint32 flags) renderer->QueueSetViewport = GLES_QueueSetViewport; renderer->QueueSetDrawColor = GLES_QueueSetViewport; /* SetViewport and SetDrawColor are (currently) no-ops. */ renderer->QueueDrawPoints = GLES_QueueDrawPoints; + renderer->QueueDrawLines = GLES_QueueDrawLines; renderer->QueueGeometry = GLES_QueueGeometry; renderer->RunCommandQueue = GLES_RunCommandQueue; renderer->RenderReadPixels = GLES_RenderReadPixels; diff --git a/src/render/opengles2/SDL_render_gles2.c b/src/render/opengles2/SDL_render_gles2.c index cbd99c54c..3011c2e12 100644 --- a/src/render/opengles2/SDL_render_gles2.c +++ b/src/render/opengles2/SDL_render_gles2.c @@ -692,6 +692,63 @@ GLES2_QueueDrawPoints(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL return 0; } +static int +GLES2_QueueDrawLines(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points, int count) +{ + const SDL_bool colorswap = (renderer->target && (renderer->target->format == SDL_PIXELFORMAT_ARGB8888 || renderer->target->format == SDL_PIXELFORMAT_RGB888)); + int i; + GLfloat prevx, prevy; + SDL_VertexSolid *verts = (SDL_VertexSolid *) SDL_AllocateRenderVertices(renderer, count * sizeof(*verts), 0, &cmd->data.draw.first); + SDL_Color color; + color.r = cmd->data.draw.r; + color.g = cmd->data.draw.g; + color.b = cmd->data.draw.b; + color.a = cmd->data.draw.a; + + if (!verts) { + return -1; + } + + if (colorswap) { + Uint8 r = color.r; + color.r = color.b; + color.b = r; + } + + cmd->data.draw.count = count; + + /* 0.5f offset to hit the center of the pixel. */ + prevx = 0.5f + points->x; + prevy = 0.5f + points->y; + verts->position.x = prevx; + verts->position.y = prevy; + verts->color = color; + verts++; + + /* bump the end of each line segment out a quarter of a pixel, to provoke + the diamond-exit rule. Without this, you won't just drop the last + pixel of the last line segment, but you might also drop pixels at the + edge of any given line segment along the way too. */ + for (i = 1; i < count; i++) { + const GLfloat xstart = prevx; + const GLfloat ystart = prevy; + const GLfloat xend = points[i].x + 0.5f; /* 0.5f to hit pixel center. */ + const GLfloat yend = points[i].y + 0.5f; + /* bump a little in the direction we are moving in. */ + const GLfloat deltax = xend - xstart; + const GLfloat deltay = yend - ystart; + const GLfloat angle = SDL_atan2f(deltay, deltax); + prevx = xend + (SDL_cosf(angle) * 0.25f); + prevy = yend + (SDL_sinf(angle) * 0.25f); + verts->position.x = prevx; + verts->position.y = prevy; + verts->color = color; + verts++; + } + + return 0; +} + static int GLES2_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, const float *xy, int xy_stride, const SDL_Color *color, int color_stride, const float *uv, int uv_stride, @@ -1126,8 +1183,39 @@ GLES2_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *ver case SDL_RENDERCMD_COPY_EX: /* unused */ break; - case SDL_RENDERCMD_DRAW_LINES: /* unused */ + case SDL_RENDERCMD_DRAW_LINES: { + if (SetDrawState(data, cmd, GLES2_IMAGESOURCE_SOLID) == 0) { + size_t count = cmd->data.draw.count; + if (count > 2) { + /* joined lines cannot be grouped */ + data->glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)count); + } else { + /* let's group non joined lines */ + SDL_RenderCommand *finalcmd = cmd; + SDL_RenderCommand *nextcmd = cmd->next; + SDL_BlendMode thisblend = cmd->data.draw.blend; + + while (nextcmd != NULL) { + const SDL_RenderCommandType nextcmdtype = nextcmd->command; + if (nextcmdtype != SDL_RENDERCMD_DRAW_LINES) { + 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 += cmd->data.draw.count; + } + nextcmd = nextcmd->next; + } + + data->glDrawArrays(GL_LINES, 0, (GLsizei)count); + cmd = finalcmd; /* skip any copy commands we just combined in here. */ + } + } break; + } case SDL_RENDERCMD_DRAW_POINTS: case SDL_RENDERCMD_GEOMETRY: { @@ -2007,6 +2095,7 @@ GLES2_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->QueueSetViewport = GLES2_QueueSetViewport; renderer->QueueSetDrawColor = GLES2_QueueSetViewport; /* SetViewport and SetDrawColor are (currently) no-ops. */ renderer->QueueDrawPoints = GLES2_QueueDrawPoints; + renderer->QueueDrawLines = GLES2_QueueDrawLines; renderer->QueueGeometry = GLES2_QueueGeometry; renderer->RunCommandQueue = GLES2_RunCommandQueue; renderer->RenderReadPixels = GLES2_RenderReadPixels; diff --git a/src/render/psp/SDL_render_psp.c b/src/render/psp/SDL_render_psp.c index 51feabc0b..e7fca22a5 100644 --- a/src/render/psp/SDL_render_psp.c +++ b/src/render/psp/SDL_render_psp.c @@ -1120,8 +1120,23 @@ PSP_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *verti break; } - case SDL_RENDERCMD_DRAW_LINES: /* unused */ + case SDL_RENDERCMD_DRAW_LINES: { + const size_t count = cmd->data.draw.count; + const VertV *verts = (VertV *) (gpumem + cmd->data.draw.first); + const Uint8 r = cmd->data.draw.r; + const Uint8 g = cmd->data.draw.g; + const Uint8 b = cmd->data.draw.b; + const Uint8 a = cmd->data.draw.a; + PSP_BlendState state = { + .color = GU_RGBA(r,g,b,a), + .texture = NULL, + .mode = cmd->data.draw.blend, + .shadeModel = GU_FLAT + }; + PSP_SetBlendState(data, &state); + sceGuDrawArray(GU_LINE_STRIP, GU_VERTEX_32BITF|GU_TRANSFORM_2D, count, 0, verts); break; + } case SDL_RENDERCMD_FILL_RECTS: { const size_t count = cmd->data.draw.count; @@ -1324,6 +1339,7 @@ PSP_CreateRenderer(SDL_Window * window, Uint32 flags) renderer->QueueSetViewport = PSP_QueueSetViewport; renderer->QueueSetDrawColor = PSP_QueueSetViewport; /* SetViewport and SetDrawColor are (currently) no-ops. */ renderer->QueueDrawPoints = PSP_QueueDrawPoints; + renderer->QueueDrawLines = PSP_QueueDrawPoints; /* lines and points queue vertices the same way. */ renderer->QueueGeometry = PSP_QueueGeometry; renderer->QueueFillRects = PSP_QueueFillRects; renderer->QueueCopy = PSP_QueueCopy;