debugDraw
Procedural geometry builders for GPU debug visualization — wireframe boxes, spheres, capsules, crosses, octahedra, line segments, and camera-facing icon billboards. Pure geometry: every builder appends packed vertex bytes to a caller-supplied accumulator and returns how many vert…
debugDraw
Procedural geometry builders for GPU debug visualization — wireframe boxes,
spheres, capsules, crosses, octahedra, line segments, and camera-facing icon
billboards. Pure geometry: every builder appends packed vertex bytes to a
caller-supplied accumulator and returns how many vertices it added. Line
geometry is a non-indexed line soup (each line is two consecutive vertices)
and billboards are a triangle soup (each quad is six consecutive vertices),
so both draw through one static, grow-only sequential index buffer (0,1,2,…)
that never needs re-uploading per frame — the topology comes from the material.
Nothing here touches compute.* or renderer.* — callers own the buffers and
the Draw pass.
local debugDraw = require("@builtin::modules.debugDraw")
local vtxParts = {}
local verts = 0
verts += debugDraw.appendBox(vtxParts, bounds.min, bounds.max, { 1, 0.8, 0, 1 })
verts += debugDraw.appendLine(vtxParts, a, b, { 0, 1, 1, 1 })
local vtxBytes = debugDraw.buildVertexBytes(vtxParts)
local vtx = substrate.createBuffer({
name = "my.vtx", type = "f32", len = math.ceil(#vtxBytes / 4),
kind = "gpu", usage = { "vertex" },
})
vtx:writeBytes(vtxBytes)
-- One static sequential index buffer, grown only when the vertex count does.
local idx = substrate.createBuffer({
name = "my.idx", type = "f32", len = verts, kind = "gpu", usage = { "index" },
})
idx:writeBytes(debugDraw.buildSequentialIndexBytes(verts))
Vertex layout
Every packed vertex matches the engine's standard Vertex layout (position,
normal, uv, joints, weights, node_index, tangent, color — 92 bytes,
little-endian) so the resulting buffer draws through the ordinary vertex
pipeline via a Draw pass. Debug geometry only needs position + color; every
other field is filled with a default.
API
Each builder appends line-soup vertices to vtxParts and returns the vertex
count it added:
appendLine(vtxParts, a, b, color?)— one segment (2 verts).appendBox(vtxParts, min, max, color?)— axis-aligned box, 12 edges (24 verts).appendOrientedBox(vtxParts, center, rotation, halfExtents, color?)— a posed box.appendWireSphere(vtxParts, center, radius, color?)— three orthogonal rings.appendWireCapsule(vtxParts, center, rotation, radius, halfHeight, color?)— two rings + connectors.appendCross(vtxParts, center, size, color?)— a 3-axis marker (6 verts).appendOctahedron(vtxParts, center, size, color?)— a diamond marker (12 edges).appendBillboard(vtxParts, center, color?)— one camera-facing quad (6 triangle-soup verts) atcenter; every corner storescenterand its uv corner, and thedebugBillboardsurface shader expands it toward the camera.
Helpers:
packVertex(x, y, z, color)— pack one vertex; for building cached geometry blobs.packVertexUV(x, y, z, u, v, color)— pack one vertex carrying an explicit uv (for billboard quads).quatRotate(q, v)— rotate a vector by a unit quaternion.buildVertexBytes(vtxParts)— concatenate the packed vertices into buffer bytes.buildSequentialIndexBytes(count)— the fixed0,1,2,…,count-1line-list index buffer.VERTEX_STRIDE— 92, the byte stride of one packed vertex.
See @builtin::modules.debug_bounds for a caller and
@builtin::renderFeatures.debugViz for the Draw pass that renders the batch.
Scoped to this part · feeds back into the world's score.