Build in public

Rendering, built from first principles and written about.

A physically based renderer and glTF 2.0 viewer, on a functional core, with a pipeline you can see.

A renderer in C# and Vulkan that aims to open any glTF 2.0 asset and render it the way the spec says it should look. The frame is a pure function, so the pipeline is data, which is why it can be drawn as a graph rather than guessed at. Every technique ships as working code and a companion post that explains why it exists.

Roadmap

Every confirmed post, in reading order. Status reflects the current state of the draft, not a promise of when it will ship.

glTF Compliance

A specification decides what correct means.

08 Planned

Letting someone else decide what correct means

Why the lab handed correctness to a specification. What a personal renderer gets wrong when the only test is the author's eye, what glTF 2.0 actually requires, and what the importer was quietly dropping along the way. Also the scope decision taken before the work rather than during it: animation, skinning and morph targets sit outside a named static profile.

09 Planned

Building the ruler first

Building the ruler before the thing it measures: a device with no window, a checked-in golden, and a comparison metric that is pure and tested without a GPU. The argument is the ordering, because a milestone of specification work judged by eye means finding out at the end. What made it cheap is that rendering into a viewport, a thumbnail or a file was already the same code.

10 Planned

The material you only half imported

Reading the texture slots the loader skipped, and the consequence: the G-Buffer runs out of room and grows a fourth attachment, because emissive is a colour rather than a scalar. Plus sampler state, the half of importing a texture that is easy to forget, and the material leaving push constants for a buffer, because five texture slots with a sampler and a transform each do not fit in 128 bytes.

11 Planned

What a G-Buffer cannot hold

Alpha masking is a discard and costs nothing. Alpha blending is a contradiction: a G-Buffer stores one surface per pixel, and a blended surface is by definition not the only one there. The forward transparent pass this forces, and why the predicted second copy of the shading code never arrived.

12 Planned

The transform no TRS can spell

The phase where reading the specification properly changed the data model. A negative determinant reverses winding, so the front face becomes dynamic state per draw rather than another axis on the pipeline key. A rotation between two non-uniform scales composes to a shear, and no TRS spells shear, so a drawable gives up its transform for a world matrix while the node keeps its TRS.

13 Planned

Grading your own renderer

Pointing the harness at the Khronos sample assets and publishing what comes back: 67 pass, 0 fail, 3 outside the profile, of 70 models. The metric is LDR-FLIP, checked bit for bit against the reference implementation. The claim is a reviewed golden, which catches every regression and no misreading of the specification that the review shared.

14 Planned

What the table named

A conformance table's real output is a work list. Twenty failing rows named three missing features and nothing else: flat normals for a primitive that ships none, glTF's default material for a primitive with no material, and the primitive modes that are not triangle lists. Ends on the run that moved exactly the twenty rows it meant to and left the other forty-nine byte-identical.

Pipeline as Graph

The frame stops being a sequence of calls and becomes a picture.

15 Planned

The shape of a frame

Drawing the pipeline: layering passes by longest path, ordering within a layer to reduce crossings, and weighting nodes by measured GPU cost. The argument underneath it is that the layout is a pure function of the compiled graph, and an imperative renderer has nothing to hand such a function. Passes and resources are both nodes, which is the decision the post has to defend.

16 Planned

Where the memory goes

Resource lifetimes as a timeline under the picture: one bar per resource, from the pass that brings it into existence to the last pass that still needs it, on the same horizontal axis as the graph above it. This is the part of a frame a dependency picture cannot show, that four G-Buffer images are live at once and three are dead before the forward pass runs. Aliasing is what the lifetimes make possible, not something the compiler does.

Field Notes

Infrastructure decisions and architecture records.

Drafting

Choosing a Vulkan allocator

Field note. Why the engine centralized its own allocator instead of adopting VMA. Only one of VMA's three benefits was actually about allocation, and that was the one with no evidence behind it.

Read draft →
Drafting

Picking a swapchain present mode

Field note. Why the lab uses FIFO_RELAXED with MinImageCount instead of the AAA Mailbox default: honest frame-time signal over smoothed presentation.

Read draft →
Planned

When the renderer becomes an editor

Field note. The pipeline shift where the scene stops being the swapchain target and becomes an image a panel draws. Covers the UI backend you have to write yourself, input-capture gating, and why swapping the UI library later cost so little.

Planned

Shaders that reload themselves

Field note. Watching shader sources, recompiling to SPIR-V, and swapping pipelines without restarting. The decision worth recording is how a failed compile keeps the previous pipeline live instead of dropping the frame. Includes are watched too, so editing one recompiles everything that reads it.

Planned

From stats overlay to scene inspector

Field note. The editor's Elm-style Model/Msg/Update layer: how a pure reducer lets panels edit transforms, lights, and materials live while the renderer stays a pure consumer of the scene snapshot.

Planned

Sampling a texture inside a branch

Field note. A texture sampled inside divergent control flow takes an implicit level of detail, and the derivatives that needs are undefined, whatever the source said before the driver lowered it. It turned up three times during the compliance work, once as a ternary that moved 107 pixels of a golden containing nothing the branch was written for.

Planned

One shader, eight pipelines

Field note. One SPIR-V module and eight G-Buffer pipelines, with no variant table in the build. Specializing alpha masking and topology rather than branching on a uniform keeps the discard statically unreachable, so early depth testing survives on the opaque variant, and a topology is baked into a pipeline and cannot be told at record time.