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