tripcolor shading="gouraud" streaks/glitch on macOS arm64; shading="flat" is fine · Issue #32269 · matplotlib/matplotlib · GitHub
Skip to content

tripcolor shading="gouraud" streaks/glitch on macOS arm64; shading="flat" is fine #32269

Open

Description

@matthew-a12

Bug summary

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.

Code for reproduction

import matplotlib.pyplot as plt
import numpy as np

m = np.load("gouraud_mesh.npz")  # attach this file
pts, cells, vals = m["pts"], m["cells"], m["vals"]
fig, ax = plt.subplots(1, 2, figsize=(12, 4), constrained_layout=True)
for a, sh in zip(ax, ("flat", "gouraud")):
    im = a.tripcolor(pts[:, 0], pts[:, 1], cells, vals, shading=sh, cmap="jet")
    a.set_aspect("equal")
    a.set_title(sh)
    fig.colorbar(im, ax=a)
fig.savefig("gouraud_test_result.png", dpi=200)

Actual outcome

Image

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).

Operating system

macOS 26.6.2 arm64

Matplotlib Version

3.10.3 (also 3.11.1)

Matplotlib Backend

Agg (also macosx)

Python version

3.12.7

Jupyter version

No response

Installation

pip

Activity

  1. iccir commented on Aug 31, 2026

    @iccir
    Contributor

    Other machines render both panels identically

    Can you clarify "other machines"? Other macOS machines? Are they Intel or arm64?

  2. iccir commented on Aug 31, 2026

    @iccir
    Contributor

    @matthew-a12: Can you attach "gouraud_mesh.npz"?

  3. matthew-a12 commented on Aug 31, 2026

    @matthew-a12
    Author

    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

  4. iccir commented on Aug 31, 2026

    @iccir
    Contributor

    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.

  5. iccir commented on Aug 31, 2026

    @iccir
    Contributor

    This specific case is fixed by 8c0fee9 due to no longer dilating the triangles (we now pass in 0.0 to span_gen.triangle() rather than 0.5).

    @ayshih - Is this issue safe to close, or is there anything here that you want to investigate further?

  6. QuLogic commented on Aug 31, 2026

    @QuLogic
    Member

    Probably enough to close without any more investigation, but any idea why it only shows on macOS?

  7. iccir commented on Aug 31, 2026

    @iccir
    Contributor

    Probably enough to close without any more investigation, but any idea why it only shows on macOS?

    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.

  8. iccir commented on Sep 1, 2026

    @iccir
    Contributor

    @QuLogic - Can you try the following on a Linux machine?

    Simplified version of bug
    import matplotlib.pyplot as plt
    
    pts = [[-13.655762672424316, -0.5812439322471619],
           [-13.790421485900879, -0.6681561470031738],
           [-13.655762672424316, -0.5812439322471619]]
    cells = [[0, 1, 2]]
    vals = [300.1705322265625, 300.1552734375, 300.1705322265625]
    
    x = [p[0] for p in pts]
    y = [p[1] for p in pts]
    
    fig, ax = plt.subplots()
    im = ax.tripcolor(x, y, cells, vals, shading="gouraud", cmap="jet")
    ax.set_aspect("equal")
    plt.show()

    On my machine running qtagg and the pre-8c0fee9 version of main, I get:

    Image

    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.

  9. ayshih commented on Sep 1, 2026

    @ayshih
    Contributor

    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.

  10. ayshih commented on Sep 1, 2026

    @ayshih
    Contributor

    Can someone with such a machine give this patch a try? v3.11.x...ayshih:matplotlib:apple_silicon_is_different

  11. QuLogic commented on Sep 1, 2026

    @QuLogic
    Member

    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

  12. QuLogic commented on Sep 1, 2026

    @QuLogic
    Member

    Can someone with such a machine give this patch a try? v3.11.x...ayshih:matplotlib:apple_silicon_is_different

    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.

  13. iccir commented on Sep 4, 2026

    @iccir
    Contributor

    Can someone with such a machine give this patch a try? v3.11.x...ayshih:matplotlib:apple_silicon_is_different

    The patch fixes the issue on my M2 Ultra arm64 machine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions