Video summary
I Built a Custom GPU-Driven Rendering Pipeline (10x Faster)
Main summary
Key takeaways
Core idea / motivation
After switching the physics system to use a voxel representation (collisions generated from voxel data rather than mesh colliders), the author questions the usual rendering pipeline.
Instead of the standard workflow:
- artist-made mesh → engine mesh asset → renderer draws mesh triangles
they ask whether they can remove the meshing step and render directly from voxel-generated vertex/index data.
Baseline performance (before the new rendering approach)
- Rendering at draw distance = 40 chunks
- Game is unstable: roughly 25–40 FPS
- Occasional drops/spikes down by about ~10 FPS
Stage 1: “Draw directly”, but keep a chunk pipeline
They still generate chunk geometry via a CPU meshing system that outputs a “traditional mesh” (including face normals and bounding boxes).
- Problem: mesh creation is slow/wasteful
- GPUs don’t inherently need “mesh objects”—they need vertex/index buffers
Stage 2: Indirect rendering + “mega-grid” for fewer draw calls
They move toward a GPU-driven approach with a mega-grid and indirect rendering:
GPU allocation (done once)
Instead of rebuilding combined geometry every edit, they allocate GPU buffers upfront:
- Vertex buffer
- Index buffer
- Instance index / indirection structure
- Additional per-instance buffers:
- transforms
- bounding/clipping data
- indirect draw arguments
Filling free space
As chunks change, they fill “free space” in the mega-grid as needed, supporting:
- adding/removing sections
- working with gaps
Indexed indirect rendering
- One GPU argument buffer contains one draw command per sub-region/part
- The GPU processes them like one large structure
Stress test proof
- Rendering 10,000 changing quads
- Smooth performance (not instanced; each quad treated as unique grid data)
- Validates the technique
Stage 3: Chunk rendering results + clipping challenge
Applying the approach to real chunk rendering:
- FPS improves to around ~50 FPS vs ~30 baseline
- But there are strong latency spikes
Initial issue
- Fragments behind the camera still render
Proposed fix: camera clipping per grid
They consider updating per-grid render arguments with camera-based clipping against the truncated pyramid (view frustum).
- Doing boundary checks on the CPU per-grid/per-fragment is too slow
- They offload boundary updates to a compute shader
Outcome
- Compute shader reduces spikes somewhat
- Overall FPS stays around ~50 FPS
- They conclude clipping overhead cancels out the benefits
Stage 4: Octree-based frustum pruning (major win)
Key observation: fragment positions aren’t random; they’re structured—so per-fragment checks aren’t efficient.
They build an octree-like hierarchy over fragments/bounds:
- Test a large group against the frustum
- If outside: discard the whole group
- If inside/partial: subdivide into 8 children and continue
- Optimization: if a node is fully inside, accept all descendants without further testing
Result
- FPS rises dramatically to ~170 FPS
- Still occasional spikes, down to about ~10 FPS
Stage 5: Paging (reduce mega-grid search cost)
They attribute remaining spikes to the mega-grid being too large:
- Even without rebuilding, the system must find free space
- Searching can become expensive in worst cases
Solution: paging
- Split into multiple mega-grids once a limit is reached
- Prevents running out of space and reduces worst-case editing/search cost
Outcome
- FPS remains around ~170 FPS (stable)
- Pushing beyond ~200 is still unresolved
Stage 6: “Vertex packing” to shrink grid size and boost throughput
To exceed 200 FPS, they reduce per-vertex data size, keeping the same geometry content.
Initial data estimate (~48 bytes/vertex)
- Position (float3): 12B
- Normal: 12B
-
UVW: 12B Plus extra:
-
Color: 4B
- Ambient occlusion (AO): 1B
- Lighting: 2B
- Light settings: 1B
- Instance index: 4B
Total: ~48 bytes per vertex (Indirect rendering also requires instance index data.)
Key compression ideas
-
Remove normals
- Normals are always flat, so rebuild them during rendering
-
UV packing via lookup table
- Replace 8 bytes UV with a 1-byte UV index
- Refer to a 256-entry lookup table
-
Texture index bit limits
- Compact representation for W/texture index
- Constrain to ~2048 textures (fits ~11 bits)
- If more textures are needed, split materials
-
Color downsizing
- Reduce color from 4 bytes → 2 bytes
- About ~32,000 shade variants
-
Pack small per-vertex values together
- Combine texture index + AO + lighting + light settings into ~3 bytes using bit shifting
-
Position quantization
- Reduce position storage from 12 bytes → 6 bytes
- Assumes voxel-related spatial constraints (small precision loss for major savings)
Final result
- Vertex size reduced from 48B → ~16B per vertex
- Performance improves to over 200 FPS
- No noticeable lag
Additional scaling result: increase draw distance substantially
Previously:
- Increasing draw range beyond 40 chunks caused crashes
After optimizations:
- Test draw distance = 120 chunks
- Not perfectly smooth, but “incredible” at full resolution
Complexity scaling note
Performance complexity doesn’t scale linearly:
- Quadrupling area from doubling draw distance implies ~9× harder workload
High-end reported metrics
- ~1 billion vertices rendered
- ~1.8 million chunks loaded
- ~8 billion blocks
They also discuss future improvements:
- more granular remote chunk handling
- adding LOD
Main speaker / source
- The video author/developer (the narrator of the dev diary)
- Speaking in first person
- Building the custom GPU-driven rendering pipeline for Planet Smith
Rate this summary
Your feedback will help improve summaries.
Improve this summary
Reprocess with a stronger model when the summary feels incomplete or inaccurate.
Translate summary in another language
Ask questions to this video
Chat for follow-up questions, clarifications, and source-backed answers.