metal: pause step 3b pending sx-side fixes (filed 0024-0030)
Step 3b code is wired across UIRenderer + GlyphCache + UIPipeline +
chess game (gpu_mode = .metal on iOS, MetalGPU bound via the GPU
protocol). macOS GL chess, iOS-sim GLES chess, and iOS-sim Metal
triangle (63-metal-clear.sx) all still render.
iOS-sim Metal chess crashes inside replaceRegion uploading the 1MB
font atlas. Bisecting that crash exposed several sx-language issues
where mid-bisect tracers (NSLog inside if/else branch bodies) didn't
produce output, blocking further investigation.
Filing each finding as examples/issue-NNNN.sx rather than working
around piecemeal:
Bugs:
- 0024 NSLog/foreign-call inside if/else body not producing output
- 0025 C-ABI param coercion incomplete for composites >16B
(combined direct-call abiCoerceParamType TODO + call_indirect
path that doesn't apply C-ABI coercion at all)
- 0026 replaceRegion 1MB upload crash (likely downstream of 0025)
Features needed for step 4 + cleanup:
- 0027 Obj-C block bridge (^{...}) for animateWithDuration:
- 0028 Optional protocol box (?GPU = null) replaces T = ---; has_T: bool
- 0029 destroy_texture/buffer/shader on GPU protocol
- 0030 extern cross-file globals
Library-side: renderer.sx + glyph_cache.sx + pipeline.sx gain a
`gpu: GPU = ---; has_gpu: bool` field pair + branches that route every
GL touchpoint through the protocol when has_gpu. glyph_cache.init
saves/restores those fields around its memset. pipeline.set_gpu()
propagates to renderer + font. Renderer's MSL shader source added as
UI_MSL_SRC using packed_float2/packed_float4 to keep the 12-float
interleaved vertex layout tight (48 bytes).
metal.sx: dual-phase init (init(null, 0, 0) for eager device+queue,
re-init with the layer once UIKit installs the SxMetalView).
setStorageMode:.shared on every texture descriptor to ensure CPU-
writable atlas pixels on Apple Silicon iOS-sim.
Regression suite: 68 passing, 0 failed. WASM chess build currently
broken under step 3b state (silent compiler crash); documented in
CHECKPOINT.md, likely fallout from one of the filed issues (probably
0028 — the verbose protocol-box pattern). Step 3b resumes after
0024-0030 land.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
#import "modules/std.sx";
|
||||
#import "modules/opengl.sx";
|
||||
#import "modules/gpu/types.sx";
|
||||
#import "modules/gpu/api.sx";
|
||||
#import "modules/stb_truetype.sx";
|
||||
#import "modules/ui/types.sx";
|
||||
|
||||
@@ -176,9 +178,20 @@ GlyphCache :: struct {
|
||||
last_shape_len: s64;
|
||||
last_shape_size_q: u16;
|
||||
|
||||
// GPU protocol backend. When `has_gpu`, atlas creation + dirty uploads
|
||||
// route through `gpu` instead of raw GL.
|
||||
gpu: GPU = ---;
|
||||
has_gpu: bool = false;
|
||||
|
||||
init :: (self: *GlyphCache, path: [:0]u8, default_size: f32) {
|
||||
// Preserve any pre-set GPU dispatch across the zero-out — the
|
||||
// surrounding struct memset would otherwise wipe it.
|
||||
saved_gpu := self.gpu;
|
||||
saved_has_gpu := self.has_gpu;
|
||||
// Zero out the entire struct first (parent may be uninitialized with = ---)
|
||||
memset(self, 0, size_of(GlyphCache));
|
||||
self.gpu = saved_gpu;
|
||||
self.has_gpu = saved_has_gpu;
|
||||
|
||||
// Load font file
|
||||
file_size : s32 = 0;
|
||||
@@ -245,15 +258,25 @@ GlyphCache :: struct {
|
||||
val_bytes : s64 = self.hash_cap * 8; // s64 per slot (s32 would suffice but alignment)
|
||||
self.hash_vals = xx context.allocator.alloc(val_bytes);
|
||||
|
||||
// Create OpenGL texture
|
||||
glGenTextures(1, @self.texture_id);
|
||||
glBindTexture(GL_TEXTURE_2D, self.texture_id);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, xx GL_R8, self.atlas_width, self.atlas_height, 0, GL_RED, GL_UNSIGNED_BYTE, self.bitmap);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, xx GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, xx GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, xx GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, xx GL_CLAMP_TO_EDGE);
|
||||
// Create the atlas texture. In GPU-protocol mode we create empty and
|
||||
// let the first `flush()` push the (zero-initialized) bitmap via
|
||||
// update_texture_region — same result as the GL path's glTexImage2D
|
||||
// with the zeroed bitmap, but works whether or not the backend
|
||||
// accepts CPU pixel pointers at create time.
|
||||
if self.has_gpu {
|
||||
self.texture_id = self.gpu.create_texture(
|
||||
self.atlas_width, self.atlas_height, .r8, null);
|
||||
self.dirty = true;
|
||||
} else {
|
||||
glGenTextures(1, @self.texture_id);
|
||||
glBindTexture(GL_TEXTURE_2D, self.texture_id);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, xx GL_R8, self.atlas_width, self.atlas_height, 0, GL_RED, GL_UNSIGNED_BYTE, self.bitmap);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, xx GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, xx GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, xx GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, xx GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
out("GlyphCache initialized: ");
|
||||
out(path);
|
||||
@@ -406,9 +429,14 @@ GlyphCache :: struct {
|
||||
// Upload dirty atlas to GPU
|
||||
flush :: (self: *GlyphCache) {
|
||||
if self.dirty == false { return; }
|
||||
glBindTexture(GL_TEXTURE_2D, self.texture_id);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, self.atlas_width, self.atlas_height, GL_RED, GL_UNSIGNED_BYTE, self.bitmap);
|
||||
if self.has_gpu {
|
||||
self.gpu.update_texture_region(self.texture_id, 0, 0,
|
||||
self.atlas_width, self.atlas_height, xx self.bitmap);
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, self.texture_id);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, self.atlas_width, self.atlas_height, GL_RED, GL_UNSIGNED_BYTE, self.bitmap);
|
||||
}
|
||||
self.dirty = false;
|
||||
}
|
||||
|
||||
@@ -464,16 +492,23 @@ GlyphCache :: struct {
|
||||
self.atlas_width = new_w;
|
||||
self.atlas_height = new_h;
|
||||
|
||||
// Recreate GL texture
|
||||
glDeleteTextures(1, @self.texture_id);
|
||||
glGenTextures(1, @self.texture_id);
|
||||
glBindTexture(GL_TEXTURE_2D, self.texture_id);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, xx GL_R8, new_w, new_h, 0, GL_RED, GL_UNSIGNED_BYTE, new_bitmap);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, xx GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, xx GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, xx GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, xx GL_CLAMP_TO_EDGE);
|
||||
// Recreate atlas at the new size.
|
||||
if self.has_gpu {
|
||||
// No destroy_texture in the GPU protocol yet — old atlas
|
||||
// leaks in the backend table until process exit. Atlas grow
|
||||
// is rare so this is acceptable for now.
|
||||
self.texture_id = self.gpu.create_texture(new_w, new_h, .r8, xx new_bitmap);
|
||||
} else {
|
||||
glDeleteTextures(1, @self.texture_id);
|
||||
glGenTextures(1, @self.texture_id);
|
||||
glBindTexture(GL_TEXTURE_2D, self.texture_id);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, xx GL_R8, new_w, new_h, 0, GL_RED, GL_UNSIGNED_BYTE, new_bitmap);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, xx GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, xx GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, xx GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, xx GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
// Recompute UV coordinates for all cached glyphs
|
||||
atlas_wf : f32 = xx new_w;
|
||||
|
||||
Reference in New Issue
Block a user