[gpu] Fix coverage sign of the split-ray optimization (#6133)
_hb_gpu_slug_single casts its horizontal ray leftward or rightward
depending on which side of the band's split point the fragment is on,
and likewise for the vertical ray. The crossing signs it accumulates
are the same either way.
But over a closed contour the signed crossings of a line sum to zero,
so counting the crossings on the left gives exactly the negative of
counting them on the right (clamp(0.5 + u) + clamp(0.5 - u) == 1, so
this holds for the antialiased partial coverages too). Whenever
hLeftRay and vLeftRay disagree, xcov and ycov therefore came out with
opposite signs, and _hb_gpu_calc_coverage's
abs (xcov * xwgt + ycov * ywgt) / (xwgt + ywgt)
subtracted the two estimates instead of averaging them.
Glyph interiors were unaffected -- the min (abs (xcov), abs (ycov))
term floors the result there -- so this never showed as a hole. It
showed as edges that antialias too light wherever both weights are
nonzero and the two estimates differ.
Flip the sign back after each loop. With a CPU port of
_hb_gpu_slug_single, comparing against plain Slug (rightward rays
over the full curve list, which is what the sign convention in
_hb_gpu_calc_coverage assumes), util/gpu/default-font.ttf at scale
163 rendered at 107px had 1700 pixels differing by more than 0.05
before this change, always too dark, and none differing by more than
0.001 after -- the split optimization is now exact.
Same fix in all four shading languages. The GLSL was checked with
glslc; the HLSL, MSL and WGSL edits are the same two statements and
were not machine-compiled.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
diff --git a/src/hb-gpu-fragment-glsl.hh b/src/hb-gpu-fragment-glsl.hh
index c7356fe..1db7066 100644
--- a/src/hb-gpu-fragment-glsl.hh
+++ b/src/hb-gpu-fragment-glsl.hh
@@ -244,6 +244,12 @@
" }\n"
" }\n"
"\n"
+" /* Crossings over a closed contour sum to zero, so a leftward ray\n"
+" * returns the negative of what a rightward ray would; flip it back\n"
+" * so that xcov and ycov keep a common sign convention. */\n"
+" if (hLeftRay)\n"
+" xcov = -xcov;\n"
+"\n"
" float ycov = 0.0;\n"
" float ywgt = 0.0;\n"
"\n"
@@ -295,6 +301,10 @@
" }\n"
" }\n"
"\n"
+" /* Ditto, for the vertical ray. */\n"
+" if (vLeftRay)\n"
+" ycov = -ycov;\n"
+"\n"
" return _hb_gpu_calc_coverage (xcov, ycov, xwgt, ywgt);\n"
"}\n"
"\n"
diff --git a/src/hb-gpu-fragment-hlsl.hh b/src/hb-gpu-fragment-hlsl.hh
index bfe2c93..3ad54d1 100644
--- a/src/hb-gpu-fragment-hlsl.hh
+++ b/src/hb-gpu-fragment-hlsl.hh
@@ -214,6 +214,12 @@
" }\n"
" }\n"
"\n"
+" /* Crossings over a closed contour sum to zero, so a leftward ray\n"
+" * returns the negative of what a rightward ray would; flip it back\n"
+" * so that xcov and ycov keep a common sign convention. */\n"
+" if (hLeftRay)\n"
+" xcov = -xcov;\n"
+"\n"
" float ycov = 0.0;\n"
" float ywgt = 0.0;\n"
"\n"
@@ -265,6 +271,10 @@
" }\n"
" }\n"
"\n"
+" /* Ditto, for the vertical ray. */\n"
+" if (vLeftRay)\n"
+" ycov = -ycov;\n"
+"\n"
" return _hb_gpu_calc_coverage (xcov, ycov, xwgt, ywgt);\n"
"}\n"
"\n"
diff --git a/src/hb-gpu-fragment-msl.hh b/src/hb-gpu-fragment-msl.hh
index e30a504..83f04b1 100644
--- a/src/hb-gpu-fragment-msl.hh
+++ b/src/hb-gpu-fragment-msl.hh
@@ -218,6 +218,12 @@
" }\n"
" }\n"
"\n"
+" /* Crossings over a closed contour sum to zero, so a leftward ray\n"
+" * returns the negative of what a rightward ray would; flip it back\n"
+" * so that xcov and ycov keep a common sign convention. */\n"
+" if (hLeftRay)\n"
+" xcov = -xcov;\n"
+"\n"
" float ycov = 0.0;\n"
" float ywgt = 0.0;\n"
"\n"
@@ -269,6 +275,10 @@
" }\n"
" }\n"
"\n"
+" /* Ditto, for the vertical ray. */\n"
+" if (vLeftRay)\n"
+" ycov = -ycov;\n"
+"\n"
" return _hb_gpu_calc_coverage (xcov, ycov, xwgt, ywgt);\n"
"}\n"
"\n"
diff --git a/src/hb-gpu-fragment-wgsl.hh b/src/hb-gpu-fragment-wgsl.hh
index e77698e..3a8fa31 100644
--- a/src/hb-gpu-fragment-wgsl.hh
+++ b/src/hb-gpu-fragment-wgsl.hh
@@ -222,6 +222,11 @@
" }\n"
" }\n"
"\n"
+" /* Crossings over a closed contour sum to zero, so a leftward ray\n"
+" * returns the negative of what a rightward ray would; flip it back\n"
+" * so that xcov and ycov keep a common sign convention. */\n"
+" if (hLeftRay) { xcov = -xcov; }\n"
+"\n"
" var ycov: f32 = 0.0;\n"
" var ywgt: f32 = 0.0;\n"
"\n"
@@ -276,6 +281,9 @@
" }\n"
" }\n"
"\n"
+" /* Ditto, for the vertical ray. */\n"
+" if (vLeftRay) { ycov = -ycov; }\n"
+"\n"
" return _hb_gpu_calc_coverage (xcov, ycov, xwgt, ywgt);\n"
"}\n"
"\n"
diff --git a/src/hb-gpu-fragment.glsl b/src/hb-gpu-fragment.glsl
index 23b14a0..734f136 100644
--- a/src/hb-gpu-fragment.glsl
+++ b/src/hb-gpu-fragment.glsl
@@ -243,6 +243,12 @@
}
}
+ /* Crossings over a closed contour sum to zero, so a leftward ray
+ * returns the negative of what a rightward ray would; flip it back
+ * so that xcov and ycov keep a common sign convention. */
+ if (hLeftRay)
+ xcov = -xcov;
+
float ycov = 0.0;
float ywgt = 0.0;
@@ -294,6 +300,10 @@
}
}
+ /* Ditto, for the vertical ray. */
+ if (vLeftRay)
+ ycov = -ycov;
+
return _hb_gpu_calc_coverage (xcov, ycov, xwgt, ywgt);
}
diff --git a/src/hb-gpu-fragment.hlsl b/src/hb-gpu-fragment.hlsl
index a0c69d6..6ab728c 100644
--- a/src/hb-gpu-fragment.hlsl
+++ b/src/hb-gpu-fragment.hlsl
@@ -213,6 +213,12 @@
}
}
+ /* Crossings over a closed contour sum to zero, so a leftward ray
+ * returns the negative of what a rightward ray would; flip it back
+ * so that xcov and ycov keep a common sign convention. */
+ if (hLeftRay)
+ xcov = -xcov;
+
float ycov = 0.0;
float ywgt = 0.0;
@@ -264,6 +270,10 @@
}
}
+ /* Ditto, for the vertical ray. */
+ if (vLeftRay)
+ ycov = -ycov;
+
return _hb_gpu_calc_coverage (xcov, ycov, xwgt, ywgt);
}
diff --git a/src/hb-gpu-fragment.msl b/src/hb-gpu-fragment.msl
index bb7da49..3f3220b 100644
--- a/src/hb-gpu-fragment.msl
+++ b/src/hb-gpu-fragment.msl
@@ -217,6 +217,12 @@
}
}
+ /* Crossings over a closed contour sum to zero, so a leftward ray
+ * returns the negative of what a rightward ray would; flip it back
+ * so that xcov and ycov keep a common sign convention. */
+ if (hLeftRay)
+ xcov = -xcov;
+
float ycov = 0.0;
float ywgt = 0.0;
@@ -268,6 +274,10 @@
}
}
+ /* Ditto, for the vertical ray. */
+ if (vLeftRay)
+ ycov = -ycov;
+
return _hb_gpu_calc_coverage (xcov, ycov, xwgt, ywgt);
}
diff --git a/src/hb-gpu-fragment.wgsl b/src/hb-gpu-fragment.wgsl
index 0fac0c3..ae9c1da 100644
--- a/src/hb-gpu-fragment.wgsl
+++ b/src/hb-gpu-fragment.wgsl
@@ -221,6 +221,11 @@
}
}
+ /* Crossings over a closed contour sum to zero, so a leftward ray
+ * returns the negative of what a rightward ray would; flip it back
+ * so that xcov and ycov keep a common sign convention. */
+ if (hLeftRay) { xcov = -xcov; }
+
var ycov: f32 = 0.0;
var ywgt: f32 = 0.0;
@@ -275,6 +280,9 @@
}
}
+ /* Ditto, for the vertical ray. */
+ if (vLeftRay) { ycov = -ycov; }
+
return _hb_gpu_calc_coverage (xcov, ycov, xwgt, ywgt);
}