Smooth look on WebGPU: WGSL port fed by GPU inputs and CPU overrides (t88 phase 2c)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+51
-28
@@ -149,25 +149,27 @@ The frame loop applies speed and queued steps while keeping the per-frame tick l
|
||||
|
||||
## Drawing
|
||||
|
||||
- **The pixel look:**
|
||||
- `drawWorld` (`src/render/world.js`) fills one pixel per grain;
|
||||
- `diagonal-screws.js` derives diagonal and straight end bends from `endJoin`, sharing casing geometry between pixels, smooth shading tags, ghosts and previews;
|
||||
- `computeLight` (`light.js`) adds coloured light;
|
||||
- overlays (machines, molds, the bot, rain, arrows) are drawn on the 2D canvas afterwards
|
||||
(`machines.js`, `overlays.js`).
|
||||
- **The smooth look** (`src/render/smooth.js`) takes the buffers `drawWorld` fills (colour and
|
||||
a class per cell, the colour behind, trees, fields, light, glow) and shades them with WebGL2.
|
||||
- The cell classes are set in `world.js` and read in the shader: 4 is a crisp built piece,
|
||||
6 a wall tile, 7/8 a belt, 9–12 a screw conveyor, 13/14 a filter.
|
||||
- On software GL it falls back to pixels, unless `window.__allowSoftwareGL` is set (the test
|
||||
tools set it).
|
||||
`SET.renderer` selects WebGPU or Legacy for either look. `src/render/cell.js` holds the shared
|
||||
CPU rules; `objects.js` adds bars, flyers and blueprint ghosts. The GPU canvas sits behind the
|
||||
transparent game canvas, which draws the existing overlays (machines, molds, bot, rain, arrows).
|
||||
|
||||
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.
|
||||
- **WebGPU** (`src/render/gpu/`): `textures.js` uploads visible world rectangles, `colour.js`
|
||||
colours plain grains, and `draw.js` selects rare CPU override blocks and submits the passes.
|
||||
`light.js` computes lighting; the CPU `computeLight` is never called by this path.
|
||||
- Pixel uses nearest upscaling and separable GPU bloom (`shaders.js`).
|
||||
- Smooth adds class, back colour/sky flag, tree/geometry tags and occupancy field outputs to
|
||||
colour compute. Only this look uploads the CPU back/tree/field overrides and presence mask.
|
||||
- `smooth-shader.js` and `smooth-slope.js` literally port the legacy GLSL shapes to WGSL.
|
||||
`smooth.js` creates the smooth pipeline, field mip passes and bindings. Field AO samples
|
||||
level 2.2; glow retains the legacy nine-tap filter. WebGPU smooth always draws full resolution.
|
||||
- **Legacy**: `drawWorld` fills every cell on the CPU, and `render/light.js` computes lighting.
|
||||
Pixel uses Canvas2D; smooth (`render/smooth.js`, `smooth-shaders.js`, `slope-belt-shader.js`)
|
||||
shades the four inputs with WebGL2 and keeps its adaptive resolution/fallback. Both smooth
|
||||
renderers share `smoothSurface` for the cached parallax horizon.
|
||||
- Smooth classes are set in `cell.js`: 4 crisp built piece, 6 wall tile, 7/8 belt, 9–12 screw,
|
||||
13/14 filter, 19–22 diagonal screw, 23–30 slope belt/filter, 31/32 slope sidewall and cleats.
|
||||
`diagonal-screws.js` and `slope-belts.js` share the simulation join geometry with pixel colours,
|
||||
smooth tags, ghosts and previews.
|
||||
|
||||
## The browser shell
|
||||
|
||||
@@ -421,29 +423,48 @@ Shared CPU colour rules, used by legacy and the rare-cell WebGPU override.
|
||||
Loose bars, flying grains and queued blueprint blocks in the grain buffer.
|
||||
- functions: worldObjects
|
||||
|
||||
### src/render/gpu/colour.js (82 lines)
|
||||
### src/render/gpu/colour.js (101 lines)
|
||||
The plain-cell rules mirror cellPixel; built geometry stays in the CPU override.
|
||||
- variables: GPU_COLOUR
|
||||
- functions: gpuColourShader
|
||||
- variables: GPU_COLOUR, GPU_COLOUR_SMOOTH
|
||||
|
||||
### 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)
|
||||
### src/render/gpu/light.js (143 lines)
|
||||
GPU sources retain the CPU roof and transmission rules; directional relaxation follows only left/up, then right/down, so it cannot introduce extra light paths.
|
||||
- functions: gpuLightGrid, gpuLightBindings, gpuLight
|
||||
- variables: GPU_LIGHT, GPU_LIGHT_SPREAD, GPU_LIGHT_PACK
|
||||
|
||||
### src/render/gpu/smooth-slope.js (107 lines)
|
||||
Smooth slope geometry and cleats, ported from slope-belt-shader.js.
|
||||
- variables: GPU_SMOOTH_SLOPE_HELPERS, GPU_SMOOTH_SLOPE_DRAW
|
||||
|
||||
### src/render/gpu/smooth-shader.js (368 lines)
|
||||
Literal port of the legacy smooth shader; dpdy is reversed for WebGPU's top-left origin.
|
||||
- variables: GPU_SMOOTH
|
||||
|
||||
### src/render/gpu/smooth.js (47 lines)
|
||||
Field mip levels preserve the legacy trilinear AO sample at level 2.2.
|
||||
- functions: gpuSmoothInit, gpuSmoothTextures, gpuSmoothBindings, gpuSmoothUpload, gpuSmoothMips
|
||||
- variables: GPU_FIELD_MIP
|
||||
|
||||
### src/render/gpu/device.js (48 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)
|
||||
### src/render/gpu/textures.js (104 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)
|
||||
### src/render/gpu/draw.js (83 lines)
|
||||
Only exceptional blocks and their geometric neighbours run the CPU colour rules.
|
||||
- functions: gpuOverrides, gpuDraw, gpuPresent
|
||||
- functions: gpuOverrideHas, gpuOverrides, gpuDraw, gpuPresent
|
||||
- variables: GPU_PATTERN
|
||||
|
||||
### src/render/world.js (83 lines)
|
||||
### src/render/world.js (85 lines)
|
||||
Scale colour without changing the smooth-look class.
|
||||
- functions: fogColor, drawWorld
|
||||
|
||||
@@ -456,9 +477,9 @@ Other overlays: floating text, rain and snow, flow arrows, filter tags, molds, t
|
||||
- functions: spawnFloater, drawRain, drawFlowArrows, drawJoinMark, drawJoinMarks, flowLoaded, drawFlowRates, pingImage, drawPing, drawHoveredVein, drawPingRecharge, drawRunOutline, drawFilterTags, drawFloaters, moldLabel, drawMolds, drawBot, spawnDebris, spawnSpark, spawnSuck, drawParticles
|
||||
- variables: PING_SPEED, PING_HOLD, PING_FADE, PING_BANDS, PING_GLINTS, GLINT_MS
|
||||
|
||||
### src/render/smooth.js (103 lines)
|
||||
### src/render/smooth.js (108 lines)
|
||||
Smooth look: WebGL setup, texture uploads, drawing and adaptive resolution.
|
||||
- functions: smCompile, smoothAvailable, smUpload, smoothDraw, smoothPace
|
||||
- functions: smCompile, smoothAvailable, smUpload, smoothSurface, smoothDraw, smoothPace
|
||||
- variables: SM, smBytes
|
||||
|
||||
### src/render/slope-belt-shader.js (111 lines)
|
||||
@@ -604,6 +625,7 @@ The frame loop and boot.
|
||||
|
||||
- `test/firefox_smoke.py`: An isolated Firefox title screen and three seconds of a new pixel world.
|
||||
- `test/furnace_play.js`: Fires a starter furnace design repeatedly the way a player would, without cleaning it out between firings, to see whether it clogs. Usage: node test/furnace_play.js [design] [ore] [coal] [firings] [roll] [mode] mode: 'mix' (even ore/coal mix blown in), 'layers' (ore first, coal on top), 'vent' (blown in from above through the vent)
|
||||
- `test/gpu-readback.js`: Read the four actual smooth textures without adding COPY_SRC usage to game resources.
|
||||
- `test/harness.js`: Loads the simulation (the src/sim/ files listed in src/manifest.json, joined in order into one script, the same way build.py joins them) so scenarios can drive it headlessly in Node. Errors point at the real file and line (sim/pipes.js:42), not at the joined script.
|
||||
- `test/play.py`: Play-test in a real browser: mouse, keyboard and a mocked gamepad drive the game; save and continue. Screenshots go to shots/play/ (or the folder given as the first argument). Pixel look unless the second argument is 'smooth'.
|
||||
- `test/run_all.py`: Run the test suites and report.
|
||||
@@ -637,7 +659,7 @@ The frame loop and boot.
|
||||
- `test/t_slope_belt_art.js`: Drawing geometry against real parts, without changing transport.
|
||||
- `test/t_slope_belts.js`: Sloped belts: transport, joins, sorting, persistence and the actual drag controls.
|
||||
- `test/t_slopes.js`: Sloped walls: flow, sealed joints, building, refunds and persistence.
|
||||
- `test/t_smooth.py`: The smooth look: the WebGL pass compiles, draws, falls back cleanly, and the thermal lens still works.
|
||||
- `test/t_smooth.py`: The smooth look: the WebGPU pass compiles, draws, falls back cleanly, and the thermal lens still works.
|
||||
- `test/t_stone.js`: Uses for waste rock and sand: glass bricks from a fan furnace, the Saw, the silo and builders' orders.
|
||||
- `test/t_water.js`: Water conservation, settling, ledge pouring, boundaries, pipes and phase changes. Tall worlds keep geothermal heat out of the ponds.
|
||||
- `test/t_weather.js`: Weather: no more springs; steam rises into clouds and rains back; showers; snow when it is freezing.
|
||||
@@ -679,4 +701,5 @@ 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-light.js`: Frozen light comparisons, also used by the WebGPU browser regression suite.
|
||||
- `tools/scenes/t88-renderer.js`: Paused comparison scene: ordinary grains, fog, heat, trees and exceptional geometry.
|
||||
|
||||
+15
-3
@@ -67,7 +67,7 @@ diff -r /tmp/before /tmp/after
|
||||
gamepad and world sizes. Screenshots go to
|
||||
`shots/play/`. Pixel look by default; `python3 test/play.py shots/play smooth` uses the smooth
|
||||
look.
|
||||
- `test/t_smooth.py` checks that the WebGL2 smooth look compiles and draws, that it falls back
|
||||
- `test/t_smooth.py` checks that the WebGPU smooth look compiles and draws, compares legacy at half resolution, and falls back
|
||||
to pixels cleanly, and that the thermal lens works.
|
||||
- Run them one at a time. Two headless browsers at once can starve each other, and
|
||||
`t_smooth.py`'s half-resolution check then fails. `run_all.py` runs them one after the other.
|
||||
@@ -110,8 +110,8 @@ 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.
|
||||
- `--renderer webgpu|legacy` selects the renderer for either look (default WebGPU). WebGPU screenshots
|
||||
fail clearly when no device is available; Legacy smooth uses WebGL2; WebGPU smooth uses WGSL.
|
||||
- `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
|
||||
@@ -233,3 +233,15 @@ CPU. The GPU canvas draws the world; the transparent game canvas holds the exist
|
||||
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.
|
||||
|
||||
WebGPU Phase 2c: both looks use WebGPU. `test/t_gpu.py` also reads all four smooth input
|
||||
textures back and compares them against the legacy CPU buffers at zoom 12/6/2 with fog on/off:
|
||||
classes, sky flags, field bits and geometry tags must match exactly; RGB can differ by one.
|
||||
The smooth shader retains the legacy nine-tap glow filter and samples field AO at mip level 2.2;
|
||||
three render passes generate field levels 1-3, including non-power-of-two view sizes.
|
||||
Only smooth allocates/uploads the back/tree/field overrides and a separate presence mask.
|
||||
`tools/perf.py` refuses a WebGPU fallback in either look and records WebGPU scale as 1.
|
||||
The screenshot tool invalidates the shared parallax horizon after scene setup changes `surfY`,
|
||||
so renderer startup timing cannot change the hills in a comparison. Mip pass references:
|
||||
[Chrome's porting guide](https://developer.chrome.com/docs/web-platform/webgpu/from-webgl-to-webgpu#mipmap_generation)
|
||||
and [WebGPU Fundamentals](https://webgpufundamentals.org/webgpu/lessons/webgpu-importing-textures.html#generating-mips-on-the-gpu).
|
||||
|
||||
@@ -4,6 +4,20 @@ 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 2c, smooth look on WebGPU (Codex + Claude Code)
|
||||
|
||||
- WGSL port of the smooth look (`src/render/gpu/smooth.js`, `smooth-shader.js`, `smooth-slope.js`):
|
||||
the GPU colour pass writes all four smooth inputs for plain cells; the CPU rare-cell overrides
|
||||
(`cell.js`, now also mold walls) fill them for built pieces and objects (12.73/3.41/0.38% of
|
||||
factory cells at zoom 12/6/2). Field AO via three GPU mip passes; nine-tap glow kept; no
|
||||
WebGPU `smoothPace` (max GPU time 1.24 ms). Legacy still selectable.
|
||||
- Checked by Codex: 21 screenshot pairs legacy vs WebGPU, mean RGB error 0.01-0.22/255, small
|
||||
counts off by more than 8 (thin AO and edge rounding), no missing shapes or open joins; input
|
||||
readbacks equal legacy. Smooth factory drawWorld zoom 2 28.2 -> 10.0 ms; real loop zoom 2 CPU
|
||||
p50/p95 34.7/39.0 -> 14.5/15.7 ms. Claude did not inspect the images (token budget).
|
||||
- Open for 2d: adapters with fewer than five storage textures per stage fall back to legacy.
|
||||
- Tests: `python3 test/run_all.py --browser` passed.
|
||||
|
||||
## 2026-10-27: t88 phase 2b, lighting on the GPU (Codex + Claude Code)
|
||||
|
||||
- `src/render/gpu/light.js`: light sources per light cell (transmittance, sky from `roofY`, heat,
|
||||
|
||||
Reference in New Issue
Block a user