Commitf7843440Recorded11 Jul 2026Repositorysigil-graphics

examples: web-sprite — sigil-graphics rendering in the browser (M2)

Message

The Milestone-2 proof (Option A): a Sigil program using (sigil graphics) — reused sokolgfx / sokolgp — renders in the browser through the raw-GLES3 sigil-wasm-gles3 binding, driven by the Sigil VM tick loop. No Emscripten, no sokol_app.

Per the Option-A decision, the example compiles sigil-graphics' cond-expanded C sources directly (with the web -D flags: SOKOLGLES3 + SOKOLEXTERNALGLLOADER) and imports the (sigil graphics) Sigil modules by path, depending on sigil-wasm-gles3 — NOT the sigil-graphics package, so sigil-app (which cannot build for wasm) is never pulled. src/c/app-register.c's sigilwasmapp_register forwards to sigil-graphics' module init so (sigil graphics) resolves in the browser exactly as it does natively.

main.sgl uses the same drawing API as native sigil-graphics (begin-frame / set-color / draw-filled-rect / draw-filled-circle / end-frame); the canvas + requestAnimationFrame loop come from (sigil browser gles3), which replaces sokol_app on web.

Cross-repo dev wiring is in dev-redirects.sgl (points codeberg:sigil/sigil at a local monorepo checkout with the unreleased sigil-wasm-gles3 + runtime hook); revert + sigil deps install to restore canonical from-git resolution.

Verified in google-chrome 143 (SwiftShader) over CDP: renders a moving sprite (gold rect + teal halo + corner markers), zero console errors, import surface = gl.* + wasi + sigilwasmgles3 only (no emscripten/egl).

Changed
 examples/web-sprite/dev-redirects.sgl       | 15 +++++++++++++++
 examples/web-sprite/index.template.html     | 24 ++++++++++++++++++++++++
 examples/web-sprite/package.sgl             | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 examples/web-sprite/src/c/app-register.c    | 16 ++++++++++++++++
 examples/web-sprite/src/web-sprite/main.sgl | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 examples/web-sprite/styles.css              | 15 +++++++++++++++
 6 files changed, 217 insertions(+)
Diff
examples/web-sprite/dev-redirects.sgladded
@@ -0,0 +1,15 @@
+1
;; Development redirects — point the monorepo dependency at a local checkout.
+2
;;
+3
;; The web build reuses unreleased monorepo packages (sigil-wasm-gles3 and the
+4
;; sigil_wasm_app_register runtime hook). Until they ship, redirect the whole
+5
;; codeberg:sigil/sigil repo at a local monorepo checkout that contains the
+6
;; graphics-on-web work, then `sigil deps install`.
+7
;;
+8
;; During M2 development this pointed at the task worktree
+9
;; (../../../task-sigil-graphics-web-m1); ../../../sigil is the canonical sibling
+10
;; monorepo checkout to use once the M2 monorepo branch is merged.
+11
(redirects
+12
repos: (list
+13
(for-repo
+14
url: "codeberg:sigil/sigil"
+15
use: (from-path dir: "../../../task-sigil-graphics-web-m1"))))
examples/web-sprite/index.template.htmladded
@@ -0,0 +1,24 @@
+1
<!doctype html>
+2
<html lang="en">
+3
<head>
+4
<meta charset="utf-8">
+5
<meta name="viewport" content="width=device-width, initial-scale=1">
+6
<title>sigil-graphics on the web (no Emscripten)</title>
+7
<link rel="icon" href="data:,">
+8
<link rel="stylesheet" href="styles.css">
+9
</head>
+10
<body>
+11
<main>
+12
<h1><code>sigil-graphics</code> → WebGL2, no Emscripten</h1>
+13
<p class="sub">
+14
A Sigil program using <code>(sigil graphics)</code> — reused
+15
<code>sokol_gfx</code> / <code>sokol_gp</code> — rendering in the browser
+16
through the <code>sigil-wasm-gles3</code> raw-GLES3 binding, driven by the
+17
Sigil VM tick loop. No <code>sokol_app</code>, no Emscripten.
+18
</p>
+19
<canvas id="stage" width="640" height="480"></canvas>
+20
<div id="status" hidden></div>
+21
</main>
+22
<!--sigil:app-->
+23
</body>
+24
</html>
examples/web-sprite/package.sgladded
@@ -0,0 +1,75 @@
+1
;;; web-sprite — sigil-graphics on the web (Milestone 2, Option A).
+2
;;;
+3
;;; Proves the graphics-on-web cond-expand seam end-to-end: a Sigil program using
+4
;;; (sigil graphics) renders in-browser through the sigil-wasm-gles3 raw-GLES3
+5
;;; binding, driven by the Sigil VM tick loop — no Emscripten, no sokol_app.
+6
;;;
+7
;;; Per the M2 "Option A" decision, this example compiles sigil-graphics' own
+8
;;; cond-expanded C sources directly (with the web -D flags) and imports the
+9
;;; (sigil graphics) Sigil modules by path, depending on sigil-wasm-gles3 (NOT
+10
;;; the sigil-graphics package, so sigil-app — which cannot build for wasm — is
+11
;;; never pulled). The monorepo WASM packages are wired via from-path here for
+12
;;; local dev; production packaging (a first-class sigil-graphics web config) is
+13
;;; the filed Option-B follow-up.
+14
+15
(define sigil-repo "codeberg:sigil/sigil")
+16
+17
(package
+18
name: "web-sprite"
+19
version: "0.1.0"
+20
sigil: "^0.17"
+21
entry: '(web-sprite main)
+22
+23
configs: (list
+24
(config
+25
name: 'web
+26
output-dir: "build/web"
+27
toolchain: 'zig
+28
target: "wasm32-wasi"
+29
debug?: #f
+30
optimize: 2
+31
bundle?: #t
+32
features: '(web wasm graphics)))
+33
+34
;; Canonical monorepo deps. Local dev wiring lives in dev-redirects.sgl
+35
;; (points sigil-repo at the graphics-on-web monorepo checkout); revert it +
+36
;; `sigil deps install` to restore. sigil-wasm-gles3 must be a released
+37
;; monorepo package for a from-git build without the redirect.
+38
dependencies: (list
+39
(from-git url: sigil-repo package: "sigil-lib" version: "^0.17")
+40
(from-git url: sigil-repo package: "sigil-stdlib" version: "^0.17")
+41
(from-git url: sigil-repo package: "sigil-wasm-runtime" version: "^0.17")
+42
(from-git url: sigil-repo package: "sigil-wasm-gles3" version: "^0.17"))
+43
+44
;; This app ships its own native code: sigil-graphics' sokol natives compiled
+45
;; for wasm, plus src/c/app-register.c whose sigil_wasm_app_register hook calls
+46
;; sigil-graphics' module init. The WASM runtime's exported sigil_wasm_init
+47
;; calls that weak hook, so the whole native chain (register -> graphics init
+48
;; -> sokol -> gl* imports) is reachable and survives --gc-sections. The C
+49
;; objects link loose (no static library), and gl_shim.h is found via
+50
;; sigil-wasm-gles3's propagated c-include-dirs.
+51
tasks: (list
+52
(task
+53
name: 'build
+54
description: "Build the sigil-graphics web-sprite demo (wasm32-wasi, GLES3 over sigil-wasm-gles3)"
+55
steps: (list
+56
(compile-c-sources
+57
sources: '("src/c/app-register.c"
+58
"../../src/c/sokol-graphics.c" "../../src/c/stb_impl.c"
+59
"../../src/c/graphics.c" "../../src/c/image.c" "../../src/c/font.c")
+60
include-dirs: '("../../vendor/sokol" "../../vendor/stb")
+61
flags: (with-sigil-c-flags '("-std=c99" "-D_GNU_SOURCE"
+62
"-DSOKOL_NO_ENTRY" "-DSOKOL_GLES3"
+63
"-DSOKOL_EXTERNAL_GL_LOADER"
+64
"-Wno-unused-parameter" "-Wno-unused-function"
+65
"-Wno-sign-compare")))
+66
;; Compile the shared (sigil graphics) modules from the sigil-graphics
+67
;; source tree into library-name-relative bytecode paths.
+68
(compile-sigil-modules
+69
sources: "../../src/sigil/**/*.sgl"
+70
output-layout: 'module-name)
+71
;; Then the local app entry module.
+72
(compile-sigil-modules sources: "src/**/*.sgl")
+73
(link-web-application
+74
name: "web-sprite"
+75
html-mount-id: "status")))))
examples/web-sprite/src/c/app-register.cadded
@@ -0,0 +1,16 @@
+1
/*
+2
* app-register.c — register this web app's own native modules.
+3
*
+4
* The web build reuses sigil-graphics' sokol natives (compiled from
+5
* ../../src/c). The bytecode WASM runtime calls the weak sigil_wasm_app_register
+6
* hook after the browser bridges; we forward it to sigil-graphics' module init
+7
* so (sigil graphics) resolves in the browser exactly as it does natively.
+8
*/
+9
#include <sigil/sigil.h>
+10
+11
extern void sigil__init_sigil_graphics_module(SigilVM *vm);
+12
+13
void sigil_wasm_app_register(SigilVM *vm)
+14
{
+15
sigil__init_sigil_graphics_module(vm);
+16
}
examples/web-sprite/src/web-sprite/main.sgladded
@@ -0,0 +1,72 @@
+1
;;; web-sprite / main — sigil-graphics rendering in the browser (Milestone 2)
+2
;;;
+3
;;; A Sigil program that draws through (sigil graphics) — i.e. REUSED sokol_gfx /
+4
;;; sokol_gp — in the browser, with no Emscripten and no sokol_app. The GL calls
+5
;;; are satisfied by the sigil-wasm-gles3 raw-GLES3 binding; the canvas + the
+6
;;; requestAnimationFrame loop come from that binding's app-shell surface
+7
;;; ((sigil browser gles3)), which replaces sokol_app on web.
+8
;;;
+9
;;; Same drawing API as native sigil-graphics (begin-frame / set-color /
+10
;;; draw-filled-rect / draw-filled-circle / end-frame) — the whole point of M2.
+11
+12
(define-library (web-sprite main)
+13
(import (sigil core)
+14
(sigil math)
+15
(sigil graphics)
+16
(sigil browser gles3))
+17
+18
(export main web-tick sigil-web-dispatch-event)
+19
+20
(begin
+21
;; Virtual playfield — letterboxed to the canvas so the scene is
+22
;; resolution-independent (exercises set-viewport + the swapchain sizing).
+23
(define FIELD-W 640)
+24
(define FIELD-H 480)
+25
+26
(define (web-tick timestamp-ms)
+27
(let* ((t (* timestamp-ms 0.001))
+28
(cx (+ 320.0 (* 200.0 (sin (* t 1.3)))))
+29
(cy (+ 240.0 (* 140.0 (cos (* t 1.7))))))
+30
;; Keep the drawing buffer matched to the CSS display size.
+31
(gles3-resize-to-display)
+32
+33
(begin-frame)
+34
+35
;; A moving textured-looking sprite: a filled rect body + a bright
+36
;; halo circle, drawn through sokol_gp via sigil-graphics.
+37
(set-color 0.90 0.70 0.30 1.0)
+38
(draw-filled-rect (- cx 48.0) (- cy 48.0) 96.0 96.0)
+39
+40
(set-color 0.20 0.72 0.75 1.0)
+41
(draw-filled-circle cx cy 34.0 32)
+42
+43
;; A few static corner markers so orientation is obvious.
+44
(set-color 0.85 0.25 0.35 1.0)
+45
(draw-filled-rect 8.0 8.0 40.0 40.0)
+46
(draw-filled-rect (- FIELD-W 48) 8.0 40.0 40.0)
+47
(draw-filled-rect 8.0 (- FIELD-H 48) 40.0 40.0)
+48
(draw-filled-rect (- FIELD-W 48) (- FIELD-H 48) 40.0 40.0)
+49
+50
(end-frame)))
+51
+52
;; Input hook required by the generated web loader (unused here).
+53
(define (sigil-web-dispatch-event type payload)
+54
#t)
+55
+56
(define (main)
+57
;; Order matters: the WebGL2 context must exist before gfx-setup runs
+58
;; sg_setup (sokol immediately issues gl* calls). use-canvas creates it;
+59
;; fail fast with a readable message if the canvas / WebGL2 is unavailable
+60
;; rather than crashing deep inside a sokol gl* call.
+61
(unless (gles3-use-canvas "#stage")
+62
(error "web-sprite: could not attach WebGL2 to #stage"))
+63
(gles3-resize-to-display)
+64
+65
;; Dark letterbox bars; the pass clears to this each frame.
+66
(set-letterbox-color 0.06 0.07 0.10 1.0)
+67
(set-viewport FIELD-W FIELD-H)
+68
+69
(gfx-setup)
+70
+71
;; Drive the frame from the browser RAF loop through the Sigil VM tick.
+72
(gles3-start-loop "web-tick"))))
examples/web-sprite/styles.cssadded
@@ -0,0 +1,15 @@
+1
:root { color-scheme: dark; }
+2
* { box-sizing: border-box; }
+3
body {
+4
margin: 0; min-height: 100vh; display: flex; justify-content: center;
+5
background: #0d1017; color: #e6e9ef;
+6
font: 15px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
+7
}
+8
main { width: min(760px, 92vw); padding: 3rem 0 4rem; }
+9
h1 { font-size: 1.35rem; font-weight: 650; margin: 0 0 0.5rem; }
+10
.sub { color: #8b93a7; margin: 0 0 1.5rem; max-width: 62ch; }
+11
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.9em; color: #e6b34d; }
+12
canvas {
+13
display: block; width: 100%; aspect-ratio: 4 / 3; height: auto;
+14
border-radius: 10px; background: #04060a; box-shadow: 0 12px 40px rgba(0,0,0,0.45);
+15
}