WebGPU pixel look: GPU colours the world, CPU only rare cells (t88 phase 2a)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+42
-3
@@ -162,6 +162,13 @@ The frame loop applies speed and queued steps while keeping the per-frame tick l
|
||||
- On software GL it falls back to pixels, unless `window.__allowSoftwareGL` is set (the test
|
||||
tools set it).
|
||||
|
||||
The pixel look can use WebGPU (`src/render/gpu/`): whole-world textures receive visible
|
||||
strided uploads, `colour.js` colours plain grains, and `draw.js` selects rare CPU override
|
||||
blocks and submits colour, bloom and drawing. `cell.js` shares the original CPU cell rules
|
||||
between Legacy/Smooth and those overrides; `objects.js` adds bars, flyers and blueprint ghosts.
|
||||
The GPU canvas sits behind the transparent game canvas, which draws all existing overlays.
|
||||
CPU lighting stays in `light.js`. `SET.renderer` selects WebGPU or Legacy; Smooth uses WebGL2.
|
||||
|
||||
## The browser shell
|
||||
|
||||
- `GAME` (`src/ui/state.js`) holds the UI state: tool, selected piece, build tab, drag,
|
||||
@@ -406,7 +413,37 @@ Screw casing geometry shared by both looks, ghosts and placement previews. smoot
|
||||
- functions: diagonalScrewJoins, straightScrewJoins, diagonalScrewShape, diagonalScrewBlock
|
||||
- variables: SCREW_CLASS, diagonalJoins, diagonalAcross, diagonalAlong, diagonalCap
|
||||
|
||||
### src/render/world.js (419 lines)
|
||||
### src/render/cell.js (272 lines)
|
||||
Shared CPU colour rules, used by legacy and the rare-cell WebGPU override.
|
||||
- functions: cellPixel
|
||||
|
||||
### src/render/objects.js (80 lines)
|
||||
Loose bars, flying grains and queued blueprint blocks in the grain buffer.
|
||||
- functions: worldObjects
|
||||
|
||||
### src/render/gpu/colour.js (82 lines)
|
||||
The plain-cell rules mirror cellPixel; built geometry stays in the CPU override.
|
||||
- variables: GPU_COLOUR
|
||||
|
||||
### src/render/gpu/shaders.js (42 lines)
|
||||
Half-resolution separable Gaussian bloom, then nearest colour + linear light.
|
||||
- variables: GPU_BLUR, GPU_DRAW
|
||||
|
||||
### src/render/gpu/device.js (43 lines)
|
||||
Async startup shares the title screen's adapter; failures leave a usable legacy renderer.
|
||||
- functions: gpuInit, gpuFail, worldPixelContext
|
||||
- variables: GPU
|
||||
|
||||
### src/render/gpu/textures.js (84 lines)
|
||||
Whole-world allocation, direct strided uploads; no cell packing or dirty tracking.
|
||||
- functions: gpuTexture, gpuWrite, gpuBuffer, gpuWorldTextures, gpuViewTextures, gpuBindings, gpuUpload
|
||||
|
||||
### src/render/gpu/draw.js (69 lines)
|
||||
Only exceptional blocks and their geometric neighbours run the CPU colour rules.
|
||||
- functions: gpuOverrides, gpuDraw, gpuPresent
|
||||
- variables: GPU_PATTERN
|
||||
|
||||
### src/render/world.js (83 lines)
|
||||
Scale colour without changing the smooth-look class.
|
||||
- functions: fogColor, drawWorld
|
||||
|
||||
@@ -442,7 +479,7 @@ Game shell (src/ui/): browser only. This file: settings, the GAME state object,
|
||||
- functions: loadSettings, saveSettings, el, esc, rgbOf, fmtT, toast, setStatus
|
||||
- variables: SET_DEFAULT, SET, TOOLS, ICONS, BAG_ORDER, GAME, keys, hudDirty, $
|
||||
|
||||
### src/ui/title.js (74 lines)
|
||||
### src/ui/title.js (76 lines)
|
||||
Startup capabilities and title art.
|
||||
- functions: checkCapabilities, renderCapabilityNotice, canStartGame, buildTitleCells, drawTitle
|
||||
- variables: REQUIRE_GPU_AND_THREADS, CAPS, CAPS_CHECK, FONT5, TITLE
|
||||
@@ -461,7 +498,7 @@ Shared player state; the world save remains a complete standalone fallback.
|
||||
Starting, loading and quitting a game; modal dialogs; the pause menu.
|
||||
- functions: showLoading, enterGame, setupNewWorld, startNewWorld, startFromSave, loadSavedWorld, quitToTitle, refreshContinue, anyModal, openModal, closeModal, closeAllModals, topModal, dialog, openPause, renderWorlds
|
||||
|
||||
### src/ui/settings.js (125 lines)
|
||||
### src/ui/settings.js (126 lines)
|
||||
The settings screen.
|
||||
- functions: openSettings, renderSettings, applySettings, toggleFullscreen, confirmWipe
|
||||
- variables: SET_TABS, setTab
|
||||
@@ -585,6 +622,7 @@ The frame loop and boot.
|
||||
- `test/t_flow.js`: Throughput overlay (t49): grains per second per transport block, counted only while the overlay is on, never changing what the simulation does.
|
||||
- `test/t_fog.js`: Fog: surface, sight, range, saves and simulation independence.
|
||||
- `test/t_furnace_repeat.js`: The starter furnaces keep working when fired again and again without cleaning, however the charge goes in: mixed, ore first with coal on top, or blown in through the vent. Each case plays furnace_play.js, which builds the design and fires it like a player.
|
||||
- `test/t_gpu.py`: WebGPU pixel parity, rare-cell selection, world replacement, resize and device loss.
|
||||
- `test/t_joins.js`: The oracle states the geometric rule independently of the precomputed neighbour tables.
|
||||
- `test/t_light.js`: Run the renderer's lighting against real simulation data, with only canvas storage stubbed.
|
||||
- `test/t_load.js`: Reload wakes nearby areas first, without losing distant water, fire or saved state.
|
||||
@@ -641,3 +679,4 @@ The frame loop and boot.
|
||||
- `tools/scenes/t35-joins.js`: Run after clear-sky.js. Paused, empty pipes make casing, flights and mouths easy to inspect.
|
||||
- `tools/scenes/t36-funnel.js`: A twelve-block funnel, one block open at the bottom, draining 400 grains of sand.
|
||||
- `tools/scenes/t40-sloped-belt.js`: Self-contained: a flat feed climbs, crosses the top, and runs downhill; a filter sorts below.
|
||||
- `tools/scenes/t88-renderer.js`: Paused comparison scene: ordinary grains, fog, heat, trees and exceptional geometry.
|
||||
|
||||
+22
-1
@@ -110,6 +110,15 @@ python3 tools/shot.py shots/silo.png --setup tools/scenes/clear-sky.js \
|
||||
`tools/scenes/clear-sky.js` clears a flat work area and sets `FLOOR`, `BX`, `BY` and `CAM`. Set
|
||||
`CAM = [x, y]` to frame the shot.
|
||||
- The HUD and the cursor previews are hidden unless you pass `--hud` or `--previews`.
|
||||
- `--renderer webgpu|legacy` selects the pixel renderer (default WebGPU). WebGPU screenshots
|
||||
fail clearly when no device is available; Smooth still uses WebGL2.
|
||||
- `test/t_gpu.py` compares the rendered composite against Legacy for ordinary cells, built pieces,
|
||||
ghosts, fog and all thermal palettes, and checks world replacement, resize, timing and device loss.
|
||||
World-pixel tests use `worldPixelContext()` to read both stacked canvases. The helper
|
||||
presents the retained GPU result again before copying, because canvas textures expire after
|
||||
presentation ([WebGPU specification](https://gpuweb.github.io/gpuweb/#abstract-opdef-expire-the-current-texture),
|
||||
[Mozilla implementation notes](https://bugzilla.mozilla.org/show_bug.cgi?id=1965377)). Tests that change
|
||||
static `noiseB` in place set `GPU.world = null` before drawing to upload their new fixture.
|
||||
- `--look smooth` for the smooth look. Test in the pixel look unless you are checking graphics.
|
||||
- `--freeze` draws the last frame at a fixed time and frame count, so animations are caught at
|
||||
the same moment. Two shots of the same scene then match pixel for pixel, and a before / after
|
||||
@@ -193,7 +202,7 @@ python3 tools/shot.py shots/silo.png --setup tools/scenes/clear-sky.js \
|
||||
simulation work per frame and number of ticks. p50/p95/p99 use interpolated sorted samples;
|
||||
five warm-up frames are excluded. Zero-tick frames correctly have zero simulation time.
|
||||
JSON also records the smooth renderer's adaptive resolution scale: compare it as well as time.
|
||||
Comparisons require identical options, print all timing changes, and mark >10% regressions;
|
||||
Comparisons accept subsets of the baseline scenes, looks and zooms, with identical world/viewport dimensions and run duration, print all timing changes, and mark >10% regressions;
|
||||
tiny timings and tails can be noisy, and a zero baseline has no meaningful percent change.
|
||||
|
||||
Performance runs launch Chromium with
|
||||
@@ -212,3 +221,15 @@ python3 tools/shot.py shots/silo.png --setup tools/scenes/clear-sky.js \
|
||||
- `tools/experiments/factory_cost.js`: what running factories cost the simulation.
|
||||
- `tools/experiments/flow.js`: belt and screw conveyor throughput in grains a second.
|
||||
- `tools/experiments/worldgen.js`: material counts and timings of world generation.
|
||||
|
||||
WebGPU Phase 2a: Colour measures the CPU override fill (including loose objects and ghosts).
|
||||
`frame_ms` is the complete paused-frame CPU call (including overlays and UI).
|
||||
Upload measures strided visible-array writes and the override/light uploads, and Blit measures
|
||||
GPU command submission. `override_cells` records the selected CPU cells; GPU timing is a
|
||||
completed timestamp sample for that configuration across colour, bloom and drawing when `timestamp-query` is
|
||||
available. The timestamp excludes uploads and the CPU lighting pass. Lighting remains on the
|
||||
CPU. The GPU canvas draws the world; the transparent game canvas holds the existing overlays.
|
||||
|
||||
The comparison fixture is `tools/scenes/t88-renderer.js`. Capture with `--freeze --zoom 12`
|
||||
(and 6 or 2), `--viewport 1920x1080`, and each `--renderer`. It includes terrain, water, fire,
|
||||
magma, fog, a tree, flat/slope belts, screws, pipe, mold, bar and blueprint ghost.
|
||||
|
||||
@@ -4,6 +4,22 @@ What agents have done, newest first. Add an entry for every piece of work: the d
|
||||
note or request it was for, what changed, the files touched, the tests run, and anything left
|
||||
open or not verified. Keep each entry short; the git history has the details.
|
||||
|
||||
## 2026-10-27: t88 phase 2a, WebGPU pixel look (Claude Code + Codex)
|
||||
|
||||
- New `src/render/gpu/` (device, textures, colour compute, glow blur, draw) renders the pixel look
|
||||
on a `#gpu` canvas under `#game`, which becomes a transparent overlay in WebGPU mode. The GPU
|
||||
colours plain cells (background, materials with noise and grain, loose shade, fire, water,
|
||||
ALPHA, digp shake, incandescence, lens, fog); the CPU colours only rare cells (built blocks,
|
||||
screw/slope neighbours, bars, flyers, ghosts) through the extracted per-cell code
|
||||
(`src/render/cell.js`, `src/render/objects.js`). World arrays upload per visible rectangle with
|
||||
`queue.writeTexture`; light is still `computeLight` on the CPU, multiplied on the GPU.
|
||||
`SET.renderer` = webgpu | legacy (Settings). The smooth look still uses the WebGL2 path.
|
||||
- Screenshots legacy vs WebGPU: mean abs RGB error 0.112 / 0.062 / 0.034 at zoom 12/6/2, no pixel
|
||||
off by more than 8. Factory scene drawWorld at zoom 2: 24.55 -> 9.98 ms (overrides 1.81, uploads
|
||||
2.39, CPU light 5.59; GPU time 0.06 ms). Real loop zoom 2 CPU p50/p95: 31.7/35.3 -> 15.4/16.7 ms.
|
||||
- Tests: `python3 test/run_all.py --browser` all passed (new `test/t_gpu.py`). Not checked:
|
||||
Firefox WebGPU (no adapter in Playwright Firefox on Linux).
|
||||
|
||||
## 2026-10-27: t88 phase 1, serving and capability checks (Claude Code + Codex)
|
||||
|
||||
- The game must be served with COOP/COEP (README has nginx and Caddy lines; opening from disk is
|
||||
|
||||
Reference in New Issue
Block a user