From 22df816d22d824297f80eb9c298fade7043e8bb6 Mon Sep 17 00:00:00 2001 From: Andros Fenollosa Date: Wed, 3 Jun 2026 17:01:05 +0200 Subject: [PATCH] Metal: fix one-pixel-low glyph baseline (off-by-one in bearing_y) bearing_y used bh - 1 - raster_oy, which placed every glyph one pixel lower than the NS backend across the whole frame. The bitmap row at top-down index r covers CG y in [bh-1-r, bh-r), so the baseline at CG y = raster_oy sits bh - raster_oy rows below the top edge, without the extra -1. Found by cross-correlating wrapped-text captures: shifting Metal text up one pixel dropped the per-row mean error from ~40 to ~3 gray levels. This was the dominant share of the residual diff attributed to antialiasing: AE vs NS drops from 0.88% to 0.33% on the sparse baseline and from 3.32% to 0.56% on dense text. --- src/mtlterm.m | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mtlterm.m b/src/mtlterm.m index 6f5d7e6cfd9..ea5ca6fdf30 100644 --- a/src/mtlterm.m +++ b/src/mtlterm.m @@ -675,8 +675,12 @@ mtl_rasterize_glyph_id (CTFontRef font, CGGlyph cgGlyph, uint64_t key) bh-tall cell (Core Graphics draws y-up). All draw sites expect bearing_y to be the distance from the cell's TOP edge down to the baseline, so that y0 = baseline - bearing_y lands the cell top correctly. That distance is - (bh - 1 - raster_oy). The previous code stored raster_oy itself (the - descent), which dropped every glyph ~one ascent too low. */ + (bh - raster_oy): the row at top-down index r covers CG y in + [bh-1-r, bh-r), so the baseline CG y = raster_oy lies bh - raster_oy rows + below the top edge. The previous (bh - 1 - raster_oy) left every glyph one + pixel LOWER than the NS backend across the whole frame (verified by + cross-correlation: shifting Metal text up 1px dropped the per-row mean + error from ~40 to ~3 gray levels). */ int raster_oy = (int)(floor (-bbox.origin.y) + 1); entry->atlas_x = g_atlas_next_x; @@ -684,7 +688,7 @@ mtl_rasterize_glyph_id (CTFontRef font, CGGlyph cgGlyph, uint64_t key) entry->width = bw; entry->height = bh; entry->bearing_x = (int)(floor (-bbox.origin.x) + 1); - entry->bearing_y = bh - 1 - raster_oy; + entry->bearing_y = bh - raster_oy; entry->advance_x = (float)(adv.width / s); g_atlas_next_x += bw + 1;