Skip to content

Commit 6ea7c51

Browse files
shai-almogclaude
andcommitted
iOS Metal port: half-pixel offset for DrawLine and DrawRect line endpoints
GPU line rasterisation snaps each line to the pixel grid. A horizontal line at integer y (e.g. y=0) straddles the boundary between row -1 and row 0; hardware antialiasing splits the coverage between the two rows at half intensity each, so the line ends up looking 2 px wide and washed out instead of crisp 1 px. The standard fix is to pass endpoints through the pixel centre (y = N + 0.5) so the line sits inside a single row. The GL ES2 path already does this -- DrawLine.m:122 emits `x1+0.5, y1+0.5, x2+0.5, y2+0.5`, and DrawRect.m:122 mirrors the same +0.5 on every vertex of the closed line strip. Mirror it in Metal's CN1MetalDrawLine and CN1MetalDrawRect. Visible victim: the graphics-draw-rect test draws hundreds of concentric drawRect outlines with cycling colours. On GL each outline is a distinct 1 px coloured stripe and the whole stack reads as a moiré of stripes. On Metal pre-fix the lines smeared between adjacent rows and the inner stripes blurred into a solid filled block. graphics-draw-line had the analogous symptom on every line in the test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 774eeb2 commit 6ea7c51

1 file changed

Lines changed: 21 additions & 7 deletions

File tree

Ports/iOSPort/nativeSources/CN1Metalcompat.m

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -282,21 +282,35 @@ void CN1MetalFillRect(int color, int alpha, int x, int y, int width, int height)
282282
drawQuad(CN1MetalPipelineSolidColor, vertices, NULL, colorV, nil);
283283
}
284284

285+
// GPU line rasterisation snaps each line to the pixel grid: a horizontal
286+
// line at integer y straddles the boundary between row y-1 and row y, so
287+
// hardware antialiasing splits the coverage between two rows at half
288+
// intensity each -- the line ends up looking 2 px wide and washed out.
289+
// The standard fix is to offset the line's endpoints by half a pixel so
290+
// the line passes through the pixel-centre of a single row. The GL ES2
291+
// DrawLine / DrawRect ops already do this (DrawLine.m:122, DrawRect.m:122).
292+
// Without this, drawRect's many concentric 1-px outlines (the
293+
// graphics-draw-rect test) blurred together into a solid block instead
294+
// of the visible per-iteration stripes the GL render shows.
285295
void CN1MetalDrawLine(int color, int alpha, int x1, int y1, int x2, int y2) {
286296
simd_float4 colorV = premultipliedColor(color, alpha);
287-
float vertices[4] = { (float)x1, (float)y1, (float)x2, (float)y2 };
297+
float vertices[4] = {
298+
(float)x1 + 0.5f, (float)y1 + 0.5f,
299+
(float)x2 + 0.5f, (float)y2 + 0.5f
300+
};
288301
drawSolidPrimitive(MTLPrimitiveTypeLine, vertices, 2, colorV);
289302
}
290303

291304
void CN1MetalDrawRect(int color, int alpha, int x, int y, int width, int height) {
292305
simd_float4 colorV = premultipliedColor(color, alpha);
293-
// Closed rectangle outline as a 5-vertex line strip.
306+
// Closed rectangle outline as a 5-vertex line strip. +0.5 on every
307+
// vertex for the same pixel-centre reason as CN1MetalDrawLine.
294308
float vertices[10] = {
295-
(float)x, (float)y,
296-
(float)(x+width), (float)y,
297-
(float)(x+width), (float)(y+height),
298-
(float)x, (float)(y+height),
299-
(float)x, (float)y
309+
(float)x + 0.5f, (float)y + 0.5f,
310+
(float)(x+width) + 0.5f, (float)y + 0.5f,
311+
(float)(x+width) + 0.5f, (float)(y+height) + 0.5f,
312+
(float)x + 0.5f, (float)(y+height) + 0.5f,
313+
(float)x + 0.5f, (float)y + 0.5f
300314
};
301315
drawSolidPrimitive(MTLPrimitiveTypeLineStrip, vertices, 5, colorV);
302316
}

0 commit comments

Comments
 (0)