From e47b761a2ca79b7176d87a0e41f239f9f245714e Mon Sep 17 00:00:00 2001
From: veclavtalica <veclavtalica@tutamail.com>
Date: Fri, 28 Feb 2025 23:50:12 +0300
Subject: [PATCH] twn_lines.c: introduction with proper impl

---
 CMakeLists.txt                        |  1 +
 apps/examples/circle-raster/game.c    |  4 +-
 src/rendering/twn_deferred_commands.h |  6 ++
 src/rendering/twn_draw.c              | 35 ++---------
 src/rendering/twn_draw_c.h            | 11 ++--
 src/rendering/twn_gl_15_rendering.c   | 29 ++++-----
 src/rendering/twn_lines.c             | 87 +++++++++++++++++++++++++++
 src/twn_amalgam.c                     |  1 +
 8 files changed, 124 insertions(+), 50 deletions(-)
 create mode 100644 src/rendering/twn_lines.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index d3702b8..ecceff2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -106,6 +106,7 @@ set(TWN_NONOPT_SOURCE_FILES
         src/rendering/twn_circles.c
         src/rendering/twn_skybox.c
         src/rendering/twn_models.c
+        src/rendering/twn_lines.c
 )
 
 set(TWN_SOURCE_FILES
diff --git a/apps/examples/circle-raster/game.c b/apps/examples/circle-raster/game.c
index f33868a..6e75e91 100644
--- a/apps/examples/circle-raster/game.c
+++ b/apps/examples/circle-raster/game.c
@@ -121,6 +121,8 @@ void game_tick(void) {
     if (input_action_pressed("down") && state->r > 2)
         state->r -= 1;
 
+    draw_circle(mouse_snap, state->r * 8, (Color){125, 125, 125, 125});
+
     int32_t const rsi = (int32_t)state->r * (int32_t)state->r;
     int32_t acc = 1;
     for (int32_t iy = (int32_t)state->r - 1; iy >= 0; --iy) {
@@ -133,8 +135,6 @@ void game_tick(void) {
         }
     }
 
-    draw_circle(mouse_snap, state->r * 8, (Color){125, 125, 125, 125});
-
     /* uncomment to see performance difference between variants */
     // benchmark(state);
 }
diff --git a/src/rendering/twn_deferred_commands.h b/src/rendering/twn_deferred_commands.h
index 5e8429e..897980e 100644
--- a/src/rendering/twn_deferred_commands.h
+++ b/src/rendering/twn_deferred_commands.h
@@ -4,6 +4,7 @@
 #include "twn_types.h"
 #include "twn_gpu_texture_c.h"
 #include "twn_textures_c.h"
+#include "twn_types_c.h"
 
 #include <stddef.h>
 #include <stdbool.h>
@@ -49,6 +50,11 @@ typedef struct {
     uint32_t element_count;
     uint32_t range_start, range_end;
 
+    enum {
+        DEFERRED_COMMAND_DRAW_GEOMETRY_MODE_TRIANGLES = 0,
+        DEFERRED_COMMAND_DRAW_GEOMETRY_MODE_LINES = 1,
+    } geometry_mode;
+
     bool constant_colored;
     bool textured, texture_repeat, uses_gpu_key;
 } DeferredCommandDraw;
diff --git a/src/rendering/twn_draw.c b/src/rendering/twn_draw.c
index c13d738..cf7af86 100644
--- a/src/rendering/twn_draw.c
+++ b/src/rendering/twn_draw.c
@@ -278,7 +278,7 @@ static void render_2d(void) {
             }
 
             /* TODO: batching */
-            case PRIMITIVE_2D_LINE: {
+            case PRIMITIVE_2D_LINES: {
                 struct Render2DInvocation const invocation = {
                     .primitive = current,
                     .layer = layer,
@@ -327,8 +327,8 @@ static void render_2d(void) {
             case PRIMITIVE_2D_CIRCLE:
                 render_circle(&invocation.primitive->circle);
                 break;
-            case PRIMITIVE_2D_LINE:
-                render_line(&invocation.primitive->line);
+            case PRIMITIVE_2D_LINES:
+                render_lines(&invocation.primitive->line);
                 break;
             case PRIMITIVE_2D_TEXT:
             default:
@@ -359,8 +359,8 @@ static void render_2d(void) {
             case PRIMITIVE_2D_TEXT:
                 render_text(&invocation.primitive->text);
                 break;
-            case PRIMITIVE_2D_LINE:
-                render_line(&invocation.primitive->line);
+            case PRIMITIVE_2D_LINES:
+                render_lines(&invocation.primitive->line);
                 break;
             default:
                 SDL_assert(false);
@@ -538,31 +538,6 @@ void issue_deferred_draw_commands(void) {
 }
 
 
-/* TODO: Support thickness */
-void draw_line(Vec2 start,
-               Vec2 finish,
-               float thickness,
-               Color color)
-{
-    if (fabsf(1.0f - thickness) >= 0.00001f)
-        log_warn("Thickness isn't yet implemented for line drawing (got %f)", (double)thickness);
-
-    LinePrimitive line = {
-        .start = start,
-        .finish = finish,
-        .thickness = thickness,
-        .color = color,
-    };
-
-    Primitive2D primitive = {
-        .type = PRIMITIVE_2D_LINE,
-        .line = line,
-    };
-
-    arrput(ctx.render_queue_2d, primitive);
-}
-
-
 void draw_box(Rect rect,
               float thickness,
               Color color)
diff --git a/src/rendering/twn_draw_c.h b/src/rendering/twn_draw_c.h
index 15c4220..9ffc99f 100644
--- a/src/rendering/twn_draw_c.h
+++ b/src/rendering/twn_draw_c.h
@@ -69,9 +69,12 @@ typedef struct SpritePrimitive {
     bool repeat;
 } SpritePrimitive;
 
+/* batched in place */
 typedef struct LinePrimitive {
-    Vec2 start;
-    Vec2 finish;
+    struct LineVertex {
+        Vec3 position;
+        Color color;
+    } *vertices;
     float thickness;
     Color color;
 } LinePrimitive;
@@ -97,7 +100,7 @@ typedef struct TextPrimitive {
 
 typedef enum Primitive2DType {
     PRIMITIVE_2D_SPRITE,
-    PRIMITIVE_2D_LINE,
+    PRIMITIVE_2D_LINES,
     PRIMITIVE_2D_RECT,
     PRIMITIVE_2D_CIRCLE,
     PRIMITIVE_2D_TEXT,
@@ -319,7 +322,7 @@ IndexBuffer get_circle_element_buffer(void);
 
 void render_circle(const CirclePrimitive *circle);
 
-void render_line(const LinePrimitive *line);
+void render_lines(LinePrimitive *line);
 
 void render_rectangle(const RectPrimitive *rectangle);
 
diff --git a/src/rendering/twn_gl_15_rendering.c b/src/rendering/twn_gl_15_rendering.c
index 0b38856..6fe7dec 100644
--- a/src/rendering/twn_gl_15_rendering.c
+++ b/src/rendering/twn_gl_15_rendering.c
@@ -630,12 +630,24 @@ void finally_draw_command(DeferredCommandDraw command) {
             textures_bind(&ctx.texture_cache, command.texture_key);
     }
 
+    GLenum geometry_mode;
+    switch (command.geometry_mode) {
+    case DEFERRED_COMMAND_DRAW_GEOMETRY_MODE_TRIANGLES:
+        geometry_mode = GL_TRIANGLES;
+        break;
+    case DEFERRED_COMMAND_DRAW_GEOMETRY_MODE_LINES:
+        geometry_mode = GL_LINES;
+        break;
+    default:
+        SDL_assert(false);
+    }
+
     if (command.element_buffer) {
         SDL_assert(command.element_count != 0);
         if (command.range_start == command.range_end)
-            glDrawElements(GL_TRIANGLES, command.element_count, GL_UNSIGNED_SHORT, NULL);
+            glDrawElements(geometry_mode, command.element_count, GL_UNSIGNED_SHORT, NULL);
         else
-            glDrawRangeElements(GL_TRIANGLES,
+            glDrawRangeElements(geometry_mode,
                                 command.range_start,
                                 command.range_end,
                                 command.element_count,
@@ -643,7 +655,7 @@ void finally_draw_command(DeferredCommandDraw command) {
                                 NULL);
     } else {
         SDL_assert(command.primitive_count != 0);
-        glDrawArrays(GL_TRIANGLES, 0, command.primitive_count);
+        glDrawArrays(geometry_mode, 0, command.primitive_count);
     }
 
     /* state clearing */
@@ -662,14 +674,3 @@ void finally_draw_command(DeferredCommandDraw command) {
     glBindBuffer(GL_ARRAY_BUFFER, 0);
     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
 }
-
-
-void render_line(const LinePrimitive *line) {
-    finally_use_2d_pipeline();
-    glBegin(GL_LINES);
-        glColor4ub(line->color.r, line->color.g, line->color.b, line->color.a);
-        glVertex2f(line->start.x, line->start.y);
-        glColor4ub(line->color.r, line->color.g, line->color.b, line->color.a);
-        glVertex2f(line->finish.x, line->finish.y);
-    glEnd();
-}
diff --git a/src/rendering/twn_lines.c b/src/rendering/twn_lines.c
new file mode 100644
index 0000000..c5e282b
--- /dev/null
+++ b/src/rendering/twn_lines.c
@@ -0,0 +1,87 @@
+#include "twn_draw_c.h"
+#include "twn_draw.h"
+#include "twn_engine_context_c.h"
+#include "twn_util.h"
+
+#include <stb_ds.h>
+
+/* TODO: Support thickness */
+void draw_line(Vec2 start,
+               Vec2 finish,
+               float thickness,
+               Color color)
+{
+    if (fabsf(1.0f - thickness) >= 0.00001f)
+        log_warn("Thickness isn't yet implemented for line drawing (got %f)", (double)thickness);
+
+    struct LineVertex const v0 = { .position = (Vec3){start.x, start.y, 0}, .color = color };
+    struct LineVertex const v1 = { .position = (Vec3){finish.x, finish.y, 0}, .color = color };
+
+    /* combine with existing position if it's compatible */
+    if (arrlenu(ctx.render_queue_2d) != 0) {
+        Primitive2D *const primitive = &ctx.render_queue_2d[arrlenu(ctx.render_queue_2d) - 1];
+        if (primitive->type == PRIMITIVE_2D_LINES && fabsf(primitive->line.thickness - thickness) < 0.00001f &&
+                primitive->line.color.a == color.a && primitive->line.color.b == color.b &&
+                primitive->line.color.r == color.r && primitive->line.color.g == color.g) {
+
+            arrput(primitive->line.vertices, v0);
+            arrput(primitive->line.vertices, v1);
+            return;
+        }
+    }
+
+    LinePrimitive line = {
+        .thickness = thickness,
+        .color = color,
+    };
+
+    Primitive2D primitive = {
+        .type = PRIMITIVE_2D_LINES,
+        .line = line,
+    };
+
+    arrput(primitive.line.vertices, v0);
+    arrput(primitive.line.vertices, v1);
+    arrput(ctx.render_queue_2d, primitive);
+}
+
+
+void render_lines(LinePrimitive *line) {
+    DeferredCommandDraw command = {0};
+
+    VertexBuffer buffer = get_scratch_vertex_array();
+    specify_vertex_buffer(buffer, line->vertices, arrlenu(line->vertices) * sizeof (*line->vertices));
+
+    command.vertices = (AttributeArrayPointer) {
+        .arity = 3,
+        .type = TWN_FLOAT,
+        .stride = sizeof (struct LineVertex),
+        .offset = offsetof (struct LineVertex, position),
+        .buffer = buffer
+    };
+
+    command.colors = (AttributeArrayPointer) {
+        .arity = 4,
+        .type = TWN_UNSIGNED_BYTE,
+        .stride = sizeof (struct LineVertex),
+        .offset = offsetof (struct LineVertex, color),
+        .buffer = buffer
+    };
+
+    command.primitive_count = arrlenu(line->vertices);
+
+    command.geometry_mode = DEFERRED_COMMAND_DRAW_GEOMETRY_MODE_LINES;
+    command.pipeline = PIPELINE_2D;
+
+    command.depth_range_high = depth_range_high;
+    command.depth_range_low = depth_range_low;
+
+    DeferredCommand final_command = {
+        .type = DEFERRED_COMMAND_TYPE_DRAW,
+        .draw = command
+    };
+
+    /* TODO: should it be deleted here? */
+    arrfree(line->vertices);
+    arrpush(deferred_commands, final_command);
+}
diff --git a/src/twn_amalgam.c b/src/twn_amalgam.c
index 6cfa3a5..dee1e81 100644
--- a/src/twn_amalgam.c
+++ b/src/twn_amalgam.c
@@ -23,3 +23,4 @@
 #include "rendering/twn_triangles.c"
 #include "rendering/twn_billboards.c"
 #include "rendering/twn_models.c"
+#include "rendering/twn_lines.c"