7.6 KiB
Web capabilities
What the browser build (GOOS=js GOARCH=wasm, platform/web + gpu/webgl2) supports compared with the native build. Decisions: D289, D290.
Platform
| Feature | Web | Notes |
|---|---|---|
| Window | works | one canvas; size from CSS, pixels from devicePixelRatio |
| Resize, DPI change | works | ResizeObserver, resize, DPR check every frame |
| Keyboard | works | code → scancode; browser shortcuts (Ctrl/Cmd, F5, F11, F12) stay with the browser |
| Text input | degraded | single characters only, no IME composition |
| Mouse | works | pointer capture while a button is held |
| Wheel | works | 100 px = one line; trackpads are precise |
| Pointer lock | degraded | granted on the next click or key press after the request |
| Touch | degraded | primary pointer as the left button; multi-touch in Stage 18 |
| Gamepads | works | standard mapping; Chrome reports a pad only after a button press |
| Focus loss | works | held keys and buttons are released |
| Hidden tab | works | the platform clock excludes hidden time. Any background animation callback leaves the simulation paused. The first visible frame rebases the live clock and delivers queued input. Explicit fixed timelines retain their configured step |
| Frame pacing | works | VSync; the frame limit skips animation frames |
| Fullscreen | degraded | entered on the next click or key press |
| Display modes, monitors | missing | the display module has no modes to apply; the pixel ratio is capped at 1.5 by default (display.maxPixelRatio) |
| Clipboard write | works | navigator.clipboard.writeText (secure contexts) |
| Clipboard read | degraded | only what was pasted into the page or copied by the game |
| Cursors | works | CSS cursor names |
| Drag and drop of files | missing | |
| Player profile, saves | works | localStorage, limited to a few MB; a full store reports a write error |
| Files outside the bundle | missing | no OS file system |
GPU
| Feature | Web | Notes |
|---|---|---|
| Buffers, instancing | works | |
| Textures RGBA8, SRGB8_ALPHA8, R8 | works | |
| RGBA16F | degraded | EXT_color_buffer_float plus actual framebuffer/blend/readback probe; no requirement for 32-bit EXT_float_blend; honest LDR fallback |
| 2D array textures | works | layer/mip allocation, update, framebuffer attachment and restore; shared shadow/cookie bindings |
| Depth24/stencil8 targets | works | |
| sRGB output | works | offscreen backbuffer plus an encoding pass |
| Samplers, mipmaps | works | |
| Scissor, viewport, blend, cull | works | |
| ReadPixels | works | resolved RGBA8 targets keep the screenshot contract; RGBA16F diagnostics use RGBA/FLOAT |
| GPU timer | degraded | needs EXT_disjoint_timer_query_webgl2; unavailable/disjoint/context-canceled intervals are invalid, not zero GPU time |
| Debug output, object labels | missing | Options.Debug checks getError after creation calls |
| Context loss | works | objects are recreated from CPU copies; textures cost twice the memory |
| Shaders | works | GLSL ES 3.00 copies generated by make shaders |
Audio
| Feature | Web | Notes |
|---|---|---|
| Output | works | AudioWorklet at the context's rate: 48 kHz is asked, Safari may give 44.1 kHz and the mixer runs at what it gets |
| Start | degraded | silent until the first click, key or touch on the page; a page started by a click (the docs Try it) plays at once; audio written before that is dropped |
| Mute | works | web exports show a mute button; the choice is kept in localStorage for the site |
| Hidden tab | works | the context is suspended and resumed |
| Transport | works | a SharedArrayBuffer ring when the page is cross-origin isolated (COOP same-origin and COEP require-corp), otherwise blocks posted to the worklet |
| Latency | degraded | minimum targets of 3 blocks with SharedArrayBuffer and 6 with messages; at 48 kHz/512 frames these are ~32 and ~64 ms, plus the browser's output buffer. Reported device latency or long frames can raise the target |
| Device events | missing | the browser follows the default output device itself |
Exported games
aego export --target web (D295) runs the same runtime as the desktop export on this platform and GPU backend. Differences from desktop: data is loaded into memory before the game starts, there are no log files or crash bundles, saves and settings share the site's localStorage quota.
Performance
make web-bench (D365) runs the exported platformer with its input recording in Chromium and Firefox: 1280×720, device pixel ratio 1, render scale 1, seed 1, 120 warmup frames, 600 measured, 5 runs. While it measures, every frame advances the game by exactly one fixed step, so each run plays the same frames. It writes .cache/web-bench.json and a table in .cache/web-bench.md.
| Level | Metrics |
|---|---|
| Go | allocations and bytes per frame, bytes allocated inside the engine frame, allocations per engine zone (phases, gpu, poll), GC cycles, heap |
| Bridge | WebGL calls, timer query calls counted apart, draw calls, buffer and texture uploads and their size, program and render-target switches, time spent in WebGL calls |
| Browser | frame time p50/p95/p99 from requestAnimationFrame gaps, main-thread work per frame, frames over 33 ms, long tasks, JS heap, GPU time |
Options: WEB_BENCH_BROWSERS=chromium, WEB_BENCH_ARGS="--gpu=hard --headed" for the real GPU, --gogc=100,150,200 for GC experiments, --trace for a Chrome trace of the first run. make web-bench-baseline saves the machine's baseline, one per GPU mode. Later runs fail when a count (draw calls, WebGL calls, uploads, switches per frame) differs from the baseline or between runs, when Go allocates more than 24 bytes per frame over the baseline inside the engine frame, or when frame or work time is more than 10% slower; time is compared only with at least 5 runs. make web-smoke runs Chromium once and checks the counts, not the time. On the reference machine with --gpu=hard frame p95 must stay at or under 20 ms with at most 1% of frames over 33 ms.
make ci compares its Chromium/SwiftShader run with the versioned Stage 12 baseline in tools/websmoke/bench-stage12-baseline.json. A missing, incomplete or incompatible baseline, disabled allocation profile, wrong actual renderer, or attempt to update the baseline while gating is a failure. Direct make web-bench and make web-smoke keep using .cache/web-bench.base.json by default; CI does not replace that local file.
The engine-frame probe uses runtime.ReadMemStats at each end of a frame: runtime/metrics can publish small allocations in a later frame when an allocator cache is flushed. The frame count now includes tiny allocations, unlike the per-zone runtime/metrics counts. These stop-the-world reads happen only while the probe is enabled, and benchmark timings include their overhead; do not compare frame-byte values or timing baselines made with the older probe as if the method were unchanged. The separate stack-profile allocation gate and its exemptions remain in force.
Historical Stage 12w measurements on the reference machine, hardware GPU, 1280×720, device pixel ratio 1, 600 measured frames. These values do not measure Stage 12b lighting or its updated platformer:
| Chromium before 12w | Chromium after | Firefox before 12w | Firefox after | |
|---|---|---|---|---|
| frame p95, ms | 16.7 | 16.8 | 17.2 | 17.2 |
| frames over 33 ms | 0 | 0 | 0 | 0 |
Go allocations per frame (runtime/metrics) |
7.2 | 3.8 | 6.1 | 2.5 |
| allocations in the engine frame, not exempt | — | 0 | — | 0 |
| GC cycles in 3600 frames | 0 | 0 | — | — |
| WebGL calls per frame | 50.5 | 50.5 | 50.5 | 50.5 |
| draw calls per frame | 3.3 | 3.3 | 3.3 | 3.3 |