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.

Search Document