Commitd5e1c451Recorded26 Apr 2026Repositorysigil-graphics

fix(graphics): y-flip render-target composite + skip depth load/store

Message

David flagged the cinder integration showed the scene upside-down. sokol_gfx writes to offscreen render-target memory in OpenGL native bottom-up convention, so the texture, when sampled with default top-down UV, appears vertically mirrored on the swap chain.

draw-render-target now sources from src=(0, height, width, -height) so the texcoord V range is inverted on sample — scene is upright when composited. Inside with-render-target, user coordinates remain top-left-origin (matching the swap-chain convention), so user code isn't affected.

Also drops the per-frame depth-stencil clear: loadaction and storeaction both DONTCARE. The depth attachment exists only because sgp's pipeline validation requires the pass and pipeline depth formats to match; 2D rendering never reads or writes it, so loading and storing it is wasted work.

Note: render-target->texture (Phase 2 consumer path) returns a borrowed wrapper whose memory is still in GL bottom-up layout — custom shaders that sample it directly should flip V themselves (or use draw-render-target which handles the flip). Will be documented + smoke-tested in Phase 2.

Changed
 src/c/graphics.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)
Diff
src/c/graphics.cmodified
@@ -1212,6 +1212,11 @@ static Value native_begin_rt_pass(SigilVM *vm, int argc, Value *args)
1212
sgp_viewport(0, 0, rt->width, rt->height);
1213
sgp_project(0, (float)rt->width, 0, (float)rt->height);
1214
+1215
/* Color: clear to transparent black on entry, keep contents on exit
+1216
* (the swap-chain composite reads it). Depth: don't load, don't
+1217
* store — the depth attachment exists only to satisfy sgp's
+1218
* pipeline-validation requirement that pass and pipeline depth
+1219
* formats match; 2D rendering never reads or writes it. */
1220
sg_pass_action act = {
1221
.colors[0] = {
1222
.load_action = SG_LOADACTION_CLEAR,
@@ -1219,9 +1224,8 @@ static Value native_begin_rt_pass(SigilVM *vm, int argc, Value *args)
1224
.clear_value = {0.0f, 0.0f, 0.0f, 0.0f},
1225
},
1226
.depth = {
1222
.load_action = SG_LOADACTION_CLEAR,
+1227
.load_action = SG_LOADACTION_DONTCARE,
1228
.store_action = SG_STOREACTION_DONTCARE,
1224
.clear_value = 1.0f,
1229
},
1230
};
1231
sg_pass pass = {
@@ -1319,8 +1323,13 @@ static Value native_draw_render_target(SigilVM *vm, int argc, Value *args)
1323
sgp_set_view(0, rt->tex_view);
1324
sgp_set_sampler(0, rt->sampler);
1325
+1326
/* Flip V on sample: sokol_gfx framebuffers write to texture memory in
+1327
* GL bottom-up convention even on top-down backends, so a render
+1328
* target sampled with the default top-down UV looks vertically
+1329
* mirrored on the swap chain. Sourcing from y=height with h=-height
+1330
* inverts the texcoord range so the composite is upright. */
1331
sgp_rect dest = {x, y, w, h};
1323
sgp_rect src = {0, 0, (float)rt->width, (float)rt->height};
+1332
sgp_rect src = {0, (float)rt->height, (float)rt->width, -(float)rt->height};
1333
sgp_draw_textured_rect(0, dest, src);
1334
1335
sgp_reset_view(0);