Expand description
GPU rendering backend for retroglyph: Vulkan, Metal, and D3D12 from a single codebase via
[wgpu].
§Architecture
WgpuBackendBuilder holds configuration (fonts, grid size, integer scale) and
builds a WgpuRenderer. The glyph source is a static
FontChain (a single BitmapFont is a chain of one); every font in the chain is
grid-packed into one R8 array-texture atlas and addressed by a flat slot id (see
retroglyph_window::atlas). The renderer keeps one CPU-side instance array per grid layer and
creates its device lazily, when the windowing loop calls Presenter::init_surface:
WgpuBackendBuilder (font, grid size, scale)
| .build()
v
WgpuRenderer
implements retroglyph_window::Presenter (an Output supertrait)
wrapped by retroglyph_window::WindowBackend to become a full Backend
(WindowBackend owns the input event queue and the no-op Cursor)
|
| init_surface(window) -> wgpu Device + Queue + Surface, then GpuResources
v
one render pass per present(): every grid layer back to front, each drawn as
backgrounds then coverage-blended glyphs (then sprites), from one instance
buffer uploaded once per frame.A cell costs 16 bytes and no index buffer: the vertex shader derives the quad’s corners from the
vertex index and the cell’s (column, row) from the instance index, so the only per-instance
data is the glyph slot, two colors, the sub-cell offset, and the compositing flags.
This backend composites grid layers itself on the GPU
(composites_layers returns true): it receives the raw layered
stream from the core Terminal and draws each layer back to front, so an empty cell in a higher
layer lets the layer beneath show through while an occupied cell is opaque, matching
retroglyph-software’s per-pixel occlusion. It requests full frames
(needs_full_frame returns true) and redraws every cell of every
layer each frame, so there is no orphaned-pixel problem from sub-cell glyph spill.
§Choosing between this and retroglyph-gl
Both are GPU backends drawing the same instanced-quad pipeline, and both produce pixel-identical
output (each is checked against the retroglyph-software CPU rasterizer). They differ in which
driver stack they reach and what they cost to depend on:
retroglyph-wgpu | retroglyph-gl | |
|---|---|---|
| APIs | Vulkan, Metal, D3D12 | OpenGL 3.3, WebGL2 |
| Browser | yes, WebGPU (see Platform support) | yes, WebGL2 |
unsafe in the backend | none | unavoidable (every GL call) |
| Direct dependencies, transitively | 85 crates | 54 crates |
| Clean debug build of the crate | 15s | 7s |
| Offscreen render tests | every platform | Linux/EGL only |
The dependency and build-time figures are for one host (macOS, --all-features); the ratio is
what matters, not the absolute numbers. The difference is naga (the shader front end that
compiles this crate’s WGSL) plus wgpu-core (the validation and state-tracking layer that
makes the API safe), not the three backend APIs: only one hardware abstraction layer compiles
per platform, since wgpu-hal’s backends are target-gated. A macOS build pulls the Metal
stack and no Vulkan; a Linux build pulls ash and no Metal.
Both cover the browser today (WebGPU here, WebGL2 there); pick retroglyph-gl when a smaller
dependency tree matters more than the rest, or this one for validation-layer diagnostics, a
modern driver path, and a backend with no unsafe in it. On wasm32 this backend’s device is
ready asynchronously (see Platform support), where retroglyph-gl’s WebGL2
context is ready synchronously; that is the one real difference the browser adds to the choice.
§Performance
A 200x60 grid with three layers (36000 cells, 562 KiB of instance data) costs 266us per frame
end to end: flattening the layers, uploading, encoding six draws, submitting, and waiting for
the GPU to finish. That is roughly 4% of a 60 Hz frame budget, on an M-series laptop at
--release, and it is larger than any grid the examples use.
The number is here to set expectations, not to invite tuning: a character grid is a trivial workload for a modern GPU, and the design choices above (deriving position from the instance index, packing every layer into one upload) are about keeping the per-frame bandwidth small rather than about winning a benchmark. Emitting four real vertices per cell instead, with explicit positions and UVs, is the other common shape for this renderer and would cost roughly six times the per-frame bytes.
§Platform support
Native (Vulkan, Metal, D3D12) and the browser (WebGPU) both work.
Presenter::init_surface is synchronous, but request_adapter and request_device are not,
and a browser’s main thread has no way to block on a future the way native does. So the two
targets take different paths through the same call: Instance::create_surface is synchronous
everywhere, so on wasm32 init_surface creates the surface from the window’s canvas, spawns the
adapter and device request with wasm_bindgen_futures::spawn_local, and returns immediately;
present polls the result each frame and draws nothing until it lands.
Native, by contrast, blocks on the same requests with pollster::block_on inside
init_surface itself, so the device is always ready by the time it returns. See
crate::gpu::PendingGpu for the shared-state cell this deferral is built on.
The one user-visible consequence is on wasm32 only: the first frames after startup are blank (whatever was drawn before the device exists is not shown) until the adapter and device resolve, typically well under a second. Native has no such gap.
retroglyph-gl also covers the browser, through WebGL2, whose context creation is synchronous
and so needs no equivalent deferral. Pick between the two using the table above and the
dependency/build-time comparison it links to.
§Environment variables
wgpu reads its own configuration from the environment, and this crate passes it through
rather than overriding it:
| Variable | Effect |
|---|---|
WGPU_BACKEND | Restricts the backends to try (vulkan, metal, dx12). |
WGPU_POWER_PREF | low or high; defaults to low, since a character grid is a handful of draw calls per frame. |
WGPU_VALIDATION / WGPU_DEBUG | Toggle the driver’s validation and debug layers. |
§Features
This crate has no default features; every feature below is optional and off unless enabled.
§default-font
⚪ Optional.
Embeds the Unscii 16 default font so a caller can build a renderer with no font of its own.
Forwards to retroglyph-window’s default-font feature.
§dev
⚪ Optional.
Forwards retroglyph-core’s dev feature, which forces development diagnostics on in a build
that would otherwise compile them out (see retroglyph_core::dev).
§tilesets
⚪ Optional.
PNG sprite/tileset support: decodes sprite sheets into an RGBA array-texture atlas and draws them in a third, source-over blended pass per grid layer.
Forwards to retroglyph-window’s shared tileset decode.
Re-exports§
pub use config::WgpuBackendBuilder;pub use config::WgpuBackendError;
Modules§
- config
- Configuration, builder, and error types for the wgpu backend.
- font
- Bitmap glyph fonts and CP437 mapping, shared by retroglyph’s graphical backends.
Structs§
- Bitmap
Font - A 1-bit-per-pixel bitmap glyph font.
- Font
Chain - The glyph source a backend draws from: a primary
BitmapFontplus an ordered list of fallback fonts. - Wgpu
Renderer - The live wgpu renderer: a
Presenter, wrapped inWindowBackendto form a fullBackendfor the windowing loop.
Enums§
- Surface
Error - A failure creating or driving the wgpu device and surface.