sw_engine: fix radial gradient when focal point is outside circle on ARM/Apple Silicon

sw_engine: radial gradient
[issues 2014: radial gradient](#2014)

Radial gradient results in a corrupted image when the focal point is outside the circle on Apple Silicon.
This happens because some compilers use FMA to optimize the a = dr² - dx² - dy² calculation,
which cause loss of precision.

We rely on temporary variables to prevent FMA.
We could also use compiler specific float contraction control pragmas to avoid this if this doesn't work in the future
This commit is contained in:
Lorcán Mc Donagh 2024-03-07 16:13:33 +01:00 committed by Hermet Park
parent d46ddf43cd
commit 2854cbbcf9
2 changed files with 19 additions and 1 deletions

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="800" height="590" version="1.1" id="https://github.com/thorvg/thorvg/issues/2014" xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs>
<radialGradient cx="429" cy="3" fx="429" fy="-600" gradientUnits="userSpaceOnUse" id="b" r="600">
<stop offset="0" stop-color="red" id="stop10" />
<stop offset="1" stop-color="white" id="stop11" />
</radialGradient>
</defs>
<path d="M 800,200 H 0 V 590 H 800 V 0 Z" fill="url(#b)" id="svg_4" />
</svg>

After

Width:  |  Height:  |  Size: 575 B

View file

@ -201,7 +201,13 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* tr
fill->radial.fy = cy + r * (fy - cy) / dist;
fill->radial.dx = cx - fill->radial.fx;
fill->radial.dy = cy - fill->radial.fy;
fill->radial.a = fill->radial.dr * fill->radial.dr - fill->radial.dx * fill->radial.dx - fill->radial.dy * fill->radial.dy;
// Prevent loss of precision on Apple Silicon when dr=dy and dx=0 due to FMA
// https://github.com/thorvg/thorvg/issues/2014
auto dr2 = fill->radial.dr * fill->radial.dr;
auto dx2 = fill->radial.dx * fill->radial.dx;
auto dy2 = fill->radial.dy * fill->radial.dy;
fill->radial.a = dr2 - dx2 - dy2;
}
if (fill->radial.a > 0) fill->radial.invA = 1.0f / fill->radial.a;