Commit9b1f7965Recorded11 Jul 2026Repositorysigil-graphics

graphics: cond-expand sokol_app/glue seam for a web (wasm32-wasi) target

Message

Milestone 2 of graphics-on-web: let sigil-graphics reuse sokolgfx/sokolgp in the browser via the sigil-wasm-gles3 raw-GLES3 binding, WITHOUT disturbing the native build. The seam is gated on wasm (predefined by the wasm target), so native (SOKOLGLCORE + sokolapp + sigil-app) is byte-for-byte behaviour-identical.

sokol-graphics.c: on web define nothing extra here but include glshim.h (from sigil-wasm-gles3) before sokolgfx.h so every gl* resolves to a WASM import in the "gl" module; drop sokolapp.h / sokolglue.h (no windowing on web); provide slog_func (sigil-app is not linked on web). Native branch unchanged.

graphics.c: route the six sokolapp / sokolglue touch points (sgsetup environment, begin-frame window size, end-frame swapchain, clear fallback size, shader resolution uniform) through a siggfx* abstraction. Native expands to the exact sapp/sglue_ calls as before; web supplies a manual default-framebuffer sgenvironment/sgswapchain (lifted from the M1 wasm-sokol-sprite example) and sources the canvas size from the sigilwasmgles3 app-shell import module.

Verified: all five sigil-graphics C files compile clean for wasm32-wasi (web path) and native graphics.c compiles unchanged; the native diff is limited to behaviour-preserving inline wrappers.

Changed
 src/c/graphics.c       | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
 src/c/sokol-graphics.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++-----------
 2 files changed, 108 insertions(+), 18 deletions(-)
Diff
src/c/graphics.cmodified
@@ -6,10 +6,20 @@
6
7
#include "sigil-internal.h"
8
9
/* Sokol headers (implementation is in sokol.c) */
+9
/* Sokol headers (implementation is in sokol-graphics.c).
+10
*
+11
* On web (wasm32-wasi) there is no windowing system: sokol_app / sokol_glue are
+12
* replaced by the sigil-wasm-gles3 JS bridge (canvas + WebGL2 context +
+13
* swapchain). Those two headers are therefore excluded on the web build; every
+14
* sapp / sglue touch point is routed through the sig_gfx_ abstraction below,
+15
* whose native branch expands to the exact same calls as before. */
+16
#if !defined(__wasm__)
17
#include "sokol_app.h"
+18
#endif
19
#include "sokol_gfx.h"
+20
#if !defined(__wasm__)
21
#include "sokol_glue.h"
+22
#endif
23
#include "sokol_gp.h"
24
#include "sokol_log.h"
25
@@ -19,6 +29,46 @@
29
#include <string.h>
30
#include <math.h>
31
#include <time.h>
+32
#include <stdint.h>
+33
+34
/* ---- platform abstraction: native (sokol_app/glue) vs web (GL bridge) -------
+35
* These four accessors are the ONLY sokol_app/sokol_glue touch points in this
+36
* file. Native expands to the exact sapp / sglue calls used before, so the
+37
* native build is behaviour-identical. Web supplies the default framebuffer
+38
* swapchain manually (lifted from the Milestone-1 wasm-sokol-sprite example) and
+39
* sources the canvas size from the sigil_wasm_gles3 app-shell import module. */
+40
#if defined(__wasm__)
+41
__attribute__((import_module("sigil_wasm_gles3"), import_name("canvas_width")))
+42
extern int sigil_wasm_gles3_canvas_width(void);
+43
__attribute__((import_module("sigil_wasm_gles3"), import_name("canvas_height")))
+44
extern int sigil_wasm_gles3_canvas_height(void);
+45
+46
static int sig_gfx_width(void) { return sigil_wasm_gles3_canvas_width(); }
+47
static int sig_gfx_height(void) { return sigil_wasm_gles3_canvas_height(); }
+48
+49
static sg_environment sig_gfx_environment(void) {
+50
sg_environment env = {0};
+51
env.defaults.color_format = SG_PIXELFORMAT_RGBA8;
+52
env.defaults.depth_format = SG_PIXELFORMAT_NONE;
+53
env.defaults.sample_count = 1;
+54
return env;
+55
}
+56
static sg_swapchain sig_gfx_swapchain(void) {
+57
sg_swapchain swap = {0};
+58
swap.width = sig_gfx_width();
+59
swap.height = sig_gfx_height();
+60
swap.sample_count = 1;
+61
swap.color_format = SG_PIXELFORMAT_RGBA8;
+62
swap.depth_format = SG_PIXELFORMAT_NONE;
+63
swap.gl.framebuffer = 0; /* default framebuffer */
+64
return swap;
+65
}
+66
#else
+67
static inline int sig_gfx_width(void) { return sapp_width(); }
+68
static inline int sig_gfx_height(void) { return sapp_height(); }
+69
static inline sg_environment sig_gfx_environment(void) { return sglue_environment(); }
+70
static inline sg_swapchain sig_gfx_swapchain(void) { return sglue_swapchain(); }
+71
#endif
72
73
#ifndef M_PI
74
#define M_PI 3.14159265358979323846
@@ -183,7 +233,7 @@ static Value native_gfx_setup(SigilVM *vm, int argc, Value *args)
233
/* Initialize sokol_gfx. Install slog_func so validation failures
234
* print a useful diagnostic before sokol aborts. */
235
sg_desc desc = {
186
.environment = sglue_environment(),
+236
.environment = sig_gfx_environment(),
237
.logger.func = slog_func,
238
};
239
sg_setup(&desc);
@@ -284,8 +334,8 @@ static Value native_begin_frame(SigilVM *vm, int argc, Value *args)
334
{
335
(void)vm; (void)argc; (void)args;
336
287
int window_w = sapp_width();
288
int window_h = sapp_height();
+337
int window_w = sig_gfx_width();
+338
int window_h = sig_gfx_height();
339
340
/* Begin sokol_gp frame with full window size */
341
sgp_begin(window_w, window_h);
@@ -332,7 +382,7 @@ static Value native_end_frame(SigilVM *vm, int argc, Value *args)
382
};
383
sg_pass pass = {
384
.action = pass_action,
335
.swapchain = sglue_swapchain()
+385
.swapchain = sig_gfx_swapchain()
386
};
387
sg_begin_pass(&pass);
388
@@ -369,7 +419,7 @@ static Value native_clear_screen(SigilVM *vm, int argc, Value *args)
419
if (virtual_viewport_enabled && virtual_width > 0 && virtual_height > 0) {
420
sgp_draw_filled_rect(0, 0, (float)virtual_width, (float)virtual_height);
421
} else {
372
sgp_draw_filled_rect(0, 0, (float)sapp_width(), (float)sapp_height());
+422
sgp_draw_filled_rect(0, 0, (float)sig_gfx_width(), (float)sig_gfx_height());
423
}
424
425
/* Reset to white for subsequent drawing */
@@ -1687,7 +1737,7 @@ static void apply_active_shader_uniforms(void)
1737
&t, sizeof(t));
1738
}
1739
if (sh->u_resolution_index >= 0) {
1690
float res[2] = {(float)sapp_width(), (float)sapp_height()};
+1740
float res[2] = {(float)sig_gfx_width(), (float)sig_gfx_height()};
1741
if (virtual_viewport_enabled) {
1742
res[0] = (float)virtual_width;
1743
res[1] = (float)virtual_height;
src/c/sokol-graphics.cmodified
@@ -1,14 +1,58 @@
1
/*
2
* sokol-graphics.c - Sokol implementation for sigil-graphics
3
*
4
* Implements sokol_gfx, sokol_gp, and sokol_glue.
5
* sokol_glue bridges sokol_app (from sigil-app) to sokol_gfx.
+4
* Implements sokol_gfx and sokol_gp (and, on native, sokol_glue).
5
*
7
* Note: sokol_app.h is included for declarations only (implementation in sigil-app).
8
* SOKOL_IMPL is only defined for the headers we implement here.
+6
* NATIVE (default): SOKOL_GLCORE. sokol_glue bridges sokol_app (implemented in
+7
* sigil-app) to sokol_gfx; sokol_app.h is included for declarations only, and
+8
* slog_func comes from sigil-app.
+9
*
+10
* WEB (wasm32-wasi, __wasm__): SOKOL_GLES3 + SOKOL_EXTERNAL_GL_LOADER (set via
+11
* the web build's -D flags). sokol's gl* calls resolve to WASM imports in the
+12
* "gl" module via gl_shim.h (shipped by sigil-wasm-gles3). There is no
+13
* sokol_app / sokol_glue on web — the canvas, WebGL2 context, and swapchain come
+14
* from the sigil-wasm-gles3 JS bridge (see graphics.c's sig_gfx_* abstraction),
+15
* so those headers are excluded and slog_func is provided here since sigil-app
+16
* is not linked on web.
17
*/
18
11
/* SOKOL_GLCORE is defined via compiler flags */
+19
/* Bump sgp's uniform-block buffer to 64 floats (256 bytes) so custom shaders
+20
* have room for the auto-uniforms (u_time, u_resolution) plus a reasonable
+21
* budget of caller-set uniforms. The default 8 floats (32 bytes) doesn't even
+22
* fit the auto-uniforms plus a single vec4. */
+23
#define SGP_UNIFORM_CONTENT_SLOTS 64
+24
+25
#if defined(__wasm__)
+26
+27
/* Web: bind every gl* sokol calls as a WASM import in the "gl" module. Must be
+28
* included before sokol_gfx.h so GL types/constants/prototypes are visible. */
+29
#include "gl_shim.h"
+30
+31
#define SOKOL_IMPL
+32
/* Order matters: gfx before gp. No sokol_glue (no sokol_app on web). */
+33
#include "sokol_gfx.h"
+34
#include "sokol_gp.h"
+35
+36
/* slog_func is provided by sigil-app on native; on web sigil-app is not linked,
+37
* so supply it here. Routes to stderr (wasi fd_write -> console) so sokol
+38
* validation failures stay visible without any import beyond gl.* + wasi. */
+39
#include <stdio.h>
+40
#include <stdint.h>
+41
void slog_func(const char* tag, uint32_t log_level, uint32_t log_item_id,
+42
const char* message_or_null, uint32_t line_nr,
+43
const char* filename_or_null, void* user_data) {
+44
(void)log_item_id; (void)user_data;
+45
fprintf(stderr, "sokol[level=%u] %s:%u [%s] %s\n",
+46
log_level,
+47
filename_or_null ? filename_or_null : "?",
+48
line_nr,
+49
tag ? tag : "sokol",
+50
message_or_null ? message_or_null : "");
+51
}
+52
+53
#else
+54
+55
/* Native: SOKOL_GLCORE is defined via compiler flags. */
56
57
/* Include sokol_app declarations WITHOUT implementation (implemented in sigil-app) */
58
#include "sokol_app.h"
@@ -16,12 +60,6 @@
60
/* Now define SOKOL_IMPL for the headers we implement */
61
#define SOKOL_IMPL
62
19
/* Bump sgp's uniform-block buffer to 64 floats (256 bytes) so custom
20
* shaders have room for the auto-uniforms (u_time, u_resolution) plus
21
* a reasonable budget of caller-set uniforms. The default 8 floats
22
* (32 bytes) doesn't even fit the auto-uniforms plus a single vec4. */
23
#define SGP_UNIFORM_CONTENT_SLOTS 64
24
63
/* Order matters: gfx before glue, glue before gp */
64
#include "sokol_gfx.h"
65
#include "sokol_glue.h"
@@ -29,3 +67,5 @@
67
/* slog_func is implemented in sigil-app's archive (sigil-app's
68
* sokol-app.c picks up SOKOL_LOG_IMPL via SOKOL_IMPL). graphics.c
69
* just declares it via sokol_log.h. */
+70
+71
#endif