makepad/libs/render/CLUSTERED.md
Admin d2130e6550 libs: model, mesh_edit, csg, scene, sim, render, xr
Squash of 25 work commits (Sep 1–12):
  237da90  render: the sprite lane hands the screen draw back the way it found it
  895b9a7  particles: an emitter can ride a body's own frame
  914471f  ai-hub: a feed session whose last socket left ends on its idle timeout; skin: parent, skinned centroid and a nodes-only rig for retargets
  3fcccf3  sim: the whole world is implicitly editable, and one seam says where the ground is
  5692fab  sim: the landform world proves itself — walker through the tunnel included
  f4af6df  sim: terrain knows who changed it — a plan layer over player history
  adbb078  render: a water volume can be physics without a picture
  c17480f  render: a non-rigid body may carry its own orientation
  acad401  sim: an agent with no route holds and retries instead of walking into the wall
  22b2bf9  sim + chat: the composed world surface takes a map floor; the chat gets plan tools
  faf8112  web path: the tessellator's lap timer, the trace span and the fusion cycle timer have no clock on the web
  d3472eb  tsdf: the clock-taking XR helpers are native-only — the browser has no depth camera and no Instant
  4946c9e  sim + render: a repaint is not a world edit — colour and glow restyles never rebake the lightmap
  4317f58  sim + render + chat: walk decks as a surface, the filmed body is no obstruction, the brief never asks
  9791279  render: support rigged models and custom materials across viewers
  3ce2792  sim: use deck geometry for collision and sensing
  c8b79ff  Add portable PBR, rig and soft-body authoring support
  108d423  Add transactional polygon modeling and editable asset documents
  38f4d3b  Refine editable modeling and firm yarn character behavior
  08a2637  sim: add entity-owned lights and vehicle headlights
  00d96a7  render: add clustered lights, local shadows and incremental GI
  cf916af  render: import glTF asset extensions and wire clustered GI
  c6d2cca  model: cut transaction memory and raise capacity limits
  41af47a  raytrace: add a CPU probe-ray BVH budget example
  c963d0a  libs: the game sim splits into makepad-scene and makepad-soft-body; render, model, fab and the asset importer retarget

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 13:40:31 +02:00

5.3 KiB
Raw Permalink Blame History

Clustered forward lighting

Renderer defaults to clustered local lights. Geometry submission, static slabs, instancing, and GPU skinning are unchanged: one forward material pass, with separate sun-shadow passes. No G-buffer, geometry prepass, compute shader, depth readback, or per-object light selection is required by clustering.

This follows the CPU-assigned clustered-forward approach described in Filament's rendering documentation. It is a first lighting increment, not Filament feature or visual parity.

Lighting and shadow policy

Local lighting Sun mode World lighting work
Clustered (default) Realtime CSM; no world atlas or CPU probe bake
Clustered OnChange Sun-only atlas and legacy CPU probes
Legacy (MAKEPAD_CLUSTERED=off) Either Original eight-light selection and lamp atlas

Baked per-model AO is retained in every mode. Clustered lights reach primitives, terrain, diffuse/PBR/custom models, and skinned characters per fragment. PBR materials evaluate local GGX specular as well as diffuse. Explicitly prelit materials retain their existing unlit/baked-color semantics.

Local lights can request budgeted realtime shadows; see Entity lights for Splash headlights, point/spot fixtures and shadow budgets. Generic lights default to unshadowed and can shine through walls. The sun retains its own tier. Engine-authored lights use (1 - distance / radius)^2; imported glTF punctual lights use their separate photometric mode. Full linear HDR, exposure/tone mapping, IBL and unified PBR materials remain follow-ups. Clustering does not itself add MSAA.

Budgets and stereo

Renderer::set_cluster_config(ClusterConfig { ... }) controls XY tiles, depth slices, lights per cluster, and total uploaded lights. Defaults are 16 × 9 × 24, 32 lights/cluster, and 1024 total. Hard limits are 32 × 24 × 32, 64 lights/cluster, and 4096 total. set_clustered_lighting(bool) switches the complete legacy/new lighting policy and invalidates old baked state. Policy survives realm changes.

Flat views use frustum tiles with logarithmic depth. The backend currently late-latches XR eye matrices without exposing them to this host. XR therefore uses a conservative world-space 3D grid over light influence, shared by both eyes—not clusters derived from an unrelated flat camera. Large, dispersed worlds make that fallback less selective. Exact stereo/per-eye frustum clustering needs an eye-view snapshot API before it can replace this fallback.

Smaller lights_per_cluster bounds fragment cost but may visibly omit lights; finer grids cost CPU/memory but can reduce false-positive light references. Start device tuning with the counters, not a claimed universal Quest preset. This path has not yet been benchmarked on a headset.

RenderStats::clustered / Renderer::cluster_stats() report build/upload-queue CPU microseconds, uploaded bytes, occupancy, references, rejected lights, and overflow. They do not measure GPU time. Per-cluster overflow retains lights with the strongest conservative contribution estimate; equal scores retain the earlier input. The global cap keeps the first valid input lights. Counts are explicit because neither cap promises all lights survive. Overflow logs are throttled; MAKEPAD_CLUSTER_STATS=1 also logs frame counters periodically.

Texture ABI

One nearest-sampled RGBA32F texture contains cluster headers, packed light indices (four scalar indices per texel), then three texels per light: position/radius, RGB/angular falloff, and normalized emission direction/mode. Headers store scalar index offset/count. CPU list storage and texture upload vectors are reused. Integer-valued floats are exactly representable within the hard caps. The shader mixin and CPU packing live together in src/clustered.rs.

Local shadows use a separate nearest-sampled metadata texture (one light header, four camera rows per face) and a depth atlas, preserving the light-data stride. Inherited texture bindings are resolved by name for derived PBR materials.

Reproduce

Build the sandbox in its own workspace, then launch from the checkout root:

# In apps/sandbox:
cargo build --release -p makepad-sandbox
# From the checkout root:
SANDBOX_WORLD=clustered MAKEPAD_CLUSTER_STATS=1 ./apps/sandbox/target/release/makepad-sandbox --remote

The fixture supplies 256 moving lights and a single large floor, independent of downloaded models. MAKEPAD_CLUSTER_DEMO_LIGHTS=N changes the count; MAKEPAD_CLUSTER_DEMO_FREEZE=1 fixes light motion for A/B images. Add MAKEPAD_CLUSTERED=off for the old path. That comparison deliberately shows missing lights in the old eight-slot path; it is not equal-quality GPU work. F8 switches sun modes. Finish owned test instances via the printed remote port's /gq endpoint.

cargo test --release -p makepad-render
cargo test --release -p makepad-render clustered::tests::benchmark_build_and_pack -- --ignored --nocapture

Coverage includes more than eight overlapping lights, depth/tile boundaries, rotated and orthographic cameras, XR/world-grid lookup, empty/invalid inputs, bounded overflow, texture packing, material shader compilation/binding order, and mode/realm lifecycle. The ignored benchmark measures CPU build+pack only.