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