SDL: Prevent overdraw with connected line segments

From 4b71962031861c24bb0fe288782bfa7359e3d6ab Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 8 Jan 2022 12:02:08 -0800
Subject: [PATCH] Prevent overdraw with connected line segments

---
 src/render/SDL_render.c | 96 +++++++++++++++++++++++++----------------
 1 file changed, 60 insertions(+), 36 deletions(-)

diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index a9251c27436..2ffdae508aa 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -2807,7 +2807,7 @@ SDL_RenderDrawLineF(SDL_Renderer * renderer, float x1, float y1, float x2, float
     return SDL_RenderDrawLinesF(renderer, points, 2);
 }
 
-static int plotLineLow(SDL_Renderer *renderer, float x0, float y0, float x1, float y1)
+static int plotLineLow(SDL_Renderer *renderer, float x0, float y0, float x1, float y1, SDL_bool draw_first, SDL_bool draw_last)
 {
     int retval = 0;
     float dx = x1 - x0;
@@ -2818,6 +2818,8 @@ static int plotLineLow(SDL_Renderer *renderer, float x0, float y0, float x1, flo
     SDL_bool isstack;
     SDL_FPoint *points = SDL_small_alloc(SDL_FPoint, count, &isstack);
     SDL_FPoint *tmp = points;
+    SDL_FPoint *render_points;
+    int render_count;
 
     if (!points) {
         return SDL_OutOfMemory();
@@ -2843,10 +2845,21 @@ static int plotLineLow(SDL_Renderer *renderer, float x0, float y0, float x1, flo
     }
     SDL_assert((tmp - points) == count);
 
+    if (draw_last) {
+        render_count = count;
+    } else {
+        render_count = count - 1;
+    }
+    if (draw_first) {
+        render_points = points;
+    } else {
+        render_points = points+1;
+        --render_count;
+    }
     if (renderer->scale.x != 1.0f || renderer->scale.y != 1.0f) {
-        retval = RenderDrawPointsWithRectsF(renderer, points, count);
+        retval = RenderDrawPointsWithRectsF(renderer, render_points, render_count);
     } else {
-        retval = QueueCmdDrawPoints(renderer, points, count);
+        retval = QueueCmdDrawPoints(renderer, render_points, render_count);
     }
 
     SDL_small_free(points, isstack);
@@ -2854,7 +2867,7 @@ static int plotLineLow(SDL_Renderer *renderer, float x0, float y0, float x1, flo
     return retval;
 }
 
-static int plotLineHigh(SDL_Renderer *renderer, float x0, float y0, float x1, float y1)
+static int plotLineHigh(SDL_Renderer *renderer, float x0, float y0, float x1, float y1, SDL_bool draw_first, SDL_bool draw_last)
 {
     int retval = 0;
     float dx = x1 - x0;
@@ -2865,6 +2878,8 @@ static int plotLineHigh(SDL_Renderer *renderer, float x0, float y0, float x1, fl
     SDL_bool isstack;
     SDL_FPoint *points = SDL_small_alloc(SDL_FPoint, count, &isstack);
     SDL_FPoint *tmp = points;
+    SDL_FPoint *render_points;
+    int render_count;
 
     if (!points) {
         return SDL_OutOfMemory();
@@ -2890,10 +2905,21 @@ static int plotLineHigh(SDL_Renderer *renderer, float x0, float y0, float x1, fl
     }
     SDL_assert((tmp - points) == count);
 
+    if (draw_last) {
+        render_count = count;
+    } else {
+        render_count = count - 1;
+    }
+    if (draw_first) {
+        render_points = points;
+    } else {
+        render_points = points+1;
+        --render_count;
+    }
     if (renderer->scale.x != 1.0f || renderer->scale.y != 1.0f) {
-        retval = RenderDrawPointsWithRectsF(renderer, points, count);
+        retval = RenderDrawPointsWithRectsF(renderer, render_points, render_count);
     } else {
-        retval = QueueCmdDrawPoints(renderer, points, count);
+        retval = QueueCmdDrawPoints(renderer, render_points, render_count);
     }
 
     SDL_small_free(points, isstack);
@@ -2901,48 +2927,35 @@ static int plotLineHigh(SDL_Renderer *renderer, float x0, float y0, float x1, fl
     return retval;
 }
 
-static int plotLineBresenham(SDL_Renderer *renderer, float x0, float y0, float x1, float y1)
+static int plotLineBresenham(SDL_Renderer *renderer, float x0, float y0, float x1, float y1, SDL_bool draw_last)
 {
     if (SDL_fabs(y1 - y0) < SDL_fabs(x1 - x0)) {
         if (x0 > x1) {
-            return plotLineLow(renderer, x1, y1, x0, y0);
+            return plotLineLow(renderer, x1, y1, x0, y0, draw_last, SDL_TRUE);
         } else {
-            return plotLineLow(renderer, x0, y0, x1, y1);
+            return plotLineLow(renderer, x0, y0, x1, y1, SDL_TRUE, draw_last);
         }
     } else {
         if (y0 > y1) {
-            return plotLineHigh(renderer, x1, y1, x0, y0);
+            return plotLineHigh(renderer, x1, y1, x0, y0, draw_last, SDL_TRUE);
         } else {
-            return plotLineHigh(renderer, x0, y0, x1, y1);
+            return plotLineHigh(renderer, x0, y0, x1, y1, SDL_TRUE, draw_last);
         }
     }
 }
 
-static int
-RenderDrawLinesWithPointsF(SDL_Renderer * renderer,
-                          const SDL_FPoint * points, const int count)
-{
-    int i;
-    int retval = 0;
-
-    for (i = 0; i < count-1; ++i, ++points) {
-        retval += plotLineBresenham(renderer, points[0].x, points[0].y, points[1].x, points[1].y);
-    }
-    if (retval < 0) {
-        retval = -1;
-    }
-    return retval;
-}
-
 static int
 RenderDrawLinesWithRectsF(SDL_Renderer * renderer,
                           const SDL_FPoint * points, const int count)
 {
+    const float scale_x = renderer->scale.x;
+    const float scale_y = renderer->scale.y;
     SDL_FRect *frect;
     SDL_FRect *frects;
     int i, nrects = 0;
     int retval = 0;
     SDL_bool isstack;
+    SDL_bool draw_last = SDL_FALSE;
 
     frects = SDL_small_alloc(SDL_FRect, count-1, &isstack);
     if (!frects) {
@@ -2950,26 +2963,37 @@ RenderDrawLinesWithRectsF(SDL_Renderer * renderer,
     }
 
     for (i = 0; i < count-1; ++i) {
+        if (i == count-2) {
+            if (points[0].x != points[count-1].x || points[0].y != points[count-1].y) {
+                draw_last = SDL_TRUE;
+            }
+        }
         if (points[i].x == points[i+1].x) {
             const float minY = SDL_min(points[i].y, points[i+1].y);
             const float maxY = SDL_max(points[i].y, points[i+1].y);
 
             frect = &frects[nrects++];
-            frect->x = points[i].x * renderer->scale.x;
-            frect->y = minY * renderer->scale.y;
-            frect->w = renderer->scale.x;
-            frect->h = (maxY - minY + 1) * renderer->scale.y;
+            frect->x = points[i].x * scale_x;
+            frect->y = minY * scale_y;
+            frect->w = scale_x;
+            frect->h = (maxY - minY + draw_last) * scale_y;
+            if (!draw_last && points[i+1].y < points[i].y) {
+                frect->y += scale_y;
+            }
         } else if (points[i].y == points[i+1].y) {
             const float minX = SDL_min(points[i].x, points[i+1].x);
             const float maxX = SDL_max(points[i].x, points[i+1].x);
 
             frect = &frects[nrects++];
-            frect->x = minX * renderer->scale.x;
-            frect->y = points[i].y * renderer->scale.y;
-            frect->w = (maxX - minX + 1) * renderer->scale.x;
-            frect->h = renderer->scale.y;
+            frect->x = minX * scale_x;
+            frect->y = points[i].y * scale_y;
+            frect->w = (maxX - minX + draw_last) * scale_x;
+            frect->h = scale_y;
+            if (!draw_last && points[i+1].x < points[i].x) {
+                frect->x += scale_x;
+            }
         } else {
-            retval += RenderDrawLinesWithPointsF(renderer, &points[i], 2);
+            retval += plotLineBresenham(renderer, points[i].x, points[i].y, points[i+1].x, points[i+1].y, draw_last);
         }
     }