UI Overlay Pipeline
UIVertex Format
3 attribute descriptions, 32-byte stride.
Pipeline Differences from 3D
The UI pipeline renders within the same render pass as the 3D pipeline, after all 3D entities, before vkCmdEndRenderPass.
Push Constants
Used by the vertex shader to convert pixel coordinates to NDC.
Font Atlas
Created in createFontResources():
- Load
assets/fonts/RobotoMono-Regular.ttfviastb_truetype - Bake ASCII 32-126 (95 glyphs) at 20px height into a 512x512 bitmap
- Upload as
VK_FORMAT_R8_UNORMtexture (single-channel) - Store glyph metrics in
glyphs_[95]array (UV coords, advance, offset)
If the font file is missing, falls back to a 1x1 white placeholder (UI draws solid color quads only).
Font sampler uses LINEAR filtering and CLAMP_TO_EDGE addressing.
Vertex Shader (ui.vert)
Converts pixel coordinates [0, screenSize] to NDC [-1, 1]. Z is always 0 (no depth).
Fragment Shader (ui.frag)
Samples the R8 atlas for alpha coverage, multiplies by vertex color alpha. For non-text quads (background panels), the UV maps to the white region and alpha = 1.0.
Debug Overlay Geometry
buildDebugOverlayGeometry() constructs the overlay each frame:
- Clears
uiVertices_vector - Smooths FPS with exponential moving average:
smoothedFps_ = 0.95 * smoothedFps_ + 0.05 * (1/dt) - Calls
appendQuad()for the background panel (semi-transparent dark) - Calls
appendText()for each line: FPS, entity count, camera position, camera target - Copies final vertices into the persistently-mapped host-visible buffer for the current frame
appendQuad(x, y, w, h, color)
Generates 6 vertices (2 triangles) for a solid-color rectangle. Uses UV (0,0) which maps to a white pixel in the atlas, so color comes entirely from vertex color.
appendText(text, x, y, color)
For each character, looks up glyphs_[c - 32] to get atlas UVs and metrics, then emits 6 vertices (2 triangles) with proper positioning using xoff, yoff, and xadvance.
Host-Visible Vertex Buffers
UI uses UI_MAX_VERTICES = 4096 per frame, with one buffer per frame-in-flight (2 total). These are:
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | HOST_COHERENT_BIT- Persistently mapped at init (never unmapped)
- Written to directly via
memcpyeach frame
This avoids staging buffer overhead since UI geometry changes every frame.
Recording UI Commands
recordUICommands(commandBuffer):
- Copy
uiVertices_to mapped buffer:memcpy(uiVertexBuffersMapped_[currentFrame_], ...) - Bind UI pipeline
- Set viewport and scissor (same as 3D)
- Bind UI vertex buffer
- Bind UI descriptor set (font atlas)
- Push screenSize constant
vkCmdDraw(uiVertexCount_, 1, 0, 0)-- single non-indexed draw call
Adding new UI elements: Add geometry generation in buildDebugOverlayGeometry() using appendQuad() for rectangles and appendText() for text. Keep total vertices under 4096.
Changing font size: Modify fontPixelHeight_ (default 20.0f) in renderer.h. The atlas is baked at init, so this is a compile-time change.
Adding new text lines: Add snprintf + appendText() calls in buildDebugOverlayGeometry(). Increment the Y position by fontPixelHeight_ for each line.