# Forge Debug Lines

> Add immediate-mode debug line drawing to an SDL GPU project. Render colored lines, grids, axes, circles, and wireframe boxes with world-space (depth-tested) and overlay (always-on-top) modes. Use when someone needs diagnostic visualization, debug drawing, or wireframe rendering in SDL3 GPU.

- Skill: `majiayu000/forge-debug-lines` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add majiayu000/forge-debug-lines`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/forge-debug-lines/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/forge-debug-lines

---


# Debug Lines — Immediate-Mode Debug Drawing

This skill adds a lightweight debug drawing system to any SDL GPU project.
It uses `SDL_GPU_PRIMITIVETYPE_LINELIST` with a dynamic vertex buffer updated
every frame via the immediate-mode pattern: reset, accumulate, upload, draw.

## When to use

- Visualizing positions, bounds, directions, or other spatial data
- Drawing coordinate-axis gizmos (RGB = XYZ)
- Rendering wireframe bounding boxes for physics or collision debugging
- Drawing circles to show radii, influence zones, or trigger areas
- Adding an always-visible overlay for diagnostic indicators
- Any per-frame debug visualization that changes based on game state

## Key API calls (ordered)

1. `SDL_CreateGPUGraphicsPipeline` — create two pipelines (depth ON / depth OFF)
   with `SDL_GPU_PRIMITIVETYPE_LINELIST` and `SDL_GPU_CULLMODE_NONE`
2. `SDL_CreateGPUBuffer` — pre-allocate GPU vertex buffer for max vertices
3. `SDL_CreateGPUTransferBuffer` — pre-allocate upload staging buffer
4. `SDL_MapGPUTransferBuffer` / `SDL_UnmapGPUTransferBuffer` — map, copy, unmap each frame
5. `SDL_UploadToGPUBuffer` — copy pass to transfer data to GPU
6. `SDL_PushGPUVertexUniformData` — push VP matrix
7. `SDL_DrawGPUPrimitives` — draw world lines, then overlay lines with first_vertex offset

## Common mistakes

- **Forgetting to reset vertex counts each frame.** The immediate-mode pattern
  requires `world_count = 0; overlay_count = 0;` at the start of every frame,
  otherwise vertices accumulate and the buffer overflows.
- **Using TRIANGLELIST primitive type.** Debug lines use `LINELIST` — every pair
  of vertices is a separate line segment. There are no faces, no indices.
- **Enabling back-face culling.** Lines have no face orientation.  Always use
  `SDL_GPU_CULLMODE_NONE` for line pipelines.
- **Creating separate GPU buffers for world and overlay.** Both can share one
  buffer — use `first_vertex` offset in the draw call to select the region.
- **Forgetting the copy pass before drawing.** The transfer buffer upload
  requires `SDL_BeginGPUCopyPass` / `SDL_UploadToGPUBuffer` /
  `SDL_EndGPUCopyPass` / `SDL_SubmitGPUCommandBuffer` before the render pass.

## Code template

### Vertex format

```c
typedef struct DebugVertex {
    vec3 position;   /* 12 bytes — world-space position */
    vec4 color;      /* 16 bytes — RGBA color           */
} DebugVertex;       /* 28 bytes total                  */

#define DEBUG_VERTEX_PITCH 28
#define MAX_DEBUG_VERTICES 65536
```

### Pipeline creation

```c
/* Vertex layout: position (float3) + color (float4) */
SDL_GPUVertexAttribute attrs[2];
SDL_zero(attrs);
attrs[0].location = 0;
attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3;
attrs[0].offset = offsetof(DebugVertex, position);
attrs[1].location = 1;
attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
attrs[1].offset = offsetof(DebugVertex, color);

SDL_GPUGraphicsPipelineCreateInfo pipe;
SDL_zero(pipe);
pipe.primitive_type = SDL_GPU_PRIMITIVETYPE_LINELIST;
pipe.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE;

/* World-space pipeline: depth test ON */
pipe.depth_stencil_state.enable_depth_test = true;
pipe.depth_stencil_state.enable_depth_write = true;
pipe.depth_stencil_state.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL;
line_pipeline = SDL_CreateGPUGraphicsPipeline(device, &pipe);

/* Overlay pipeline: depth test OFF */
pipe.depth_stencil_state.enable_depth_test = false;
pipe.depth_stencil_state.enable_depth_write = false;
overlay_pipeline = SDL_CreateGPUGraphicsPipeline(device, &pipe);
```

### Per-frame upload and draw

```c
/* 1. Reset counts */
state->world_count = 0;
state->overlay_count = 0;

/* 2. Accumulate debug shapes */
debug_grid(state, 20, 1.0f, gray);
debug_axes(state, origin, 2.0f, true);   /* overlay */
debug_box_wireframe(state, min, max, orange, false);

/* 3. Upload (map → copy → unmap → copy pass) */
void *mapped = SDL_MapGPUTransferBuffer(device, transfer_buf, true);
SDL_memcpy(mapped, vertices, total * sizeof(DebugVertex));
SDL_UnmapGPUTransferBuffer(device, transfer_buf);
/* ... copy pass to GPU buffer ... */

/* 4. Draw */
if (world_count > 0) {
    SDL_BindGPUGraphicsPipeline(pass, line_pipeline);
    SDL_DrawGPUPrimitives(pass, world_count, 1, 0, 0);
}
if (overlay_count > 0) {
    SDL_BindGPUGraphicsPipeline(pass, overlay_pipeline);
    SDL_DrawGPUPrimitives(pass, overlay_count, 1, world_count, 0);
}
```

### Debug shape helpers

```c
/* Single line segment (2 vertices) */
static void debug_line(state, vec3 start, vec3 end, vec4 color, bool overlay);

/* Ground grid on XZ plane */
static void debug_grid(state, int half_size, float spacing, vec4 color);

/* Coordinate axes: red=X, green=Y, blue=Z (6 vertices) */
static void debug_axes(state, vec3 origin, float size, bool overlay);

/* Circle from line segments (2 × segments vertices) */
static void debug_circle(state, vec3 center, float radius, vec3 normal,
                          vec4 color, int segments, bool overlay);

/* Wireframe AABB (24 vertices = 12 edges) */
static void debug_box_wireframe(state, vec3 min_pt, vec3 max_pt,
                                 vec4 color, bool overlay);
```

### Shader (HLSL)

```hlsl
/* Vertex shader — transform position, pass through color */
cbuffer DebugUniforms : register(b0, space1)
{
    column_major float4x4 view_projection;
};

struct VSInput  { float3 position : TEXCOORD0; float4 color : TEXCOORD1; };
struct VSOutput { float4 clip_pos : SV_Position; float4 color : TEXCOORD0; };

VSOutput main(VSInput input) {
    VSOutput output;
    output.clip_pos = mul(view_projection, float4(input.position, 1.0));
    output.color = input.color;
    return output;
}

/* Fragment shader — pass-through color */
float4 main(PSInput input) : SV_Target { return input.color; }
```

## Lesson reference

See [Lesson 19 — Debug Lines](../../../lessons/gpu/19-debug-lines/) for the
full implementation with detailed comments explaining every step.

