plotly.js: Plotly fails when using "scattergl" and value greater than 2^24 [Only on M1]

When plotting using Plotly.newplot() and type scattergl on M1 machine, the plotting fails if the value of x-axis is greater than 2^24. Example:

var data = [
  {
    x: [16777217],
    y: [1],
    type: "scattergl",
  }
];

Plotly.newPlot('myDiv', data);

Screen Shot 2022-03-01 at 9 27 12 PM

The data gets plotted on the margin which cannot be zoomed/panned. (16777217 is 2^24 + 1) Attached screen shot. The issue only happens on M1 machine. On Intel hardware everything works well.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 4
  • Comments: 16 (4 by maintainers)

Most upvoted comments

In response to point 1 from my earlier comment, I went looking for other affected WebGL trace types in addition to scattergl. I couldn’t find any others that are affected by this same bug. I checked the following trace types:

        type: "streamtube",
        type: "cone",
        type: "mesh3d",
        type: "heatmapgl",
        type: "surface",
        type: "parcoords",
        type: "scattergl",
        type: "scatterpolargl",
        type: "scatter3d",
        type: "splom",

I found precision issues generally with x-values near 1e9. Some of them seem like the integers are being converted to float32s and we’re losing precision that way (similar to the scatter3d example from this comment). But I couldn’t find any issues that were M1 Mac specific.

Somewhat surprisingly, splom was not fixed by this change, despite also depending on regl-scatter2d. But I can repro that on Windows, so I guess that’s being caused by something else.

Can anyone else find M1 mac specific precision issues that aren’t fixed by https://github.com/gl-vis/regl-scatter2d/pull/36? If not, I recommend closing this ticket and tracking down the non-platform-specific precision bugs in another issue.

@Coding-with-Adam, yes, I’ll give it a shot!

I have a couple questions about how to do this:

  1. I’m going to try to fix this for all the relevant shaders, but I’m not totally sure how to find all the affected shaders and a good test case for each one. I might need to start by just fixing this on just scattergl. And fix each webgl plot type, one at a time with multiple PRs.
  2. Some of the affected shaders are in dependent packages (such as https://github.com/gl-vis/regl-scatter2d). Are the maintainers of these dependencies as motivated to fix this as plotly devs are?
  3. [Related to point 2] is there a performance penalty? If so, how big? According to this comment, plotly is likely not bottlenecked on webgl render. But if I’m making changes to dependencies, other packages might have bottlenecks in different places. Are there benchmarks I can use to quantify the performance penalty?

@justinjhendrick we also maintain gl-vis modules. Let’s start by the minimal PR to regl-scatter2d and provide a link to this issue in its description. After that we will could have a look at rendering times on CircleCI, etc. But at this point I’m not too worried about the performance side effect. Thank you!

Safari’s implementation of WebGL passes the -ffast-math option to the Metal compiler and this is causing the vertex shader to be rewritten from (just concentrating on the x coord of the vertex position):

position.x =
        (((_ux + _utranslate.x) * _uscale.x) +
        ((_uxFract + _utranslateFract.x) * _uscale.x) +
        ((_ux + _utranslate.x) * _uscaleFract.x) +
        ((_uxFract + _utranslateFract.x) * _uscaleFract.x)) * 2.0 - 1.0f;

to:

position.x = (2.0 * (_uscaleFract.x + _uscale.x)) * ((_utranslateFract.x + _utranslate.x) + (_uxFract + _ux)) - 1.0

This change in the order of floating-point operations leads to a difference in the calculation causing an incorrect position:

position.x = (2.0 * (_uscaleFract.x + _uscale.x)) * ((_utranslateFract.x + _utranslate.x) + (_uxFract + _ux)) - 1.0
= (2.0 * (0.0 + 0.5)) * ((0.0 + -16777216.0) + (1.0 + 1677216.0)) - 1.0

The position and translation 16777216.0 are exactly representable because they’re power-of-2 (2 ** 24), but a 32-bit float can’t represent 16777217.0, so 16777216.0 + 1.0 becomes 16777216.0.

= (2.0 * 0.5) * (-16777216.0 + 16777216) - 1.0
= 1.0 * 0.0 - 1.0
= -1.0

Which is the incorrect position shown in the initial bug report.

As a workaround, -ffast-math can be disabled by marking gl_Position as invariant in the GLSL shader, but it should be noted that this comes with a potential impact on the performance of the shader.

As a workaround, -ffast-math can be disabled by marking gl_Position as invariant in the GLSL shader, but it should be noted that this comes with a potential impact on the performance of the shader.

I can confirm that @djg’s suggestion fixed the issue for me. I added invariant gl_Position; after these two lines in these shaders: regl-scatter2d/marker-vert.glsl and regl-scatter2d/marker-vert.glsl. With those changed, scattergl was plotting points in the correct places with x values near 1e9.

Same issue here for M1 Pro chip + Chrome. Everything’s fine using devices without the new M1 chip. M1 img_v2_c9e5de32-8fda-425f-bb84-dd670cf1c3bg Other devices image

The same issue here. This issue happens only when using the WebGL mode. The plot becomes normal when manually switched back to the SVG mode.

image image