opengl: Build out full GL_LINES and respect the diamond-exit rule.
authorRyan C. Gordon <icculus@icculus.org>
Mon, 10 Feb 2020 12:53:54 -0500
changeset 134980c915d307499
parent 13497 22308b7ba06c
child 13499 8b93225188c8
opengl: Build out full GL_LINES and respect the diamond-exit rule.

Likewise for the GLES1 and GLES2 renderers.

This solves the missing pixel at the end of a line and removes all the
heuristics for various platforms/drivers. It's possible we could still use
GL_LINE_STRIP with this and save some vertex buffer space, assuming this
doesn't upset some driver somewhere, but this seems to be a clean fix that
makes the GL renderers match the software renderer output.

Diamond-exit rule explanation:
http://graphics-software-engineer.blogspot.com/2012/04/rasterization-rules.html

Fixes Bugzilla #3182.
src/render/opengl/SDL_render_gl.c
src/render/opengles/SDL_render_gles.c
src/render/opengles2/SDL_render_gles2.c
     1.1 --- a/src/render/opengl/SDL_render_gl.c	Mon Feb 10 09:42:53 2020 -0800
     1.2 +++ b/src/render/opengl/SDL_render_gl.c	Mon Feb 10 12:53:54 2020 -0500
     1.3 @@ -866,6 +866,60 @@
     1.4  }
     1.5  
     1.6  static int
     1.7 +GL_QueueDrawLines(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points, int count)
     1.8 +{
     1.9 +    GLfloat *verts;
    1.10 +    int i;
    1.11 +
    1.12 +    SDL_assert(count >= 2);  /* should have been checked at the higher level. */
    1.13 +
    1.14 +    verts = (GLfloat *) SDL_AllocateRenderVertices(renderer, (count-1) * 4 * sizeof (GLfloat), 0, &cmd->data.draw.first);
    1.15 +    if (!verts) {
    1.16 +        return -1;
    1.17 +    }
    1.18 +
    1.19 +    cmd->data.draw.count = count;
    1.20 +
    1.21 +    /* GL_LINE_STRIP seems to be unreliable on various drivers, so try
    1.22 +       to build out our own GL_LINES.  :(
    1.23 +       If the line segment is completely horizontal or vertical,
    1.24 +       make it one pixel longer, to satisfy the diamond-exit rule.
    1.25 +       We should probably do this for diagonal lines too, but we'd have to
    1.26 +       do some trigonometry to figure out the correct pixel and generally
    1.27 +       when we have problems with pixel perfection, it's for straight lines
    1.28 +       that are missing a pixel that frames something and not arbitrary
    1.29 +       angles. Maybe !!! FIXME for later, though. */
    1.30 +
    1.31 +    for (i = 0; i < count-1; i++, points++) {
    1.32 +        GLfloat xstart = 0.5f + points[0].x;   /* 0.5f to get to the center of the pixel. */
    1.33 +        GLfloat ystart = 0.5f + points[0].y;
    1.34 +        GLfloat xend = 0.5f + points[1].x;
    1.35 +        GLfloat yend = 0.5f + points[1].y;
    1.36 +
    1.37 +        if (xstart == xend) {  /* vertical line */
    1.38 +            if (yend > ystart) {
    1.39 +                yend += 1.0f;
    1.40 +            } else {
    1.41 +                ystart += 1.0f;
    1.42 +            }
    1.43 +        } else if (ystart == yend) {  /* horizontal line */
    1.44 +            if (xend > xstart) {
    1.45 +                xend += 1.0f;
    1.46 +            } else {
    1.47 +                xstart += 1.0f;
    1.48 +            }
    1.49 +        }
    1.50 +
    1.51 +        *(verts++) = xstart;
    1.52 +        *(verts++) = ystart;
    1.53 +        *(verts++) = xend;
    1.54 +        *(verts++) = yend;
    1.55 +    }
    1.56 +
    1.57 +    return 0;
    1.58 +}
    1.59 +
    1.60 +static int
    1.61  GL_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects, int count)
    1.62  {
    1.63      GLfloat *verts = (GLfloat *) SDL_AllocateRenderVertices(renderer, count * 4 * sizeof (GLfloat), 0, &cmd->data.draw.first);
    1.64 @@ -1228,59 +1282,14 @@
    1.65              case SDL_RENDERCMD_DRAW_LINES: {
    1.66                  const GLfloat *verts = (GLfloat *) (((Uint8 *) vertices) + cmd->data.draw.first);
    1.67                  const size_t count = cmd->data.draw.count;
    1.68 +                SDL_assert(count >= 2);
    1.69                  SetDrawState(data, cmd, SHADER_SOLID);
    1.70 -                if (count > 2 && (verts[0] == verts[(count-1)*2]) && (verts[1] == verts[(count*2)-1])) {
    1.71 -                    data->glBegin(GL_LINE_LOOP);
    1.72 -                    /* GL_LINE_LOOP takes care of the final segment */
    1.73 -                    for (i = 1; i < count; ++i, verts += 2) {
    1.74 -                        data->glVertex2f(verts[0], verts[1]);
    1.75 -                    }
    1.76 -                    data->glEnd();
    1.77 -                } else {
    1.78 -                    #if defined(__MACOSX__) || defined(__WIN32__)
    1.79 -                    #else
    1.80 -                    int x1, y1, x2, y2;
    1.81 -                    #endif
    1.82 -
    1.83 -                    data->glBegin(GL_LINE_STRIP);
    1.84 -                    for (i = 0; i < count; ++i, verts += 2) {
    1.85 -                        data->glVertex2f(verts[0], verts[1]);
    1.86 -                    }
    1.87 -                    data->glEnd();
    1.88 -                    verts -= 2 * count;
    1.89 -
    1.90 -                    /* The line is half open, so we need one more point to complete it.
    1.91 -                     * http://www.opengl.org/documentation/specs/version1.1/glspec1.1/node47.html
    1.92 -                     * If we have to, we can use vertical line and horizontal line textures
    1.93 -                     * for vertical and horizontal lines, and then create custom textures
    1.94 -                     * for diagonal lines and software render those.  It's terrible, but at
    1.95 -                     * least it would be pixel perfect.
    1.96 -                     */
    1.97 -
    1.98 -                    data->glBegin(GL_POINTS);
    1.99 -                    #if defined(__MACOSX__) || defined(__WIN32__)
   1.100 -                    /* Mac OS X and Windows seem to always leave the last point open */
   1.101 -                    data->glVertex2f(verts[(count-1)*2], verts[(count*2)-1]);
   1.102 -                    #else
   1.103 -                    /* Linux seems to leave the right-most or bottom-most point open */
   1.104 -                    x1 = verts[0];
   1.105 -                    y1 = verts[1];
   1.106 -                    x2 = verts[(count-1)*2];
   1.107 -                    y2 = verts[(count*2)-1];
   1.108 -
   1.109 -                    if (x1 > x2) {
   1.110 -                        data->glVertex2f(x1, y1);
   1.111 -                    } else if (x2 > x1) {
   1.112 -                        data->glVertex2f(x2, y2);
   1.113 -                    }
   1.114 -                    if (y1 > y2) {
   1.115 -                        data->glVertex2f(x1, y1);
   1.116 -                    } else if (y2 > y1) {
   1.117 -                        data->glVertex2f(x2, y2);
   1.118 -                    }
   1.119 -                    #endif
   1.120 -                    data->glEnd();
   1.121 +                data->glBegin(GL_LINES);
   1.122 +                for (i = 0; i < count-1; ++i, verts += 4) {
   1.123 +                    data->glVertex2f(verts[0], verts[1]);
   1.124 +                    data->glVertex2f(verts[2], verts[3]);
   1.125                  }
   1.126 +                data->glEnd();
   1.127                  break;
   1.128              }
   1.129  
   1.130 @@ -1619,7 +1628,7 @@
   1.131      renderer->QueueSetViewport = GL_QueueSetViewport;
   1.132      renderer->QueueSetDrawColor = GL_QueueSetViewport;  /* SetViewport and SetDrawColor are (currently) no-ops. */
   1.133      renderer->QueueDrawPoints = GL_QueueDrawPoints;
   1.134 -    renderer->QueueDrawLines = GL_QueueDrawPoints;  /* lines and points queue vertices the same way. */
   1.135 +    renderer->QueueDrawLines = GL_QueueDrawLines;
   1.136      renderer->QueueFillRects = GL_QueueFillRects;
   1.137      renderer->QueueCopy = GL_QueueCopy;
   1.138      renderer->QueueCopyEx = GL_QueueCopyEx;
     2.1 --- a/src/render/opengles/SDL_render_gles.c	Mon Feb 10 09:42:53 2020 -0800
     2.2 +++ b/src/render/opengles/SDL_render_gles.c	Mon Feb 10 12:53:54 2020 -0500
     2.3 @@ -561,6 +561,60 @@
     2.4  }
     2.5  
     2.6  static int
     2.7 +GLES_QueueDrawLines(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points, int count)
     2.8 +{
     2.9 +    GLfloat *verts;
    2.10 +    int i;
    2.11 +
    2.12 +    SDL_assert(count >= 2);  /* should have been checked at the higher level. */
    2.13 +
    2.14 +    verts = (GLfloat *) SDL_AllocateRenderVertices(renderer, (count-1) * 4 * sizeof (GLfloat), 0, &cmd->data.draw.first);
    2.15 +    if (!verts) {
    2.16 +        return -1;
    2.17 +    }
    2.18 +
    2.19 +    cmd->data.draw.count = count;
    2.20 +
    2.21 +    /* GL_LINE_STRIP seems to be unreliable on various drivers, so try
    2.22 +       to build out our own GL_LINES.  :(
    2.23 +       If the line segment is completely horizontal or vertical,
    2.24 +       make it one pixel longer, to satisfy the diamond-exit rule.
    2.25 +       We should probably do this for diagonal lines too, but we'd have to
    2.26 +       do some trigonometry to figure out the correct pixel and generally
    2.27 +       when we have problems with pixel perfection, it's for straight lines
    2.28 +       that are missing a pixel that frames something and not arbitrary
    2.29 +       angles. Maybe !!! FIXME for later, though. */
    2.30 +
    2.31 +    for (i = 0; i < count-1; i++, points++) {
    2.32 +        GLfloat xstart = 0.5f + points[0].x;   /* 0.5f to get to the center of the pixel. */
    2.33 +        GLfloat ystart = 0.5f + points[0].y;
    2.34 +        GLfloat xend = 0.5f + points[1].x;
    2.35 +        GLfloat yend = 0.5f + points[1].y;
    2.36 +
    2.37 +        if (xstart == xend) {  /* vertical line */
    2.38 +            if (yend > ystart) {
    2.39 +                yend += 1.0f;
    2.40 +            } else {
    2.41 +                ystart += 1.0f;
    2.42 +            }
    2.43 +        } else if (ystart == yend) {  /* horizontal line */
    2.44 +            if (xend > xstart) {
    2.45 +                xend += 1.0f;
    2.46 +            } else {
    2.47 +                xstart += 1.0f;
    2.48 +            }
    2.49 +        }
    2.50 +
    2.51 +        *(verts++) = xstart;
    2.52 +        *(verts++) = ystart;
    2.53 +        *(verts++) = xend;
    2.54 +        *(verts++) = yend;
    2.55 +    }
    2.56 +
    2.57 +    return 0;
    2.58 +}
    2.59 +
    2.60 +static int
    2.61  GLES_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects, int count)
    2.62  {
    2.63      GLfloat *verts = (GLfloat *) SDL_AllocateRenderVertices(renderer, count * 8 * sizeof (GLfloat), 0, &cmd->data.draw.first);
    2.64 @@ -900,16 +954,10 @@
    2.65              case SDL_RENDERCMD_DRAW_LINES: {
    2.66                  const GLfloat *verts = (GLfloat *) (((Uint8 *) vertices) + cmd->data.draw.first);
    2.67                  const size_t count = cmd->data.draw.count;
    2.68 +                SDL_assert(count >= 2);
    2.69                  SetDrawState(data, cmd);
    2.70                  data->glVertexPointer(2, GL_FLOAT, 0, verts);
    2.71 -                if (count > 2 && (verts[0] == verts[(count-1)*2]) && (verts[1] == verts[(count*2)-1])) {
    2.72 -                    /* GL_LINE_LOOP takes care of the final segment */
    2.73 -                    data->glDrawArrays(GL_LINE_LOOP, 0, (GLsizei) (count - 1));
    2.74 -                } else {
    2.75 -                    data->glDrawArrays(GL_LINE_STRIP, 0, (GLsizei) count);
    2.76 -                    /* We need to close the endpoint of the line */
    2.77 -                    data->glDrawArrays(GL_POINTS, (GLsizei) (count - 1), 1);
    2.78 -                }
    2.79 +                data->glDrawArrays(GL_LINES, 0, (GLsizei) ((count-1) * 2));
    2.80                  break;
    2.81              }
    2.82  
    2.83 @@ -1158,7 +1206,7 @@
    2.84      renderer->QueueSetViewport = GLES_QueueSetViewport;
    2.85      renderer->QueueSetDrawColor = GLES_QueueSetViewport;  /* SetViewport and SetDrawColor are (currently) no-ops. */
    2.86      renderer->QueueDrawPoints = GLES_QueueDrawPoints;
    2.87 -    renderer->QueueDrawLines = GLES_QueueDrawPoints;  /* lines and points queue vertices the same way. */
    2.88 +    renderer->QueueDrawLines = GLES_QueueDrawLines;
    2.89      renderer->QueueFillRects = GLES_QueueFillRects;
    2.90      renderer->QueueCopy = GLES_QueueCopy;
    2.91      renderer->QueueCopyEx = GLES_QueueCopyEx;
     3.1 --- a/src/render/opengles2/SDL_render_gles2.c	Mon Feb 10 09:42:53 2020 -0800
     3.2 +++ b/src/render/opengles2/SDL_render_gles2.c	Mon Feb 10 12:53:54 2020 -0500
     3.3 @@ -784,6 +784,60 @@
     3.4  }
     3.5  
     3.6  static int
     3.7 +GLES2_QueueDrawLines(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points, int count)
     3.8 +{
     3.9 +    GLfloat *verts;
    3.10 +    int i;
    3.11 +
    3.12 +    SDL_assert(count >= 2);  /* should have been checked at the higher level. */
    3.13 +
    3.14 +    verts = (GLfloat *) SDL_AllocateRenderVertices(renderer, (count-1) * 4 * sizeof (GLfloat), 0, &cmd->data.draw.first);
    3.15 +    if (!verts) {
    3.16 +        return -1;
    3.17 +    }
    3.18 +
    3.19 +    cmd->data.draw.count = count;
    3.20 +
    3.21 +    /* GL_LINE_STRIP seems to be unreliable on various drivers, so try
    3.22 +       to build out our own GL_LINES.  :(
    3.23 +       If the line segment is completely horizontal or vertical,
    3.24 +       make it one pixel longer, to satisfy the diamond-exit rule.
    3.25 +       We should probably do this for diagonal lines too, but we'd have to
    3.26 +       do some trigonometry to figure out the correct pixel and generally
    3.27 +       when we have problems with pixel perfection, it's for straight lines
    3.28 +       that are missing a pixel that frames something and not arbitrary
    3.29 +       angles. Maybe !!! FIXME for later, though. */
    3.30 +
    3.31 +    for (i = 0; i < count-1; i++, points++) {
    3.32 +        GLfloat xstart = 0.5f + points[0].x;   /* 0.5f to get to the center of the pixel. */
    3.33 +        GLfloat ystart = 0.5f + points[0].y;
    3.34 +        GLfloat xend = 0.5f + points[1].x;
    3.35 +        GLfloat yend = 0.5f + points[1].y;
    3.36 +
    3.37 +        if (xstart == xend) {  /* vertical line */
    3.38 +            if (yend > ystart) {
    3.39 +                yend += 1.0f;
    3.40 +            } else {
    3.41 +                ystart += 1.0f;
    3.42 +            }
    3.43 +        } else if (ystart == yend) {  /* horizontal line */
    3.44 +            if (xend > xstart) {
    3.45 +                xend += 1.0f;
    3.46 +            } else {
    3.47 +                xstart += 1.0f;
    3.48 +            }
    3.49 +        }
    3.50 +
    3.51 +        *(verts++) = xstart;
    3.52 +        *(verts++) = ystart;
    3.53 +        *(verts++) = xend;
    3.54 +        *(verts++) = yend;
    3.55 +    }
    3.56 +
    3.57 +    return 0;
    3.58 +}
    3.59 +
    3.60 +static int
    3.61  GLES2_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects, int count)
    3.62  {
    3.63      GLfloat *verts = (GLfloat *) SDL_AllocateRenderVertices(renderer, count * 8 * sizeof (GLfloat), 0, &cmd->data.draw.first);
    3.64 @@ -1294,17 +1348,10 @@
    3.65              }
    3.66  
    3.67              case SDL_RENDERCMD_DRAW_LINES: {
    3.68 -                const GLfloat *verts = (GLfloat *) (((Uint8 *) vertices) + cmd->data.draw.first);
    3.69                  const size_t count = cmd->data.draw.count;
    3.70 +                SDL_assert(count >= 2);
    3.71                  if (SetDrawState(data, cmd, GLES2_IMAGESOURCE_SOLID) == 0) {
    3.72 -                    if (count > 2 && (verts[0] == verts[(count-1)*2]) && (verts[1] == verts[(count*2)-1])) {
    3.73 -                        /* GL_LINE_LOOP takes care of the final segment */
    3.74 -                        data->glDrawArrays(GL_LINE_LOOP, 0, (GLsizei) (count - 1));
    3.75 -                    } else {
    3.76 -                        data->glDrawArrays(GL_LINE_STRIP, 0, (GLsizei) count);
    3.77 -                        /* We need to close the endpoint of the line */
    3.78 -                        data->glDrawArrays(GL_POINTS, (GLsizei) (count - 1), 1);
    3.79 -                    }
    3.80 +                    data->glDrawArrays(GL_LINES, 0, (GLsizei) ((count-1) * 2));
    3.81                  }
    3.82                  break;
    3.83              }
    3.84 @@ -2099,7 +2146,7 @@
    3.85      renderer->QueueSetViewport    = GLES2_QueueSetViewport;
    3.86      renderer->QueueSetDrawColor   = GLES2_QueueSetViewport;  /* SetViewport and SetDrawColor are (currently) no-ops. */
    3.87      renderer->QueueDrawPoints     = GLES2_QueueDrawPoints;
    3.88 -    renderer->QueueDrawLines      = GLES2_QueueDrawPoints;  /* lines and points queue vertices the same way. */
    3.89 +    renderer->QueueDrawLines      = GLES2_QueueDrawLines;
    3.90      renderer->QueueFillRects      = GLES2_QueueFillRects;
    3.91      renderer->QueueCopy           = GLES2_QueueCopy;
    3.92      renderer->QueueCopyEx         = GLES2_QueueCopyEx;