Builtin Library
README.md
debugViz
The shared edit-mode debug-visualization core. Owns the ONE anchor entity, the ONE vertex-colour line material, the ONE batched vertex/index compute-buffer pair, and the ONE render feature that every debug-visualization gizmo draws through. Individual gizmos (bounds boxes, camera frustums, ...) are providers — small modules that append their own geometry into the shared batch every tick, instead of each owning their own GPU state.
entity.spawn("DebugViz"):component.add("DebugViz")
How it fits together
DebugViz.componentowns the lifecycle: registers the built-in providers and callsdebugViz.update(dt)every edit-mode tick;onDisable/onDestroycalldebugViz.teardown().debugViz.registerProvider(name, fn)registersfn(emit)undername. Registering an existing name replaces its function.debugViz.update(dt)calls every registered provider with anemithandle, which records what each call asked to draw, then uploads the combined vertex/index buffers (grow-only capacity). The upload follows what changed: a tick that drew the same thing writes nothing, a tick where some draws changed in place (the same commands, the same vertex counts) writes only their vertices, and a tick where a draw appeared, went or changed its vertex count writes the whole batch.emit.section(changed, draw)marks a part of a provider's drawing that changes apart from the rest:draw(emit)runs whenchangedis true or the section has not drawn before, and otherwise its last recording is replayed without runningdraw.debugViz.state()returns the latest{ vtx, idx, anchor, indexCount, uploadedBytes, shapes, icons }for the render feature to draw.@builtin::renderFeatures.debugVizreadsstate()every frame and draws the batch through one Draw pass, using@builtin::shaders.debugGeometry— a solid-colour shader that reads each vertex's baked colour, so every provider can use its own colour in the same draw call. Each instanced shape (below) draws in a Draw pass of its own.
The draw needs an anchor entity with an IDENTITY world transform and a render-object (so the Draw pass has a valid instance slot and a material to override) — this module owns one, spawned lazily, internal so it stays out of the inspector and excluded from scene saves.
Writing a provider
local debugViz = require("@builtin::modules.debugViz")
local function tick(emit)
emit.box({ x = -1, y = -1, z = -1 }, { x = 1, y = 1, z = 1 }, { 1, 0, 1, 1 })
emit.line({ x = 0, y = 0, z = 0 }, { x = 0, y = 5, z = 0 }, { 1, 0, 1, 1 })
end
debugViz.registerProvider("my_gizmo", tick)
emit.box(min, max, color?) and emit.line(a, b, color?) append world-space
geometry into the shared batch for this tick; color is an {r, g, b, a}
array baked per-vertex (defaults to amber if omitted). A provider that needs
to exclude the shared anchor entity from its own scene queries reads
debugViz.anchorId().
A provider that hands the same packed geometry back every tick
(emit.rawLineSoup(bytes, vertexCount)) costs nothing to upload while the
geometry stays the same string.
Instanced shapes
emit.shape(entityId, key, bytes, vertexCount) draws a line-list blob in the
local space of entityId. The blob is uploaded once under key and drawn once
per entity that asks for it, through that entity's own world transform: any
number of entities sharing a shape cost one GPU buffer, and moving them costs
no per-tick work. A key names immutable bytes (geometry that changes takes a
new key), the buffer lives for as long as some entity asks for the key each
tick, and the entity needs a render object (a Mesh). debug_wireframe draws
every mesh this way.
API
debugViz.registerProvider(name, fn, opts?)— register (or replace) a provider.fn(emit)runs once per edit-mode tick.opts.labelis the name the Debug menu and the Rendering panel list the overlay under: text as written, or ani18n.textvalue (i18n.text("hud.my_gizmo")), which reads in the editor's language. Without one the overlay is listed byname.opts.defaultis whether the overlay starts on, andopts.available()whether it currently has anything to show.debugViz.providers()— every provider that currently has something to draw, in registration order, as{ name, label, enabled }with the label in the editor's language.debugViz.anchorId()— the anchor entity's id, ornilbefore the first tick.debugViz.update(dt)— run every provider and upload what their drawing changed.debugViz.state()— the latest batched draw, ornilbefore the first tick.debugViz.teardown()— tear down the feature, material, buffers, and anchor entity. Registered providers stay registered.debugViz.MATERIAL— the registry key of the shared vertex-colour line material the render feature draws with.