iOS lock step keyboard + metal

This commit is contained in:
agra
2026-05-18 17:40:10 +03:00
parent b43472e6ab
commit f9ecf9d00e
68 changed files with 4794 additions and 203 deletions

View File

@@ -3,6 +3,7 @@
#import "modules/gpu/types.sx";
#import "modules/gpu/api.sx";
#import "modules/stb_truetype.sx";
#import "modules/stb.sx";
#import "modules/ui/types.sx";
// Cached glyph data with UV coordinates into the atlas texture
@@ -426,17 +427,26 @@ GlyphCache :: struct {
context.allocator.dealloc(old_vals);
}
// Upload dirty atlas to GPU
// Upload dirty atlas to GPU. On the Metal path, defer the upload to
// end-of-frame (`upload_atlas_to_gpu`) — calling `replaceRegion:` against
// the same R8 MTLTexture multiple times within one frame garbles the
// contents on iOS-sim Metal. The dirty flag carries over so the final
// end-of-frame upload picks up every rasterization that happened during
// the frame's render pass.
flush :: (self: *GlyphCache) {
if self.dirty == false { return; }
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);
}
if self.has_gpu { 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);
self.dirty = false;
}
upload_atlas_to_gpu :: (self: *GlyphCache) {
if self.has_gpu == false { return; }
if self.dirty == false { return; }
self.gpu.update_texture_region(self.texture_id, 0, 0,
self.atlas_width, self.atlas_height, xx self.bitmap);
self.dirty = false;
}

View File

@@ -175,6 +175,11 @@ UIPipeline :: struct {
self.renderer.begin(self.screen_width, self.screen_height, self.font.texture_id);
self.renderer.process(@self.render_tree);
// Push any glyphs rasterized during process() to the GPU atlas BEFORE
// the final draw is recorded. On Metal we deferred per-render_text
// uploads so this is the single point where the atlas reaches the
// GPU. On the GL path it's a no-op (uploads already happened inline).
self.font.upload_atlas_to_gpu();
self.renderer.flush();
if !self.has_gpu {

View File

@@ -375,6 +375,7 @@ UIRenderer :: struct {
u1 := cached.uv_x + cached.uv_w;
v1 := cached.uv_y + cached.uv_h;
if self.vertex_count + 6 > MAX_UI_VERTICES {
self.flush();
}
@@ -619,12 +620,7 @@ fragment float4 fmain(VOut in [[stage_in]],
// Image mode (mode == -2.0): sample texture
return tex.sample(s, in.uv) * in.color;
} else if (mode < 0.0) {
// Text mode (mode == -1.0): the glyph atlas stores R8 alpha
// coverage from stbtt_MakeGlyphBitmap. Use the sampled value
// directly as alpha (no smoothstep — those were for SDFs and
// thinned anti-aliased coverage strokes). Small-size text renders
// dim on dark backgrounds because most glyph pixels sit in 0.1-0.5
// coverage; tracked as the "faint text" follow-up.
// Text mode (mode == -1.0): the glyph atlas stores R8 alpha coverage.
float alpha = tex.sample(s, in.uv).r;
return float4(in.color.rgb, in.color.a * alpha);
} else if (mode > 0.0 || border > 0.0) {