commit ebe39ce25dd14075927210472e485c6e87085b5d
parent 53edbab522984f44ef43d42d0656d2e0ab99980b
Author: Nicolas Silva <nical@fastmail.com>
Date: Wed, 1 Oct 2025 15:26:42 +0000
Bug 1978773 - Fix partial chunk steps in the new radial gradient swgl span shader. r=lsalzman
The code was incorrectly assuming that one could simply update dotPos and dotPosDelta for partial chunks by dividing by the chunk size and multiplying by the number of pixels in the partial chunk. That's true for dotPosDelta but not for dotPos where there is a compounding effect with each iteration feeding back into the next in a way that makes things non-linear. There has to be a more elegant solution but after many failed attempts I ended up expressing it in terms of a single-pixel step applied multiple times depending on the number of pixels in the partial chunk. It should be pretty fast still and this way the math looks similar to the whole-chunk steps.
In addition, the patch rounds endT up so that we go all the way to the next pixel that is in a different stop pair rather than stopping one pixel before. This removes a lot of unnecessary partial chunks. Before that, we would round endT down which meant that in the next sub-span we would still be in the same gradient stop pair and advance by a pixel before stepping into the next stop pair.
Differential Revision: https://phabricator.services.mozilla.com/D266691
Diffstat:
1 file changed, 34 insertions(+), 6 deletions(-)
diff --git a/gfx/wr/swgl/src/swgl_ext.h b/gfx/wr/swgl/src/swgl_ext.h
@@ -2048,11 +2048,11 @@ static bool commitRadialGradientFromStops(sampler2D sampler, int offsetsAddress,
endT = min(endT, middleT);
}
}
-
// Ensure that we are advancing by at least one pixel at each iteration.
- endT = max(endT, t + 1.0);
+ endT = max(ceil(endT), t + 1.0);
- // Figure out how many chunks are actually inside the gradient stop pair.
+ // Figure out how many pixels belonging to whole chunks are inside the gradient
+ // stop pair.
int inside = int(endT - t) & ~3;
// Convert start and end colors to BGRA and scale to 0..255 range.
auto minColorF = stopColors[stopIndex].zyxw * 255.0f;
@@ -2114,9 +2114,37 @@ static bool commitRadialGradientFromStops(sampler2D sampler, int offsetsAddress,
buf += remainder;
t += remainder;
- float f = float(remainder) * 0.25;
- dotPos += dotPosDelta * f;
- dotPosDelta += deltaDelta2 * f;
+ // dotPosDelta's members are monotonically increasing, so adjusting the step only
+ // requires undoing the factor of 4 and multiplying with the actual number of
+ // remainder pixels.
+ float partialDeltaDelta2 = deltaDelta2 * 0.25 * float(remainder);
+ dotPosDelta += partialDeltaDelta2;
+
+ // For dotPos, however, there is a compounding effect that makes the math trickier.
+ // For simplicity's sake we are just computing the the parameters for a single-pixel
+ // step and applying it remainder times.
+
+ // The deltaDelta2 for a single-pixel step (undoing the 4*4 factor we did earlier
+ // when making deltaDelta2 work for 4-pixels chunks).
+ float singlePxDeltaDelta2 = deltaDelta2 * 0.0625;
+ // The first single-pixel delta for dotPos (The difference between dotPos's first
+ // two lanes).
+ float dotPosDeltaFirst = dotPos.y - dotPos.x;
+ // For each 1-pixel step the delta is applied and monotonically increased by
+ // singleDeltaDelta2.
+ // TODO: This should be be Float pxOffsets(0.0f, 1.0f, 2.0f, 3.0f); but it does
+ // not compile in some configurations for some reason.
+ Float pxOffsets = Float(0.0f);
+ pxOffsets.y = 1.0;
+ pxOffsets.z = 2.0;
+ pxOffsets.w = 3.0;
+ Float partialDotPosDelta = Float(dotPosDeltaFirst) + Float(singlePxDeltaDelta2) * pxOffsets;
+
+ // Apply each single-pixel step.
+ for (int i = 0; i < remainder; ++i) {
+ dotPos += partialDotPosDelta;
+ partialDotPosDelta += singlePxDeltaDelta2;
+ }
}
}
return true;