A gizmo is a visual debugging or interaction aid used in 3D and 2D engines. It typically represents non-rendered objects like axes, bounding boxes, camera frustums, or physics shapes using simple graphics like lines, circles, and squares. Gizmos are helpful during development to visualize invisible data. They don’t affect gameplay and are usually drawn on top of everything else.

Immediate mode api

To make them as straight forward to use you have to draw them every frame with an immediate mode api. Every frame you call add_square(topleft, bottomright, blue) and there will be square drawn at that point. If you don’t call that function that frame, it’s not drawn. This is in contrast to a retained api where you declare which gizmos are there, and the continue to get drawn until they are removed.

Recording commands

You can’t directly tell the GPU to draw a line somewhere using sokol, so instead of all gizmo commands are recorded in a temporary vertex buffer that get’s cleared every frame.

GPU pipeline

The pipeline definition has two vertex attributes and we want the shader to draw lines between the vertices

{
    primitive_type = .LINES,
    layout = {
        attrs = {
            ATTR_texcube_position = {format = .FLOAT3},
            ATTR_texcube_color0 = {format = .FLOAT4},
        },
    },
    cull_mode = .NONE,
    depth = {compare = .ALWAYS, write_enabled = false},
}
// Vertex type is
GizmoVertex :: struct {
    pos:   Vec3,
    color: Vec4,
}

Drawing every frame

To draw the recorded gizmos every frame you need to call render_gizmos somewhere between beginning and ending a render pass, preferably at the end so they get drawn on top.

render_gizmos :: proc(mvp: Mat4) {
    vs_params_gizmo := Vs_Params_Gizmo {
        mvp = mvp,
    }
    sg.apply_pipeline(g.gizmo.pip)
    sg.apply_bindings(g.gizmo.bind)
    sg.apply_uniforms(
        UB_vs_params_gizmo,
        {ptr = &vs_params_gizmo, size = size_of(vs_params_gizmo)},
    )
    sg.draw(0, len(gizmos), 1)
}

Shader

It comes with a very simple shader. It is most useful to draw gizmos in world space instead of screen space so we pass the camera projection matrix to the shader and project the gizmo position to screen space.

@header package game
@header import sg "sokol/gfx"
@ctype mat4 Mat4
 
@vs vs
layout(binding=0) uniform vs_params_gizmo {
    mat4 mvp;
};
 
in vec4 position;
in vec4 color0;
 
out vec4 v_color;
 
void main() {
    gl_Position = mvp *position;
 
    v_color = color0;
 
}
@end
 
 
 
@fs fs
 
in vec4 v_color;
out vec4 frag_color;
 
void main() {
    frag_color = v_color; 
}
@end
 
@program gizmo vs fs