Docs Decisions

Contents

ADR-0007: Compatibility is the baseline renderer; frames come from the frame server

  • Status: Proposed
  • Date: 2026-09-30

Context

Godot 4.7's web export runs only the Compatibility renderer (WebGL 2). Measured on the dev box (research/testing-verification.md §0):

  • Forward+ and Compatibility frames of the same scene differ by 38 levels on average: a different look, not noise.
  • Compatibility renders windowless and bit-identically under Xvfb on llvmpipe (1.6 s per warm shot). Forward+ does too on lavapipe (3 s).
  • The real GPU renders only on the owner's desktop display, which pops windows. Under Xvfb, RADV can't present and Godot silently falls back to software. Forcing the GPU made Godot silently move to the owner's Wayland session.
  • Headless Chromium with ANGLE on Vulkan reaches the GPU with no window, for the Compatibility renderer only.
  • --headless draws nothing: captures fail or hang, and Movie Maker crashes.

unspent-ortho built Forward+ first and then spent effort making the web look match ("the web is the same place on a worse night"): a fitted compatibility trim, depth fog for volumetrics, shader warm-up for first-draw freezes, and a light budget per tier (research/unspent-ortho-study.md §6.5, §6.6).

Decision

  1. Compatibility is the baseline. Every render profile must look right in Compatibility. That's the renderer for the web, for verification frames and for goldens.
  2. Forward+ is an optional desktop layer, deferred until a style needs it. A style may later declare Forward+ enhancements (volumetric fog, SSAO, more shadowed lights). They add; they never change what a frame shows, and a Forward+ frame set is checked separately on the GPU box. Until a style asks for it, every platform runs Compatibility, as research/godot.md §9 recommends: one look, one set of goldens, no shader drift. M1 ships Compatibility only.
  3. One colour door per profile. Shaders get palette colours through one function that corrects for the renderer. Transparent geometry has an explicit render priority, and a lint checks it.
  4. Frames come from the frame server. A SubViewport at the exact output size, a flush of the scene tree (await process_frame) and then force_draw, then a read-back, at a named tick. Movie Maker is kept for trailers and "watch the bot" videos, not tests.
  5. Every frame job asserts its adapter. It prints and checks the rendering method, the driver and get_video_adapter_name(), and fails on a silent fallback. Windowless jobs pin --display-driver x11 under Xvfb with the ICD set explicitly.
  6. No history-dependent effects in verification frames. A capture profile turns off TAA, SDFGI, auto exposure and GPU particles, or warms up a fixed number of ticks with them fixed.
  7. Shader warm-up is an engine feature. The player pre-draws every program a game uses before play, and the web ladder rung counts programs compiled after the first tick.

Alternatives considered

  • Forward+ first, web as a degraded port (unspent-ortho's path). It gives the best desktop look, but two looks to maintain, a web that surprises, and verification frames that need a display.
  • Exact golden frames for generated games. A new game has no baseline. Goldens are for engine regression on sample packs; generated games get property checks and judged frames.

Consequences

  • Every generated game is verifiable on a machine without a GPU, and the frames it's judged by are the frames web players see.
  • Desktop-only richness is opt-in per style and costs a second check on the GPU box.
  • The Tideline slice's night must read in Compatibility first. That's a real constraint on its look (no volumetrics), handled with depth fog and light pools, as unspent-ortho's web tier does.

From docs/adr/0007-rendering-and-frames.md in the Gamewright repository.