From db7c21e5d805a9300383c5dd718952685615fb46 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Tue, 29 Sep 2026 14:45:02 +0300 Subject: [PATCH] render3d/overlay: the font atlas kept as its coverage, one byte a texel The atlas is white glyphs on alpha and the overlay reads only the coverage, but it was uploaded RGBA8: Maroon Lake's 3072 x 2688 atlas was 43 MB of Metal memory with its mips, the largest single thing in the launcher window. overlay_font now keeps the alpha alone as R8 (ov_font_tex; any other PNG shape is uploaded as before) and overlay.frag reads .r - the white fallback texture reads 1 there as it did from .a. Launcher window, graphics regions: the atlas 43.0 -> 11.5 MB; process footprint at 25 s 402 -> 378 MB. The headless launcher shot is identical to the unchanged build's (0 pixels differ; two unchanged runs differ from each other by 1.69%, and the new one by the same against them). Co-Authored-By: Claude Opus 5.5 --- packages/ludic.render3d/overlay.ludic | 25 +++++++++++++++++- packages/ludic.render3d/shaders/overlay.frag | 4 +-- .../shaders/spv/1b654972.frag.spv | Bin 4852 -> 4852 bytes .../shaders/spv/e86f78e8.frag.spv | Bin 6824 -> 6824 bytes 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/ludic.render3d/overlay.ludic b/packages/ludic.render3d/overlay.ludic index f9c4ec1d..31e35b65 100644 --- a/packages/ludic.render3d/overlay.ludic +++ b/packages/ludic.render3d/overlay.ludic @@ -67,6 +67,29 @@ function overlay_init(render3d_st: mut Render3dState, font_dir: string) -> bool return true } +# The atlas is white glyphs on alpha, and the overlay reads only the coverage: it is kept as one +# byte a texel (R8). As RGBA a 3072 x 2688 atlas was 44 MB with its mips, the most of any UI texture. +@alloc_ok("loading a font: a load, not a frame") +function ov_font_tex(render3d_st: mut Render3dState, path: string) -> int { + let px = png_decode(render3d_st, path) + if px == null { return 0 } + if render3d_st.tex_channels != 4 or render3d_st.tex_depth != 8 { + let id0 = tex_upload(render3d_st, px, false, true) + free(px) + tex_note_size(render3d_st, id0) + return id0 + } + let n = render3d_st.tex_w * render3d_st.tex_h + let a = bytes(n) + for i in 0 .. n { a[i] = px[i * 4 + 3] } + free(px) + render3d_st.tex_channels = 1 + let id = tex_upload(render3d_st, a, false, true) + free(a) + tex_note_size(render3d_st, id) + return id +} + # Load (or swap to) the font atlas in `font_dir`: font.png and font.json. A game calls it # again to change fonts at run time - a language whose script the default atlas does not # carry brings its own. Returns false, and keeps the font it had, if there is none there. @@ -74,7 +97,7 @@ function overlay_init(render3d_st: mut Render3dState, font_dir: string) -> bool function overlay_font(render3d_st: mut Render3dState, font_dir: string) -> bool { let meta = Fs.read_text(font_dir + "/font.json") if meta == null { return false } - let tex = tex_load_ex(render3d_st, font_dir + "/font.png", false, 0) + let tex = ov_font_tex(render3d_st, font_dir + "/font.png") if tex == 0 { return false } let j = Json.parse(meta) render3d_st.ov_font_cols = jint(j, "cols", 16); render3d_st.ov_font_rows = jint(j, "rows", 6) diff --git a/packages/ludic.render3d/shaders/overlay.frag b/packages/ludic.render3d/shaders/overlay.frag index 1b3d08f9..0d3a9c30 100644 --- a/packages/ludic.render3d/shaders/overlay.frag +++ b/packages/ludic.render3d/shaders/overlay.frag @@ -1,4 +1,4 @@ -// 2D overlay: an image texture, the font atlas (white glyphs on alpha) or a flat colour, times a +// 2D overlay: an image texture, the font atlas (glyph coverage, R8) or a flat colour, times a // colour. Which one is the vertex's own: v carries 4 x the mode (0 image, 1 font, 2 flat) on top of // its real coordinate, so text, panels and an image's quads share a draw (overlay.ludic, ov_use_tex). in vec2 v_uv; @@ -27,7 +27,7 @@ void main() { if (mode > 1.5) { o_color = v_col; } else if (mode > 0.5) { - o_color = vec4(v_col.rgb, v_col.a * texture(u_font, uv).a); + o_color = vec4(v_col.rgb, v_col.a * texture(u_font, uv).r); // the atlas is its coverage alone (R8) } else { vec4 t = texture(u_tex, uv); o_color = vec4(v_col.rgb * t.rgb, v_col.a * t.a); diff --git a/packages/ludic.render3d/shaders/spv/1b654972.frag.spv b/packages/ludic.render3d/shaders/spv/1b654972.frag.spv index 2c38afa027d7b9198c5933d7dcd90f28b34a3dab..46c181f1a522968bf0767065d19ea2099e02881a 100644 GIT binary patch delta 14 VcmeyO`bBla839Iy&1VJFnE)~r1$h7f delta 14 VcmeyO`bBla839J-&1VJFnE)~-1$+Pi diff --git a/packages/ludic.render3d/shaders/spv/e86f78e8.frag.spv b/packages/ludic.render3d/shaders/spv/e86f78e8.frag.spv index eedac340ad4347728419fd85eda0a9e9447deda9..5cc0f51d4cc66e4a7b9fac179d301cfddc64ca5e 100644 GIT binary patch delta 14 VcmZ2sy25lrga{+U=138LHUK7g1Wf<{ delta 14 VcmZ2sy25lrga{+^=138LHUK7y1W*6~