You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
tripcolor shading="gouraud" streaks/glitch on macOS arm64; shading="flat" is fine #32269
On macOS arm64, tripcolor(..., shading="gouraud") draws long coloured streaks and glitches. The same mesh with shading="flat" is fine. Standalone matplotlib + numpy, Agg backend. Other machines render both panels identically.
Left (flat): smooth field. Right (gouraud): radial streaks (PNG attached). Same with savefig/Agg.
Expected outcome
Gouraud should look like a smooth version of the flat panel, matching other machines.
Additional information
matplotlib 3.10.3 backend: Agg
numpy 2.0.2
python 3.12.7
OS macOS-26.6.2-arm64-arm-64bit
Also on matplotlib 3.11.1
Only the Gouraud path fails. Force-reinstall and a fresh env did not help. Workaround: shading="flat". Distinct from #28769 (small colour offset, not streaking).
Sorry, I actually am not sure what other machines were used. I was contacting support (Filipe at Tidy3D) for an application I use that uses this rendering and they said it works on their machine, and helped me narrow it down to the gouraud meshing.
On my machine running qtagg and the pre-8c0fee9 version of main, I get:
Why is this happening?
There are two duplicated points at (-13.655762672424316, -0.5812439322471619). When we dilate the triangle, we call calc_orthogonal() on these two points. We calculate d as 0, and then divide by zero to produce nan.
We end up trying to move a line to (nan, nan).
I don't understand why this is reported as only occurring on macOS. My only thought is that d got calculated to a floating-point denormalized number (denormal) and macOS is running with "flush denormals to zero". This could create nan instead of a tiny denormal value. However, I couldn't find any occurrences in the dataset where the delta of two points would result in a denormal.
Interesting! Apparently Apple silicon converts NaNs (and infinity) to integers differently than Intel-based processors. For example, a NaN converted to uint32 on Intel becomes zero, but on arm64 becomes 4294967295.
We have to continue to dilate on matplotlib 3.11.x, but we could tweak the Agg code so that calc_orthogonal() returns zeros instead of NaNs in this situation.
Ah, yes, that is an ARM vs x86 difference with NaN handling, because that conversion is undefined. That's why we normally skip NaN in paths (via PathNanRemover). If the triangle dilation is producing NaN, then we should ensure that doesn't get to the rasterizer. See, e.g., the following where we remove NaN triangles before dilation:
I was going to suggest dropping triangles with NaN, post-dilation (i.e., repeat the check I mentioned above after the span_gen.triangle call). I don't know which is better for edge cases, though.
Activity
iccir commentedon Aug 31, 2026
Can you clarify "other machines"? Other macOS machines? Are they Intel or arm64?
iccir commentedon Aug 31, 2026
@matthew-a12: Can you attach "gouraud_mesh.npz"?
matthew-a12 commentedon Aug 31, 2026
Sorry, I actually am not sure what other machines were used. I was contacting support (Filipe at Tidy3D) for an application I use that uses this rendering and they said it works on their machine, and helped me narrow it down to the gouraud meshing.
I have attached gouraud_mesh.npz.
gouraud_mesh.zip
iccir commentedon Aug 31, 2026
Can reproduce on macOS 14 and macOS 27 running python 3.14.5 in a fresh venv.
Cannot reproduce on either system using my dev version of matplotlib.
iccir commentedon Aug 31, 2026
This specific case is fixed by 8c0fee9 due to no longer dilating the triangles (we now pass in
0.0tospan_gen.triangle()rather than0.5).@ayshih - Is this issue safe to close, or is there anything here that you want to investigate further?
QuLogic commentedon Aug 31, 2026
Probably enough to close without any more investigation, but any idea why it only shows on macOS?
iccir commentedon Aug 31, 2026
I'm going to investigate when I get home tonight, as I'm immensely curious.
My first thought is that an uninitialized variable or floating point optimization is pulling a coordinate far past (0, 0) to the top-left.
This is in the dilation code, which is now completely unused. However, I want to ensure that it's not a bug that can show up elsewhere.
iccir commentedon Sep 1, 2026
@QuLogic - Can you try the following on a Linux machine?
Simplified version of bug
On my machine running qtagg and the pre-8c0fee9 version of main, I get:
Why is this happening?
There are two duplicated points at
(-13.655762672424316, -0.5812439322471619). When we dilate the triangle, we callcalc_orthogonal()on these two points. We calculatedas 0, and then divide by zero to producenan.We end up trying to move a line to
(nan, nan).I don't understand why this is reported as only occurring on macOS. My only thought is that
dgot calculated to a floating-point denormalized number (denormal) and macOS is running with "flush denormals to zero". This could createnaninstead of a tiny denormal value. However, I couldn't find any occurrences in the dataset where the delta of two points would result in a denormal.ayshih commentedon Sep 1, 2026
Interesting! Apparently Apple silicon converts NaNs (and infinity) to integers differently than Intel-based processors. For example, a NaN converted to uint32 on Intel becomes zero, but on arm64 becomes 4294967295.
We have to continue to dilate on matplotlib 3.11.x, but we could tweak the Agg code so that
calc_orthogonal()returns zeros instead of NaNs in this situation.ayshih commentedon Sep 1, 2026
Can someone with such a machine give this patch a try? v3.11.x...ayshih:matplotlib:apple_silicon_is_different
QuLogic commentedon Sep 1, 2026
Ah, yes, that is an ARM vs x86 difference with NaN handling, because that conversion is undefined. That's why we normally skip NaN in paths (via
PathNanRemover). If the triangle dilation is producing NaN, then we should ensure that doesn't get to the rasterizer. See, e.g., the following where we remove NaN triangles before dilation:matplotlib/src/_backend_agg.h
Lines 1161 to 1163 in ef42921
QuLogic commentedon Sep 1, 2026
I was going to suggest dropping triangles with NaN, post-dilation (i.e., repeat the check I mentioned above after the
span_gen.trianglecall). I don't know which is better for edge cases, though.iccir commentedon Sep 4, 2026
The patch fixes the issue on my M2 Ultra arm64 machine.