From 89064a70fa5c441d6a2505b148d794df24377626 Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Fri, 31 Jul 2026 02:10:23 -0400 Subject: [PATCH 1/3] intrinsics fix --- src/pixie/simd/avx2.nim | 14 +++++++++++++- src/pixie/simd/neon.nim | 17 +++++++++++++---- src/pixie/simd/sse2.nim | 18 ++++++++++++++++-- tests/test_paths.nim | 30 ++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 7 deletions(-) diff --git a/src/pixie/simd/avx2.nim b/src/pixie/simd/avx2.nim index 1800e583..0a959c2e 100644 --- a/src/pixie/simd/avx2.nim +++ b/src/pixie/simd/avx2.nim @@ -477,7 +477,19 @@ proc blendLineCoverageOverwriteAvx2*( mm_srli_si128(coverageHi, 8), ] for j in 0 ..< 4: - mm256_store_si256(line[i].addr, rgbxVec.applyCoverage(coverages[j])) + let + preserve = mm256_cmpeq_epi32( + mm256_cvtepu8_epi32(coverages[j]), vecZero + ) + source = rgbxVec.applyCoverage(coverages[j]) + backdrop = mm256_load_si256(line[i].addr) + mm256_store_si256( + line[i].addr, + mm256_or_si256( + mm256_and_si256(preserve, backdrop), + mm256_andnot_si256(preserve, source) + ) + ) i += 8 for i in i ..< len: diff --git a/src/pixie/simd/neon.nim b/src/pixie/simd/neon.nim index 8455ed0a..fe5def30 100644 --- a/src/pixie/simd/neon.nim +++ b/src/pixie/simd/neon.nim @@ -462,11 +462,20 @@ proc blendLineCoverageOverwriteNeon*( elif mask255 == uint64.high: vst4q_u8(line[i].addr, vecRgbx) else: + let backdrop = vld4q_u8(line[i].addr) var source: uint8x16x4 - source.val[0] = multiplyDiv255(vecRgbx.val[0], coverage) - source.val[1] = multiplyDiv255(vecRgbx.val[1], coverage) - source.val[2] = multiplyDiv255(vecRgbx.val[2], coverage) - source.val[3] = multiplyDiv255(vecRgbx.val[3], coverage) + source.val[0] = vbslq_u8( + eqZero, backdrop.val[0], multiplyDiv255(vecRgbx.val[0], coverage) + ) + source.val[1] = vbslq_u8( + eqZero, backdrop.val[1], multiplyDiv255(vecRgbx.val[1], coverage) + ) + source.val[2] = vbslq_u8( + eqZero, backdrop.val[2], multiplyDiv255(vecRgbx.val[2], coverage) + ) + source.val[3] = vbslq_u8( + eqZero, backdrop.val[3], multiplyDiv255(vecRgbx.val[3], coverage) + ) vst4q_u8(line[i].addr, source) i += 16 diff --git a/src/pixie/simd/sse2.nim b/src/pixie/simd/sse2.nim index 35c7333b..111cc827 100644 --- a/src/pixie/simd/sse2.nim +++ b/src/pixie/simd/sse2.nim @@ -554,10 +554,24 @@ proc blendLineCoverageOverwriteSse2*( mm_store_si128(line[i].addr, rgbxVec) i += 4 else: - var coverage = coverage + var + coverage = coverage + eqZero = eqZero for _ in 0 ..< 4: - mm_store_si128(line[i].addr, rgbxVec.applyCoverage(coverage)) + var preserve = mm_unpacklo_epi8(eqZero, eqZero) + preserve = mm_unpacklo_epi16(preserve, preserve) + let + source = rgbxVec.applyCoverage(coverage) + backdrop = mm_load_si128(line[i].addr) + mm_store_si128( + line[i].addr, + mm_or_si128( + mm_and_si128(preserve, backdrop), + mm_andnot_si128(preserve, source) + ) + ) coverage = mm_srli_si128(coverage, 4) + eqZero = mm_srli_si128(eqZero, 4) i += 4 for i in i ..< len: diff --git a/tests/test_paths.nim b/tests/test_paths.nim index 29a6f59d..7b92ab95 100644 --- a/tests/test_paths.nim +++ b/tests/test_paths.nim @@ -760,3 +760,33 @@ block: """ let path = parsePath(pathStr) doAssert path.computeBounds() == rect(0, 0, 0, 100) + +block: # OverwriteBlend writes only where the path has coverage + const backdrop = rgbx(0, 0, 255, 255) + + let + paint = newPaint(SolidPaint) + path = newPath() + paint.color = color(1, 0, 0, 1) + path.circle(circle(vec2(64, 32), 40)) + + # Drawn onto a transparent image this is rgbx * coverage, so it is both the + # coverage map and the expected result where covered. + let expected = newImage(128, 64) + expected.fillPath(path, paint) + + let image = newImage(128, 64) + image.fill(backdrop) + paint.blendMode = OverwriteBlend + image.fillPath(path, paint) + + var covered, uncovered: int + for y in 0 ..< image.height: + for x in 0 ..< image.width: + if expected[x, y].a == 0: + doAssert image[x, y] == backdrop, &"{x},{y} was overwritten" + inc uncovered + else: + doAssert image[x, y] == expected[x, y], &"{x},{y} is wrong" + inc covered + doAssert covered > 0 and uncovered > 0 From ad3bffd751abc4ca3b6a19eb3407e6f604c523e1 Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Fri, 31 Jul 2026 02:33:33 -0400 Subject: [PATCH 2/3] scalar fix --- src/pixie/paths.nim | 7 +++++-- tests/test_paths.nim | 31 ++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index b0aa3d02..b2191f3f 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -1601,6 +1601,7 @@ proc fillShapes( # rasterize only within the total bounds let rgbx = color.asRgbx() + clearsUncovered = blendMode == MaskBlend segments = shapes.shapesToSegments() bounds = computeBounds(segments).snapToPixels() startX = max(0, bounds.x.int) @@ -1806,7 +1807,8 @@ proc fillShapes( applyOpacity(vecRgbx, area) else: rgbx * area - image.data[dataIndex] = blender(backdrop, source) + if source.a != 0 or clearsUncovered: + image.data[dataIndex] = blender(backdrop, source) block: # Right-side partial coverage let @@ -1844,7 +1846,8 @@ proc fillShapes( applyOpacity(vecRgbx, area) else: rgbx * area - image.data[dataIndex] = blender(backdrop, source) + if source.a != 0 or clearsUncovered: + image.data[dataIndex] = blender(backdrop, source) let fillBegin = leftCoverEnd.clamp(0, image.width) diff --git a/tests/test_paths.nim b/tests/test_paths.nim index 7b92ab95..f531519a 100644 --- a/tests/test_paths.nim +++ b/tests/test_paths.nim @@ -1,4 +1,4 @@ -import chroma, pixie, pixie/fileformats/png, strformat, xrays +import chroma, pixie, std/math, pixie/fileformats/png, strformat, xrays block: let pathStr = """ @@ -790,3 +790,32 @@ block: # OverwriteBlend writes only where the path has coverage doAssert image[x, y] == expected[x, y], &"{x},{y} is wrong" inc covered doAssert covered > 0 and uncovered > 0 + +block: # a sliver whose coverage rounds away must not erase the backdrop + const backdrop = rgbx(0, 0, 255, 255) + + # Spikes thin enough that coverage rounds to nothing near their tips. + let path = newPath() + for i in 0 ..< 80: + let + a = i.float32 * PI.float32 / 40 + r = if (i and 1) == 0: 42.0 else: 92.0 + pt = vec2(130 + cos(a) * r, 100 + sin(a) * r) + if i == 0: path.moveTo(pt) else: path.lineTo(pt) + path.closePath() + + let + paint = newPaint(SolidPaint) + expected = newImage(260, 200) + paint.color = color(1, 0.85, 0.1, 1) + expected.fillPath(path, paint) + + let image = newImage(260, 200) + image.fill(backdrop) + paint.blendMode = OverwriteBlend + image.fillPath(path, paint) + + for y in 0 ..< image.height: + for x in 0 ..< image.width: + if expected[x, y].a == 0: + doAssert image[x, y] == backdrop, &"{x},{y} was overwritten" From 5ee01ed0a0b8d2724dbb0b1c1d35220da788e4db Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Fri, 31 Jul 2026 02:54:37 -0400 Subject: [PATCH 3/3] test convention --- tests/paths/overwriteCircle.png | Bin 0 -> 944 bytes tests/paths/overwriteStar.png | Bin 0 -> 19797 bytes tests/test_paths.nim | 62 +++++++++++--------------------- 3 files changed, 20 insertions(+), 42 deletions(-) create mode 100644 tests/paths/overwriteCircle.png create mode 100644 tests/paths/overwriteStar.png diff --git a/tests/paths/overwriteCircle.png b/tests/paths/overwriteCircle.png new file mode 100644 index 0000000000000000000000000000000000000000..803b1deac6423cbbeb39da9f17ed0fe5aaac09b1 GIT binary patch literal 944 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 0 and uncovered > 0 - -block: # a sliver whose coverage rounds away must not erase the backdrop - const backdrop = rgbx(0, 0, 255, 255) - +block: # Spikes thin enough that coverage rounds to nothing near their tips. - let path = newPath() + let + image = newImage(260, 200) + paint = newPaint(SolidPaint) + path = newPath() + image.fill(rgbx(0, 0, 255, 255)) + paint.color = color(1, 0.85, 0.1, 1) + paint.blendMode = OverwriteBlend for i in 0 ..< 80: let a = i.float32 * PI.float32 / 40 @@ -803,19 +793,7 @@ block: # a sliver whose coverage rounds away must not erase the backdrop pt = vec2(130 + cos(a) * r, 100 + sin(a) * r) if i == 0: path.moveTo(pt) else: path.lineTo(pt) path.closePath() - - let - paint = newPaint(SolidPaint) - expected = newImage(260, 200) - paint.color = color(1, 0.85, 0.1, 1) - expected.fillPath(path, paint) - - let image = newImage(260, 200) - image.fill(backdrop) - paint.blendMode = OverwriteBlend image.fillPath(path, paint) - - for y in 0 ..< image.height: - for x in 0 ..< image.width: - if expected[x, y].a == 0: - doAssert image[x, y] == backdrop, &"{x},{y} was overwritten" + for c in image.data: + doAssert c.a != 0 + image.xray("tests/paths/overwriteStar.png")