Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

XI · Rendering

Verified against Minecraft 26.3 · Part XI · One thread, a hundred-odd times a second, turning a world nobody can see into a picture — and two layers of machinery underneath that never touch the world at all.

The renderer is not allowed to look at the world. A frame is one call to Minecraft.renderFrame, on the same thread that ticked the world a moment earlier, and it runs twice over: once to copy the live game into a pile of value objects, and once to draw those objects with the live game held at arm’s length. Everything in this part is either that wall, or something feeding it, or something on the far side of it working from a copy. And the claim the part makes is that nothing you see on screen is the world; it is a copy of the world, and every seam a player notices is two copies disagreeing. A mob pins in place under /tick freeze while the item in your hand keeps swaying, because each was copied at a partial tick of its own. A chest’s lid stops and the chest in your hand does not, for the same reason one step further out. And terrain fills in outward as you fly because the copy that decides what is visible can only reach as far as the copies that already exist — which is the same disagreement again, now between the world and the meshes that stand in for it.

Besides the renderer’s own per-tick half, three things escape the frame, and each of them is a page: particles are stepped from the tick, most sections are meshed on a background pool, and the atlases are built by a resource reload and animated before each frame.

It is also the largest thing on the client. client/renderer, client/model, client/particle, com/mojang/blaze3d and com/mojang/renderpearl together come to 1,291 files and 98,283 lines — counted the way the atlas counts a part, leaving out the hatched packages — against 358 files and 48,834 lines for net/minecraft/server counted the same way. net/minecraft/server has a little over a quarter as many files as the client’s renderer, and about half its lines.

The shape of the part

Part XI is a substrate under a pipeline. Two of its pages — the window and Blaze3D — are what the renderer stands on: neither has a trace through the world, and Blaze3D is cited far more than it cites. The rest is a pipeline. The frame opens the part ahead of the substrate it stands on, because it is the shortest way to see the whole shape at once and because a reader who has watched one frame end to end has a reason to care what a GpuDevice is.

flowchart TD
    FRAME["1 · The frame"]
    subgraph SUB["the substrate"]
        WIN["2 · The window"]
        B3D["3 · Blaze3D"]
    end
    subgraph PIPE["the pipeline"]
        VIS["4 · Visibility and the frame graph"]
        MESH["5 · Section meshing"]
        MOD["6 · Models and atlases"]
        ENT["7 · Entity rendering"]
        BEN["8 · Block-entity rendering"]
        SKY["9 · Lightmap, fog and sky"]
        PART["10 · Particles"]
        POST["11 · Post-processing"]
        VIS --> MESH --> MOD --> ENT --> BEN --> SKY --> PART --> POST
    end
    FRAME --> SUB --> PIPE

The part as a substrate and a pipeline, numbered to the watch order, the frame first: an arrow is the next lecture, which is not the order a frame runs in.

The arrows in that figure are the order to watch in, which is not the order a frame runs in. Visibility hands section meshing the sections to compile, and a frame’s terrain is drawn from meshes an earlier frame made, so the fourth and fifth lectures feed each other. Inside a frame the order is different — the sky pass is declared before the main one, the lightmap is built before the world is drawn at all, and one of the five post chains runs as passes of the world’s own graph while the other four build one and throw it away.

Before you start

The client loop from Part X, and not optionally: it is the page that says when a frame happens and how many ticks ran before it, and this part begins exactly where that page’s frame zone opens. It is one of the two longest pages in Part X, so budget for it.

The resource system from Part II before models and atlases, which is a reload listener and leans on the barrier semantics taught there rather than restating them.

Environment attributes and timelines from Part IV before lightmap, fog and sky. Part IV owns that system; Part XI is one of its client-side consumers and does not re-teach it.

The client level, for what the thing being drawn is — a Level with its authority removed — and for the two ways LevelExtractor is reached, pushed and pulled.

Watch in this order

  1. The frame — one method, two halves, and a wall between them. Fifteen profiler zones, six clocks disagreeing on purpose, and a failed surface acquisition that costs you the picture but not the work.
  2. The window — the substrate nothing else admits to needing. A retry loop that settles the graphics backend before the window exists, nineteen kinds of window event and the six the game is never told about, and NativeImage, the seam between a file and a texture.
  3. Blaze3D — the game’s own graphics API, and the part’s vocabulary page. Four validating façades over two real backends, one of which is Vulkan and is the larger of the two.
  4. Visibility and the frame graph — what the frame decides to draw, and in what order. A reachability walk whose asymmetry — uncompiled sections stop it, empty ones do not — is why terrain reveals itself outward.
  5. Section meshing — where the triangles came from. A block is placed, a halo of positions goes dirty, the sections it touches are rebuilt from a snapshot — by default inline for your own block, on a worker for the server’s — and the new mesh swaps in all at once.
  6. Models and atlases — the reload pipeline behind block, item and particle quads. Twelve atlases stitched in parallel, one barrier, and a quad whose chunk layer is read out of its sprite’s pixels.
  7. Entity rendering — every entity in the world, in four stages, none of which is called render. The zombie is animated at least twice per frame.
  8. Block-entity rendering — the same four stages with three differences that show. A chest’s block model is empty, a block entity is never frustum-tested on its own, and the chest in your hand is drawn by a different renderer at a different partial tick.
  9. Lightmap, fog and sky — what colour all of it is. One question asked by four of the five renderers, most of which no longer know what time it is.
  10. Particles — the part’s policy page: three distance rules for a particle or a level event, enforced in three places and three readers of one setting who disagree about what its values mean, with a break puff that answers to almost none of them.
  11. Post-processing — the closer. Five JSON-declared shader chains, which is how the pause-menu blur and the creeper spectator shader turn out to be the same machine — and a resource pack can rewrite all five and add its own.

Four and five are a pair — one journey seen from its two ends — and so are seven and eight, the second of which is written as the differences from the first. One to three can be watched in order or in the order one, three, two; the window is the page a viewer is most likely to skip and least likely to regret.

Where the part stops

What draws over the world rather than in it — screens, the HUD, the render tree they record into and the text inside them — is Part X, from the GUI render tree onward; what the server chose to tell this client is Part IX. The packages themselves cross the boundary in both directions: the GUI’s render states and the debug renderers under client/renderer count against Part XI while being Part X’s subjects, and client/resources/model counts against Part X while being the subject of models and atlases.

Everything else left out is the families — one class per mob model, one per renderer, one per particle, one file per GL or Vulkan call site — which is why 41% of the part’s lines are named on no page in the book, the second-highest figure in the book after Part V’s. Teaching each family’s shape and declining its instances leaves a great many lines untouched, and that is the trade, made on purpose.

Reference this part uses

Submit phases and feature renderers is the catalogue behind entity rendering and block-entity rendering: the fifteen phases a submitted feature can land in, and the twelve renderers that write the vertices. The threads for the two that matter here — the Render thread most of the part runs on, and the background pool that meshes sections — and diagram lanes for the abbreviations the figures use. Naming drift is worth having open throughout: the world-facing half of the old LevelRenderer is now LevelExtractor, and many of the names a 1.21-era reader reaches for moved with it. The glossary for extract, render state, frame graph, partial tick, atlas, special model renderer and built-in block model.


Rules: names, never code · how the system works, not how the code reads · newest version only · every backticked name passes tools/verify_names.py.