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.
This commit is contained in:
Andros Fenollosa 2026-06-03 17:01:05 +02:00
parent 6fbb07a9c1
commit 22df816d22

View file

@ -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;