Commit7b283fd5Recorded19 Sep 2026Repositorysigil-graphics

Parse uniform arrays, raise the uniform block to 1 KiB, add pixel-upload textures

Message
  • parse_fragment_uniforms reads uniform <type> <name>[N]; (whitespace tolerant, optional lowp/mediump/highp qualifier) and lays the entry out as N * element size with array_count = N in the sg_shader_desc.
  • SIGIL_GFX_UNIFORM_BUFFER_SIZE and SGP_UNIFORM_CONTENT_SLOTS go from 256 bytes / 64 floats to 1024 bytes / 256 floats together.
  • set-shader-uniform takes a list or vector, flat or nested per element, for any entry; the float count must match exactly.
  • make-texture-from-pixels and update-texture: RGBA8 bytevectors over a dynamic_update sg_image; one upload per texture per frame, refused with an error rather than tripping sokol's validation layer.
  • make-render-target and make-texture-from-pixels take an optional 'nearest / 'linear sampler filter.
  • u_resolution inside with-render-target is the target's size.
Changed
 src/c/graphics.c       | 452 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
 src/c/sokol-graphics.c |  17 +++++--
 src/sigil/graphics.sgl |   7 ++-
 3 files changed, 416 insertions(+), 60 deletions(-)
Diff
src/c/graphics.cmodified
@@ -107,8 +107,21 @@ typedef struct {
107
int width;
108
int height;
109
bool owns_resources;
+110
/* make-texture-from-pixels creates the image with dynamic_update
+111
* usage so update-texture can refill it. sokol_gfx allows one
+112
* sg_update_image per image per frame; `upload_frame` is the
+113
* gfx_frame_serial of the last upload so update-texture can refuse
+114
* a second one instead of tripping sokol's validation layer. */
+115
bool dynamic;
+116
uint32_t upload_frame;
117
} GfxTexture;
118
+119
/* Frame serial: advanced by end-frame right after sg_commit, so it
+120
* moves in lockstep with sokol_gfx's private frame index (which is what
+121
* sg_update_image's once-per-frame rule is checked against). Starts at
+122
* 1 like sokol's. */
+123
static uint32_t gfx_frame_serial = 1;
+124
125
/* Render target structure.
126
*
127
* Holds a color image, a color-attachment view (for rendering INTO),
@@ -132,16 +145,28 @@ typedef struct {
145
146
/* Shader uniform entry — one per uniform declared in the user's
147
* fragment GLSL. The `offset` is into the per-shader uniform buffer;
−135
* sgp_set_uniform ships the buffer contents to the GPU per-draw. */
+148
* sgp_set_uniform ships the buffer contents to the GPU per-draw.
+149
*
+150
* SIGIL_GFX_UNIFORM_BUFFER_SIZE must equal SGP_UNIFORM_CONTENT_SLOTS *
+151
* sizeof(float) (see sokol-graphics.c): sgp_set_uniform asserts the
+152
* pushed size fits its per-draw slot. 1 KiB holds 16 members of up to
+153
* `vec4 name[16]` each, minus the auto-uniforms; a larger block costs
+154
* sokol_gp `max_commands` (16384) copies of it in its uniform pool.
+155
*
+156
* sokol_gfx caps a uniform block at SG_MAX_UNIFORMBLOCK_MEMBERS (16)
+157
* declared members; an array counts as one member, so the 16 here is
+158
* that cap, not a float budget. */
159
#define SIGIL_GFX_MAX_UNIFORMS 16
−137
#define SIGIL_GFX_UNIFORM_BUFFER_SIZE 256 /* matches SGP_UNIFORM_CONTENT_SLOTS=64 floats */
+160
#define SIGIL_GFX_UNIFORM_BUFFER_SIZE 1024 /* matches SGP_UNIFORM_CONTENT_SLOTS=256 floats */
161
#define SIGIL_GFX_UNIFORM_NAME_MAX 64
162
163
typedef struct {
164
char name[SIGIL_GFX_UNIFORM_NAME_MAX];
165
sg_uniform_type type;
166
uint32_t offset;
−144
uint32_t size;
+167
uint32_t size; /* total bytes = elem_size * array_count */
+168
uint32_t elem_size; /* bytes of one element (float=4 ... mat4=64) */
+169
int array_count; /* 1 for a scalar/vector, N for `name[N]` */
170
} GfxUniformEntry;
171
172
/* Shader structure.
@@ -172,6 +197,13 @@ typedef struct {
197
* u_resolution tracks viewport size without caller intervention. */
198
static GfxShader *active_shader = NULL;
199
+200
/* The render-target pass in progress, if any (%begin-rt-pass sets it,
+201
* %end-rt-pass clears it; sokol_gfx passes do not nest). While active,
+202
* the auto-uniform u_resolution reports this size. */
+203
static bool rt_pass_active = false;
+204
static int rt_pass_width = 0;
+205
static int rt_pass_height = 0;
+206
207
/* Process start time for u_time. Set on first sg_setup. */
208
static double gfx_start_time = 0.0;
209
@@ -392,6 +424,7 @@ static Value native_end_frame(SigilVM *vm, int argc, Value *args)
424
425
sg_end_pass();
426
sg_commit();
+427
gfx_frame_serial++;
428
429
return SIGIL_NIL;
430
}
@@ -936,12 +969,204 @@ static Value native_load_texture(SigilVM *vm, int argc, Value *args)
969
tex->width = width;
970
tex->height = height;
971
tex->owns_resources = true;
+972
tex->dynamic = false;
+973
tex->upload_frame = 0;
+974
+975
ensure_texture_type(vm);
+976
return sigil_make_foreign(vm, texture_type_tag, tex, texture_destructor,
+977
sizeof(GfxTexture));
+978
}
+979
+980
/* Parse an optional sampler-filter argument: the symbol `nearest` or
+981
* `linear` (a string is accepted too). Returns 0 and sets the VM error
+982
* on anything else. `who` names the caller for the message. */
+983
static int filter_from_value(SigilVM *vm, Value v, const char *who, sg_filter *out)
+984
{
+985
const char *name = NULL;
+986
if (sigil_is_symbol(v)) {
+987
name = sigil_symbol_name(v);
+988
} else if (sigil_is_string(v)) {
+989
name = sigil_string_bytes(v);
+990
}
+991
if (name && strcmp(name, "nearest") == 0) { *out = SG_FILTER_NEAREST; return 1; }
+992
if (name && strcmp(name, "linear") == 0) { *out = SG_FILTER_LINEAR; return 1; }
+993
char msg[160];
+994
snprintf(msg, sizeof(msg), "%s: filter must be 'nearest or 'linear", who);
+995
sigil__vm_set_error(vm, SIGIL_ERR_TYPE, msg);
+996
return 0;
+997
}
+998
+999
/* Bytevector argument check shared by the pixel-upload natives: the
+1000
* value must be a bytevector holding exactly width*height*4 bytes
+1001
* (RGBA8, row-major, top row first). Returns the byte pointer or NULL
+1002
* with the VM error set. */
+1003
static const uint8_t *pixels_from_bytevector(SigilVM *vm, Value v, int width,
+1004
int height, const char *who)
+1005
{
+1006
if (!sigil_is_bytevector(v)) {
+1007
char msg[160];
+1008
snprintf(msg, sizeof(msg), "%s: pixels must be a bytevector", who);
+1009
sigil__vm_set_error(vm, SIGIL_ERR_TYPE, msg);
+1010
return NULL;
+1011
}
+1012
size_t need = (size_t)width * (size_t)height * 4u;
+1013
size_t got = sigil_bytevector_length(v);
+1014
if (got != need) {
+1015
char msg[200];
+1016
snprintf(msg, sizeof(msg),
+1017
"%s: pixel size mismatch: %dx%d RGBA8 needs %zu bytes, got %zu",
+1018
who, width, height, need, got);
+1019
sigil__vm_set_error(vm, SIGIL_ERR_TYPE, msg);
+1020
return NULL;
+1021
}
+1022
return sigil_bytevector_data(v);
+1023
}
+1024
+1025
/*
+1026
* (make-texture-from-pixels width height bytes [filter]) -> <texture>
+1027
*
+1028
* Create a GPU texture from CPU-side RGBA8 pixels: `bytes` is a
+1029
* bytevector of exactly width*height*4 bytes, row-major with the top
+1030
* row first (the same orientation load-texture gives a decoded image).
+1031
* `filter` is 'linear (default, as load-texture) or 'nearest.
+1032
*
+1033
* The image is created with dynamic_update usage so update-texture can
+1034
* refill it; the initial pixels go up through sg_update_image, which
+1035
* counts as this frame's one allowed upload (see update-texture).
+1036
*/
+1037
static Value native_make_texture_from_pixels(SigilVM *vm, int argc, Value *args)
+1038
{
+1039
if (argc < 3) {
+1040
sigil__vm_error(vm, SIGIL_ERR_ARITY,
+1041
"make-texture-from-pixels: requires width, height, bytes");
+1042
return SIGIL_UNDEFINED;
+1043
}
+1044
if (!sigil_is_fixnum(args[0]) || !sigil_is_fixnum(args[1])) {
+1045
sigil__vm_set_error(vm, SIGIL_ERR_TYPE,
+1046
"make-texture-from-pixels: width and height must be integers");
+1047
return SIGIL_UNDEFINED;
+1048
}
+1049
int width = (int)sigil_as_fixnum(args[0]);
+1050
int height = (int)sigil_as_fixnum(args[1]);
+1051
if (width <= 0 || height <= 0) {
+1052
sigil__vm_set_error(vm, SIGIL_ERR_TYPE,
+1053
"make-texture-from-pixels: width and height must be positive");
+1054
return SIGIL_UNDEFINED;
+1055
}
+1056
const uint8_t *pixels = pixels_from_bytevector(vm, args[2], width, height,
+1057
"make-texture-from-pixels");
+1058
if (!pixels) return SIGIL_UNDEFINED;
+1059
+1060
sg_filter filter = SG_FILTER_LINEAR;
+1061
if (argc > 3 && !filter_from_value(vm, args[3], "make-texture-from-pixels", &filter)) {
+1062
return SIGIL_UNDEFINED;
+1063
}
+1064
+1065
/* dynamic_update images must be created without initial data
+1066
* (sokol validates that); the first fill is an update below. */
+1067
sg_image_desc img_desc = {
+1068
.usage = { .dynamic_update = true },
+1069
.width = width,
+1070
.height = height,
+1071
.pixel_format = SG_PIXELFORMAT_RGBA8,
+1072
};
+1073
sg_image img = sg_make_image(&img_desc);
+1074
if (img.id == SG_INVALID_ID) {
+1075
sigil__vm_set_error(vm, SIGIL_ERR_RUNTIME,
+1076
"make-texture-from-pixels: failed to create GPU texture");
+1077
return SIGIL_FALSE;
+1078
}
+1079
sg_image_data data = {
+1080
.mip_levels[0] = { .ptr = pixels, .size = (size_t)width * height * 4 }
+1081
};
+1082
sg_update_image(img, &data);
+1083
+1084
sg_sampler_desc smp_desc = {
+1085
.min_filter = filter,
+1086
.mag_filter = filter,
+1087
.wrap_u = SG_WRAP_CLAMP_TO_EDGE,
+1088
.wrap_v = SG_WRAP_CLAMP_TO_EDGE
+1089
};
+1090
sg_sampler smp = sg_make_sampler(&smp_desc);
+1091
+1092
sg_view view = sgp_make_texture_view_from_image(img, "sigil-pixel-texture");
+1093
if (view.id == SG_INVALID_ID) {
+1094
sg_destroy_image(img);
+1095
sg_destroy_sampler(smp);
+1096
sigil__vm_set_error(vm, SIGIL_ERR_RUNTIME,
+1097
"make-texture-from-pixels: failed to create texture view");
+1098
return SIGIL_FALSE;
+1099
}
+1100
+1101
GfxTexture *tex = malloc(sizeof(GfxTexture));
+1102
if (!tex) {
+1103
sg_destroy_view(view);
+1104
sg_destroy_image(img);
+1105
sg_destroy_sampler(smp);
+1106
sigil__vm_set_error(vm, SIGIL_ERR_MEMORY, "make-texture-from-pixels: out of memory");
+1107
return SIGIL_FALSE;
+1108
}
+1109
+1110
tex->handle = img;
+1111
tex->sampler = smp;
+1112
tex->view = view;
+1113
tex->width = width;
+1114
tex->height = height;
+1115
tex->owns_resources = true;
+1116
tex->dynamic = true;
+1117
tex->upload_frame = gfx_frame_serial;
1118
1119
ensure_texture_type(vm);
1120
return sigil_make_foreign(vm, texture_type_tag, tex, texture_destructor,
1121
sizeof(GfxTexture));
1122
}
1123
+1124
/*
+1125
* (update-texture tex bytes) - Replace a pixel texture's contents
+1126
*
+1127
* `tex` must come from make-texture-from-pixels (load-texture images
+1128
* are immutable on the GPU); `bytes` has the same shape as at creation:
+1129
* width*height*4 RGBA8 bytes, top row first. Errors on a size mismatch.
+1130
*
+1131
* sokol_gfx permits one sg_update_image per image per frame (a frame
+1132
* ends at end-frame). make-texture-from-pixels spends that frame's
+1133
* upload, so the first update-texture of a texture made in the same
+1134
* frame is refused; a second update-texture in one frame is refused
+1135
* the same way. The error names the texture's last upload frame.
+1136
*/
+1137
static Value native_update_texture(SigilVM *vm, int argc, Value *args)
+1138
{
+1139
if (argc < 2) {
+1140
sigil__vm_error(vm, SIGIL_ERR_ARITY,
+1141
"update-texture: requires texture, bytes");
+1142
return SIGIL_UNDEFINED;
+1143
}
+1144
GfxTexture *tex = get_texture(vm, args[0]);
+1145
if (!tex) {
+1146
sigil__vm_set_error(vm, SIGIL_ERR_TYPE, "update-texture: expected texture");
+1147
return SIGIL_UNDEFINED;
+1148
}
+1149
if (!tex->dynamic) {
+1150
sigil__vm_set_error(vm, SIGIL_ERR_RUNTIME,
+1151
"update-texture: texture is immutable (only make-texture-from-pixels textures can be updated)");
+1152
return SIGIL_UNDEFINED;
+1153
}
+1154
const uint8_t *pixels = pixels_from_bytevector(vm, args[1], tex->width, tex->height,
+1155
"update-texture");
+1156
if (!pixels) return SIGIL_UNDEFINED;
+1157
if (tex->upload_frame == gfx_frame_serial) {
+1158
sigil__vm_set_error(vm, SIGIL_ERR_RUNTIME,
+1159
"update-texture: texture already uploaded this frame (sokol_gfx allows one upload per texture per frame; end-frame starts the next)");
+1160
return SIGIL_UNDEFINED;
+1161
}
+1162
sg_image_data data = {
+1163
.mip_levels[0] = { .ptr = pixels, .size = (size_t)tex->width * tex->height * 4 }
+1164
};
+1165
sg_update_image(tex->handle, &data);
+1166
tex->upload_frame = gfx_frame_serial;
+1167
return SIGIL_NIL;
+1168
}
+1169
1170
/*
1171
* (texture? obj) -> boolean
1172
*/
@@ -1138,6 +1363,16 @@ static Value native_make_render_target(SigilVM *vm, int argc, Value *args)
1363
return SIGIL_FALSE;
1364
}
1365
+1366
/* Optional third argument: the sampler filter used when this target
+1367
* is drawn as a texture — 'linear (default, the v0.10 behaviour) or
+1368
* 'nearest for texel-exact reads (cell grids, pixel art). Note that
+1369
* GLSL texelFetch bypasses the sampler entirely, so a shader that
+1370
* reads neighbours by integer texel works with either. */
+1371
sg_filter filter = SG_FILTER_LINEAR;
+1372
if (argc > 2 && !filter_from_value(vm, args[2], "make-render-target", &filter)) {
+1373
return SIGIL_UNDEFINED;
+1374
}
+1375
1376
/* Pixel format and sample count default to sg_environment.defaults
1377
* (i.e., the swap chain's color format / sample count). Matching them
1378
* is required so sgp's default pipelines validate against this pass —
@@ -1209,8 +1444,8 @@ static Value native_make_render_target(SigilVM *vm, int argc, Value *args)
1444
}
1445
1446
sg_sampler_desc smp_desc = {
−1212
.min_filter = SG_FILTER_LINEAR,
−1213
.mag_filter = SG_FILTER_LINEAR,
+1447
.min_filter = filter,
+1448
.mag_filter = filter,
1449
.wrap_u = SG_WRAP_CLAMP_TO_EDGE,
1450
.wrap_v = SG_WRAP_CLAMP_TO_EDGE,
1451
};
@@ -1354,6 +1589,12 @@ static Value native_begin_rt_pass(SigilVM *vm, int argc, Value *args)
1589
};
1590
sg_begin_pass(&pass);
1591
+1592
/* A shader bound inside this pass sees u_resolution = the target's
+1593
* size (it is the surface being drawn), not the window's. */
+1594
rt_pass_active = true;
+1595
rt_pass_width = rt->width;
+1596
rt_pass_height = rt->height;
+1597
1598
return SIGIL_NIL;
1599
}
1600
@@ -1370,6 +1611,7 @@ static Value native_end_rt_pass(SigilVM *vm, int argc, Value *args)
1611
sgp_flush();
1612
sg_end_pass();
1613
sgp_end();
+1614
rt_pass_active = false;
1615
1616
return SIGIL_NIL;
1617
}
@@ -1405,6 +1647,8 @@ static Value native_render_target_to_texture(SigilVM *vm, int argc, Value *args)
1647
tex->width = rt->width;
1648
tex->height = rt->height;
1649
tex->owns_resources = false;
+1650
tex->dynamic = false;
+1651
tex->upload_frame = 0;
1652
1653
ensure_texture_type(vm);
1654
return sigil_make_foreign(vm, texture_type_tag, tex, texture_destructor,
@@ -1511,15 +1755,35 @@ static int map_glsl_type(const char *tok, sg_uniform_type *out_type, uint32_t *o
1755
return 0;
1756
}
1757
−1514
/* Parse the user's fragment GLSL for `uniform <type> <name>;` declarations.
+1758
static int is_glsl_space(char c)
+1759
{
+1760
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+1761
}
+1762
+1763
/* Parse the user's fragment GLSL for `uniform <type> <name>;` and
+1764
* `uniform <type> <name>[N];` declarations.
1765
*
1766
* Builds the GfxShader's uniform layout. Skips sampler2D — those are
1767
* texture bindings, not uniform-block entries (the channel-0 binding is
1768
* managed by the draw primitive). Recognised types: float, vec2, vec3,
−1519
* vec4, mat4. Unknown types are silently skipped (the shader-create call
−1520
* will fail later at link time if names mismatch — fine).
+1769
* vec4, mat4, optionally preceded by a precision qualifier (lowp /
+1770
* mediump / highp, as GLSL ES sources write). Unknown types are silently
+1771
* skipped (the shader-create call will fail later at link time if names
+1772
* mismatch — fine).
+1773
*
+1774
* An array suffix `[N]` (N a literal positive integer; whitespace is
+1775
* allowed around N and before the bracket) makes the entry N elements
+1776
* long: size = N * element size, array_count = N. A malformed suffix
+1777
* (no literal, no closing bracket) skips the declaration the same way an
+1778
* unknown type does, so the link reports the mismatch.
1779
*
−1522
* NATIVE layout: tightly packed, no padding. */
+1780
* NATIVE layout: tightly packed, no padding — arrays included, which is
+1781
* what sokol_gfx's _sg_uniform_size computes for SG_UNIFORMLAYOUT_NATIVE
+1782
* and what glUniform*fv consumes. Stops at SIGIL_GFX_MAX_UNIFORMS members
+1783
* (sokol's SG_MAX_UNIFORMBLOCK_MEMBERS) or when the block would exceed
+1784
* SIGIL_GFX_UNIFORM_BUFFER_SIZE; a truncated layout links with a name
+1785
* mismatch rather than silently dropping a member (sokol's GL backend
+1786
* logs the missing uniform). */
1787
static void parse_fragment_uniforms(GfxShader *sh, const char *frag_src)
1788
{
1789
const char *p = frag_src;
@@ -1529,28 +1793,33 @@ static void parse_fragment_uniforms(GfxShader *sh, const char *frag_src)
1793
sh->u_resolution_index = -1;
1794
1795
while (*p) {
−1532
/* Skip whitespace, then look for the literal "uniform " token at
−1533
* a line boundary or after whitespace. */
+1796
/* Look for the literal "uniform" token at a line boundary or
+1797
* after whitespace. */
1798
const char *u = strstr(p, "uniform");
1799
if (!u) break;
−1536
/* Make sure 'uniform' is at start of token (preceded by whitespace
−1537
* or newline or BOF). */
−1538
if (u != frag_src) {
−1539
char prev = u[-1];
−1540
if (prev != ' ' && prev != '\t' && prev != '\n' && prev != '\r') {
−1541
p = u + 7;
−1542
continue;
−1543
}
+1800
if (u != frag_src && !is_glsl_space(u[-1])) {
+1801
p = u + 7;
+1802
continue;
1803
}
1804
const char *q = u + 7;
−1546
while (*q == ' ' || *q == '\t') q++;
−1547
/* Read type token. */
+1805
if (!is_glsl_space(*q)) { p = q; continue; }
+1806
while (is_glsl_space(*q)) q++;
+1807
/* Read type token; a precision qualifier first is skipped. */
1808
char type_buf[32];
1809
size_t ti = 0;
−1550
while (*q && *q != ' ' && *q != '\t' && ti < sizeof(type_buf)-1) {
+1810
while (*q && !is_glsl_space(*q) && *q != ';' && ti < sizeof(type_buf)-1) {
1811
type_buf[ti++] = *q++;
1812
}
1813
type_buf[ti] = '\0';
+1814
if (strcmp(type_buf, "lowp") == 0 || strcmp(type_buf, "mediump") == 0 ||
+1815
strcmp(type_buf, "highp") == 0) {
+1816
while (is_glsl_space(*q)) q++;
+1817
ti = 0;
+1818
while (*q && !is_glsl_space(*q) && *q != ';' && ti < sizeof(type_buf)-1) {
+1819
type_buf[ti++] = *q++;
+1820
}
+1821
type_buf[ti] = '\0';
+1822
}
1823
/* Skip sampler2D / sampler types — those are texture bindings,
1824
* not uniform-block entries. */
1825
if (strncmp(type_buf, "sampler", 7) == 0) {
@@ -1558,21 +1827,45 @@ static void parse_fragment_uniforms(GfxShader *sh, const char *frag_src)
1827
continue;
1828
}
1829
sg_uniform_type utype = SG_UNIFORMTYPE_INVALID;
−1561
uint32_t usize = 0;
−1562
if (!map_glsl_type(type_buf, &utype, &usize)) {
+1830
uint32_t elem_size = 0;
+1831
if (!map_glsl_type(type_buf, &utype, &elem_size)) {
1832
p = q;
1833
continue;
1834
}
−1566
while (*q == ' ' || *q == '\t') q++;
−1567
/* Read name token (until ';' or whitespace or '['). */
+1835
while (is_glsl_space(*q)) q++;
+1836
/* Read name token (until ';', whitespace or '['). */
1837
char name_buf[SIGIL_GFX_UNIFORM_NAME_MAX];
1838
size_t ni = 0;
−1570
while (*q && *q != ';' && *q != ' ' && *q != '\t' && *q != '[' &&
+1839
while (*q && *q != ';' && !is_glsl_space(*q) && *q != '[' &&
1840
ni < sizeof(name_buf)-1) {
1841
name_buf[ni++] = *q++;
1842
}
1843
name_buf[ni] = '\0';
1844
if (ni == 0) { p = q; continue; }
+1845
/* Optional array suffix. */
+1846
int array_count = 1;
+1847
while (is_glsl_space(*q)) q++;
+1848
if (*q == '[') {
+1849
q++;
+1850
while (is_glsl_space(*q)) q++;
+1851
long n = 0;
+1852
int digits = 0;
+1853
while (*q >= '0' && *q <= '9') {
+1854
n = n * 10 + (*q - '0');
+1855
digits++;
+1856
q++;
+1857
if (n > 4096) break; /* absurd; bail on this decl */
+1858
}
+1859
while (is_glsl_space(*q)) q++;
+1860
if (digits == 0 || n <= 0 || n > 4096 || *q != ']') {
+1861
/* Not a literal-sized array we can lay out; skip it. */
+1862
p = q;
+1863
continue;
+1864
}
+1865
q++;
+1866
array_count = (int)n;
+1867
}
+1868
uint32_t usize = elem_size * (uint32_t)array_count;
1869
if (sh->num_uniforms >= SIGIL_GFX_MAX_UNIFORMS) break;
1870
if (cursor + usize > sizeof(sh->buffer)) break;
1871
GfxUniformEntry *ent = &sh->uniforms[sh->num_uniforms];
@@ -1581,6 +1874,8 @@ static void parse_fragment_uniforms(GfxShader *sh, const char *frag_src)
1874
ent->type = utype;
1875
ent->offset = cursor;
1876
ent->size = usize;
+1877
ent->elem_size = elem_size;
+1878
ent->array_count = array_count;
1879
if (strcmp(name_buf, "u_time") == 0) {
1880
sh->u_time_index = sh->num_uniforms;
1881
} else if (strcmp(name_buf, "u_resolution") == 0) {
@@ -1646,9 +1941,10 @@ static Value native_load_shader(SigilVM *vm, int argc, Value *args)
1941
/* Uniform block on the fragment stage at slot 1 (matches sgp's
1942
* SGP_UNIFORM_SLOT_FRAGMENT — sgp_flush emits fragment uniforms
1943
* to slot 1 when sgp_set_uniform's fs_size > 0). NATIVE layout —
−1649
* tightly packed, alignment=1, matches my parser's offset
−1650
* computation. array_count must be >= 1 (sokol asserts > 0 in
−1651
* _sg_uniform_size). */
+1944
* tightly packed, alignment=1, matches the parser's offset
+1945
* computation. array_count is 1 for scalars/vectors and N for a
+1946
* `name[N]` declaration (sokol asserts > 0 in _sg_uniform_size and
+1947
* passes it to glUniform*fv as the element count). */
1948
if (sh->num_uniforms > 0) {
1949
desc.uniform_blocks[1].stage = SG_SHADERSTAGE_FRAGMENT;
1950
desc.uniform_blocks[1].size = sh->buffer_size;

Showing the first 500 of 662 diff lines for this file. This diff is INCOMPLETE; read the file or clone the repository for the rest.

src/c/sokol-graphics.cmodified
@@ -16,11 +16,18 @@
16
* is not linked on web.
17
*/
18
−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
+19
/* sgp's per-draw uniform buffer: 256 floats (1 KiB). Must equal
+20
* SIGIL_GFX_UNIFORM_BUFFER_SIZE / sizeof(float) in graphics.c, which
+21
* mirrors the block on the CPU side and asserts nothing larger is pushed.
+22
* sokol_gp's default is 8 floats (32 bytes), too small for even the
+23
* auto-uniforms (u_time, u_resolution) plus one vec4; 64 floats (v0.10)
+24
* held a handful of vec4s; 256 floats holds uniform arrays such as
+25
* `vec4 palette[16]` beside them. Cost: sokol_gp keeps max_commands
+26
* (16384) copies of the sgp_uniform struct in its pool, so the pool goes
+27
* from 4.3 MB to 16.8 MB, allocated and zeroed once in sgp_setup; per draw
+28
* under a custom shader it memcmps one 1 KiB struct. Default-pipeline
+29
* draws (no shader bound) skip the uniform path entirely. */
+30
#define SGP_UNIFORM_CONTENT_SLOTS 256
31
32
#if defined(__wasm__)
33
src/sigil/graphics.sglmodified
@@ -42,6 +42,7 @@
42
load-texture
43
texture? texture-width texture-height
44
draw-texture draw-texture-region
+45
make-texture-from-pixels update-texture
46
47
;; Render targets (offscreen render-to-texture)
48
make-render-target
@@ -114,8 +115,10 @@
115
116
;; Public render-target constructor — wraps the raw native to keep
117
;; the surface name additive (the existing C native is %make-render-target).
−117
(define (make-render-target width height)
−118
(%make-render-target width height))
+118
;; The optional filter is 'linear (default) or 'nearest: how the target
+119
;; samples when drawn as a texture (nearest for texel-exact cell grids).
+120
(define (make-render-target width height . filter)
+121
(apply %make-render-target width height filter))
122
123
;; Run body with the render target bound as the active draw surface.
124
;; All draws inside body land in rt's color image. Uses dynamic-wind