Skip to content

Render Pipeline Overview

Awaken draws your scene with a single forward WebGPU renderer that runs one render() call per frame - this page traces that frame from the ECS world all the way to the swapchain.

One renderer, one frame

Everything on screen comes from one class, Renderer, in packages/render/src/renderer.ts. The editor Viewport, the standalone Player, and every export construct the same renderer over the same GPU context. There is no second draw path that can drift.

The renderer is forward-shaded: geometry is lit in one pass rather than deferred into a G-buffer. All shaders are authored as WGSL string constants inline in packages/render/src/shader.ts (MESH_WGSL, SKY_WGSL, POST_WGSL, …) and compiled into GPU pipelines once, in the Renderer constructor. Nothing is compiled per frame - the per-frame work is culling, buffer writes, and draw calls.

The public entry point is tiny:

ts
renderer.render(world, view, proj);
  • world - the ECS World (entities with Transform + MeshRenderer, plus lights and cameras).
  • view / proj - the camera view and projection matrices (from the active Camera in Play, or the editor OrbitCamera while editing).

Because the geometry rarely changes between frames, the renderer keeps a scene cache: a persistent, GPU-resident instance buffer plus world bounding spheres, resolved materials, merged static chunks, a cull octree, and gathered lights. render() rebuilds that cache only when you call invalidate() (the world changed); otherwise it reuses it and, at most, refits the octree if something moved. This is what keeps large scenes fast - see Performance Systems.

The frame, end to end

A single render() call does CPU preparation, then records GPU work into two command encoders: one for the shadow passes (submitted on its own), then one for everything else (SSAO, the main scene pass, bloom/grade, the selection outline, and the optional camera-preview inset).

CPU preparation

  1. Cache - if the cache is dirty, rebuildCache() walks the ECS Transform + MeshRenderer set, builds the persistent 24-float instance buffer (matrix + colour + metallic/roughness/hasTexture/pickId), computes world bounding spheres, resolves materials, merges static chunks, builds the cull octree, and gathers lights. If only transforms moved, it does a much cheaper octree refit instead.
  2. Camera & shadows - view/proj are multiplied into viewProj, inverted, and the eye position extracted. The cascade shadow matrices are fit from the cached sun direction, and everything is packed into the 116-float camera uniform.
  3. Lights, globals, grade - up to 8 lights are packed; the material-time globals and the 44-float colour grade are written.
  4. Shadow dirty test - each cascade decides whether its cached static layer must re-bake (sun moved, geometry changed, or the texel-snapped region shifted).
  5. Cull - one octree traversal (queryFrusta) covers the camera frustum, only the cascades that must re-bake, and the optional preview camera at once. Optional size-aware distance culling drops small/far objects. The visible set is split into instanced groups and static chunks, then packed into the per-frame visible→slot redirection buffer.

GPU work

  1. Shadow passes (encoder 1) - for each cascade, a dynamic depth layer is rendered every frame and a static depth layer is rendered only when that cascade is dirty (cached otherwise), both into a 2048² depth24plus array texture. This encoder is submitted before the main encoder.
  2. GPU-driven cull (encoder 2, opt-in) - a compute kernel culls the opaque main pass into indirect draw args when enabled and supported. Off by default.
  3. SSAO (opt-in) - a world-position/normal prepass feeds a 16-sample ambient-occlusion fullscreen pass and a blur, which darkens ambient only. These are fragment passes, not a compute kernel (only the GPU cull above is compute). Off by default.
  4. Main pass → MSAA → scene texture - the scene is drawn into a 4× MSAA colour target (by default) and resolved into an offscreen scene texture. When MSAA is off the pass draws straight into the scene texture (WebGPU cannot resolve a single-sample pass). Draw order inside the pass: sky (fullscreen gradient) → grid (editor only, ray-marched y=0 plane) → opaque (instanced groups / indirect / merged static chunks / per-material pipelines) → transparent (alpha-blended, depth-write off) → particles (camera-facing billboards, depth-tested, depth-write off) → overlay linesgizmo.
  5. Bloom + grade - a half-resolution bright-pass, a Gaussian blur, then a composite pass that adds bloom and applies the entire URP-style colour grade from the 44-float grade uniform, writing the final image to the swapchain.
  6. Selection outline - selected meshes are drawn into a mask, then a screen-space edge detect paints a constant-thickness band over the swapchain.
  7. Camera-preview inset (opt) - the scene is re-rendered from a previewed camera into a swapchain sub-rect, single-sample, reusing this frame's shadow maps.

The MSAA→scene-texture→swapchain path exists because the colour grade and bloom must read the finished HDR-ish scene as a texture. The scene is never presented directly; the composite pass is always the thing that writes the swapchain.

📸 Screenshot - save as img/render-frame-overview.png

A populated scene in the Viewport with shadows, sky gradient, and a couple of transparent objects, plus the stats overlay (F3 / ⓘ) visible showing draw calls and per-pass GPU ms.

What each pass costs

The renderer reports a RenderStats block each frame (draw calls, instances, static batches, shadow draw calls, CPU rebuild/cull ms, and - on the timestamp-query feature - real per-pass GPU ms for the shadow, main, and post passes). This is surfaced in the editor stats overlay and is the first place to look when a frame is slow. See Performance Systems.

In this section

  • PBR & Lighting - the Cook-Torrance lighting model, the up-to-8 light set, the sun, ambient, fog, and tonemapping.
  • Camera - the Camera component, active-camera selection, the projection, and the 116-float camera uniform.
  • Shadows - directional cascaded shadow maps, texel snapping, PCF, and cached static shadows.
  • Materials & Hook Shaders - preset PBR versus authored hook-based materials.
  • Particles - effect assets, emitters, api.burst, the CPU sim, and the billboard pass.
  • Post-Processing (Volume) - the add/remove effect stack and how the grade is packed and applied.
  • Performance Systems - culling, batching, distance culling, GPU cull, texture compression, MSAA, and render scale.
  • Mesh Format - the runtime vertex layout, static chunks, the instance record, welding, and shipped-file quantization.
  • Limitations & Roadmap - what the renderer does not do yet.

See also

Awaken — browser-native WebGPU game engine.