SDL: Added the hint SDL_HINT_RENDER_LINE_METHOD to select the line rendering method

From 09ece861d1f8015f4960ab5fcfc6cb599e979f3e Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 8 Jan 2022 11:36:29 -0800
Subject: [PATCH] Added the hint SDL_HINT_RENDER_LINE_METHOD to select the line
 rendering method

---
 include/SDL_hints.h        | 13 ++++++++++++
 src/render/SDL_render.c    | 42 +++++++++++++++++++++++++++++++-------
 src/render/SDL_sysrender.h | 11 ++++++++++
 3 files changed, 59 insertions(+), 7 deletions(-)

diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index 0e0908664dd..1185f42229b 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -1059,6 +1059,19 @@ extern "C" {
  */
 #define SDL_HINT_RENDER_BATCHING  "SDL_RENDER_BATCHING"
 
+/**
+ *  \brief  A variable controlling how the 2D render API renders lines
+ *
+ *  This variable can be set to the following values:
+ *    "0"     - Use the default line drawing method (Bresenham's line algorithm as of SDL 2.0.20)
+ *    "1"     - Use the driver point API using Bresenham's line algorithm (correct, draws many points)
+ *    "2"     - Use the driver line API (occasionally misses line endpoints based on hardware driver quirks, was the default before 2.0.20)
+ *    "3"     - Use the driver geometry API (correct, draws thicker diagonal lines)
+ *
+ *  This variable should be set when the renderer is created.
+ */
+#define SDL_HINT_RENDER_LINE_METHOD "SDL_RENDER_LINE_METHOD"
+
 /**
  *  \brief  A variable controlling whether to enable Direct3D 11+'s Debug Layer.
  *
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index 2f6fe8345c5..a9251c27436 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -896,6 +896,26 @@ void VerifyDrawQueueFunctions(const SDL_Renderer *renderer)
     SDL_assert(renderer->RunCommandQueue != NULL);
 }
 
+static SDL_RenderLineMethod SDL_GetRenderLineMethod()
+{
+    const char *hint = SDL_GetHint(SDL_HINT_RENDER_LINE_METHOD);
+
+    int method = 0;
+    if (hint) {
+        method = SDL_atoi(hint);
+    }
+    switch (method) {
+    case 1:
+        return SDL_RENDERLINEMETHOD_POINTS;
+    case 2:
+        return SDL_RENDERLINEMETHOD_LINES;
+    case 3:
+        return SDL_RENDERLINEMETHOD_GEOMETRY;
+    default:
+        return SDL_RENDERLINEMETHOD_POINTS;
+    }
+}
+
 SDL_Renderer *
 SDL_CreateRenderer(SDL_Window * window, int index, Uint32 flags)
 {
@@ -1011,6 +1031,8 @@ SDL_CreateRenderer(SDL_Window * window, int index, Uint32 flags)
 
     renderer->relative_scaling = SDL_GetHintBoolean(SDL_HINT_MOUSE_RELATIVE_SCALING, SDL_TRUE);
 
+    renderer->line_method = SDL_GetRenderLineMethod();
+
     if (SDL_GetWindowFlags(window) & (SDL_WINDOW_HIDDEN|SDL_WINDOW_MINIMIZED)) {
         renderer->hidden = SDL_TRUE;
     } else {
@@ -1062,6 +1084,9 @@ SDL_CreateSoftwareRenderer(SDL_Surface * surface)
         /* new textures start at zero, so we start at 1 so first render doesn't flush by accident. */
         renderer->render_command_generation = 1;
 
+        /* Software renderer always uses line method, for speed */
+        renderer->line_method = SDL_RENDERLINEMETHOD_LINES;
+
         SDL_RenderSetViewport(renderer, NULL);
     }
     return renderer;
@@ -2794,6 +2819,10 @@ static int plotLineLow(SDL_Renderer *renderer, float x0, float y0, float x1, flo
     SDL_FPoint *points = SDL_small_alloc(SDL_FPoint, count, &isstack);
     SDL_FPoint *tmp = points;
 
+    if (!points) {
+        return SDL_OutOfMemory();
+    }
+
     if (dy < 0) {
         yi = -1;
         dy = -dy;
@@ -2837,6 +2866,10 @@ static int plotLineHigh(SDL_Renderer *renderer, float x0, float y0, float x1, fl
     SDL_FPoint *points = SDL_small_alloc(SDL_FPoint, count, &isstack);
     SDL_FPoint *tmp = points;
 
+    if (!points) {
+        return SDL_OutOfMemory();
+    }
+
     if (dx < 0) {
         xi = -1;
         dx = -dx;
@@ -2999,8 +3032,6 @@ SDL_RenderDrawLinesF(SDL_Renderer * renderer,
                      const SDL_FPoint * points, int count)
 {
     int retval = 0;
-    int use_renderpoints;
-    int use_rendergeometry;
 
     CHECK_RENDERER_MAGIC(renderer, -1);
 
@@ -3018,12 +3049,9 @@ SDL_RenderDrawLinesF(SDL_Renderer * renderer,
     }
 #endif
 
-    use_renderpoints = 1;
-    use_rendergeometry = 1;
-
-    if (use_renderpoints) {
+    if (renderer->line_method == SDL_RENDERLINEMETHOD_POINTS) {
         retval = RenderDrawLinesWithRectsF(renderer, points, count);
-    } else if (use_rendergeometry) {
+    } else if (renderer->line_method == SDL_RENDERLINEMETHOD_GEOMETRY) {
         SDL_bool isstack1;
         SDL_bool isstack2;
         const float scale_x = renderer->scale.x;
diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h
index 1500b3a355d..ff38ed0dd69 100644
--- a/src/render/SDL_sysrender.h
+++ b/src/render/SDL_sysrender.h
@@ -114,6 +114,14 @@ typedef struct SDL_VertexSolid
 } SDL_VertexSolid;
 
 
+typedef enum
+{
+    SDL_RENDERLINEMETHOD_POINTS,
+    SDL_RENDERLINEMETHOD_LINES,
+    SDL_RENDERLINEMETHOD_GEOMETRY,
+} SDL_RenderLineMethod;
+
+
 /* Define the SDL renderer structure */
 struct SDL_Renderer
 {
@@ -214,6 +222,9 @@ struct SDL_Renderer
     /* Whether or not to scale relative mouse motion */
     SDL_bool relative_scaling;
 
+    /* The method of drawing lines */
+    SDL_RenderLineMethod line_method;
+
     /* Remainder from scaled relative motion */
     float xrel;
     float yrel;