glemy/render
Rendering.
Runs WGSL/WebGPU. Takes entities produced by glemy/physics and draws
them to a canvas – glemy/game_tiers’s real, on-page <canvas> in the
shipped game, or a Deno-native OffscreenCanvas in
test/glemy/render_test.gleam, which exercises this with real GPU
execution, not mocks (see decision 0015 in docs/decisions.jsonl).
Types
An opaque handle to whatever WebGPU-capable canvas
render_entities_to_canvas should draw onto — a real <canvas>
element in a browser, or an OffscreenCanvas in a test (see
render_test.gleam). Gleam never inspects one of these directly,
only passes it through to FFI — but see get_canvas below for how
application code actually obtains one; this type has no other public
constructor.
pub type Canvas
An entity paired with the RGB color (each channel 0.0-1.0) it
should be drawn with – rendering has no concept of what an entity’s
color should be derived from (that’s entirely a particular game’s
decision, e.g. glemy/games/tiers’s tier-to-color table), so the
caller pairs each entity with its color directly rather than this
module reaching into game-specific lookup logic itself. A single
zipped list, not two separate same-length lists kept in sync by
convention – an earlier version of this API took entities and
colors as separate parameters, which meant a caller-side mismatch
(wrong length, wrong order) type-checked fine and only failed at
runtime, inside the FFI layer, when a color lookup ran off the end of
a too-short list. Pairing them in one list makes that mismatch
unrepresentable instead of merely undocumented.
pub type ColoredEntity =
#(entity.Entity, #(Float, Float, Float))
Values
pub fn get_canvas(id: String) -> Canvas
Obtains a Canvas handle for the real <canvas> element with id
in the current document — the only way application code constructs
a Canvas (test code builds one directly from an OffscreenCanvas
instead, with no DOM involved — see render_test_ffi.mjs). Lives
here, not in glemy/game_tiers, because Canvas is this module’s own
type: any runner built on this Core API needs this same function to
use render_entities_to_canvas at all, not a private copy of it.
pub fn render_entities_to_bytes(
entities: List(#(entity.Entity, #(Float, Float, Float))),
bounds: bounds.Bounds,
) -> promise.Promise(Result(List(Int), String))
Renders every entity as a circle — using its world-space position
and radius from glemy/physics/entity, mapped into clip space by
linearly scaling bounds to fill the viewport — to a
texture_widthxtexture_height offscreen texture, one draw call per
entity within a single render pass (not instanced — see the note in
render_ffi.mjs on why). Returns the resulting pixels as RGBA bytes
(0-255 each), row-major, 4 bytes per pixel,
texture_width * texture_height * 4 bytes total.
Note bounds’ width and height aren’t required to match — if they
differ, circles are stretched into ellipses along the wider axis
(there’s no aspect-ratio-preserving camera yet). Not an issue for a
square Bounds.
Tested for real in render_test.gleam: real GPU execution via Deno’s
native WebGPU, pixel bytes asserted on directly, no mocking.
pub fn render_entities_to_canvas(
entities: List(#(entity.Entity, #(Float, Float, Float))),
bounds: bounds.Bounds,
canvas: Canvas,
) -> promise.Promise(Result(List(Int), String))
Renders every entity the same way as render_entities_to_bytes, but
directly onto canvas’s own WebGPU context (canvas.getContext ("webgpu")) instead of a throwaway offscreen texture — real
GPU-resident presentation, no CPU roundtrip in between the render
and what actually ends up on screen. canvas can be a real
<canvas> element (what glemy/game_tiers uses) or an OffscreenCanvas
(what tests use) — both expose the same context surface. Still
returns the rendered pixels as bytes (via the same
copyTextureToBuffer readback render_entities_to_bytes uses, just
against the canvas’s current texture, with any WebGPU row padding
already stripped — see render_entities_to_canvas_raw), so callers
— including tests — can verify pixel correctness without a separate
mechanism.
Tested for real in render_test.gleam, using a genuine
OffscreenCanvas — Deno supports it (and its "webgpu" context)
natively, no browser or third-party automation library needed;
see decision 0015 in docs/decisions.jsonl.
See ColoredEntity’s own doc comment for why color is paired with
each entity here rather than looked up or supplied separately.
pub const texture_height: Int
pub const texture_width: Int
The offscreen texture’s dimensions. 64 is not an arbitrary round
number: at 4 bytes/pixel (rgba8unorm) it makes each row exactly 256
bytes, which is WebGPU’s required row alignment for
copyTextureToBuffer — so no padding math is needed when reading
pixels back.