flekschas / regl-scatterplot

Scalable WebGL-based scatter plot library build with Regl
https://flekschas.github.io/regl-scatterplot/
MIT License
184 stars 21 forks source link

Displaying colors instead of dots after hiding and showing points multiple times. #165

Open Sagva opened 5 months ago

Sagva commented 5 months ago

Hello!

We encountered a bug using the latest version of the regl-scatterplot package, which breaks the displaying of dots when we try to hide/show the drawn points.

The bug could be seen and reproduced in the Codepen https://codepen.io/Sagva/pen/bGZKyBb

It was tested in different browsers with the same result: Chrom, Firefox, Edge (all on OS Windows 11).

Steps to reproduce:

  1. Create a scatterplot
  2. Draw points using the method:
    scatterplot.draw([
    [0.2, -0.1, 0, 0.1337],
    [0.3, 0.1, 1, 0.3371],
    [-0.9, 0.8, 2, 0.3713],
    ]);
  3. Press the “Hide/show points” button which uses scatterplot.draw([]) or scatterplot.clear() for hiding and
    scatterplot.draw([
    [0.2, -0.1, 0, 0.1337],
    [0.3, 0.1, 1, 0.3371],
    [-0.9, 0.8, 2, 0.3713],
    ]); 

    for showing points again.

After around 30 showings/hidings the scatter plot displays the colors in a mesh instead of the dots. image

The console shows warnings:

WebGL: INVALID_ENUM: activeTexture: texture unit out of range [.WebGL-000045A00AE21B00] GL_INVALID_OPERATION: Feedback loop formed between Framebuffer and active Texture. [.WebGL-000045A00AE21B00] GL_INVALID_VALUE: Sampler uniform value out of range.

image

Best regards, Elena.

flekschas commented 5 months ago

Oh nooo! That's an odd one. I'll take a look what might causes this error to be thrown. You mentioned "in the latest version". Does that mean this used to work before?

As a quick work around, if you really only want to hide and show points, scatterplot.filter([]) and scatterplot.unfilter() should be more performant.

Sagva commented 5 months ago

Oh nooo! That's an odd one. I'll take a look what might causes this error to be thrown. You mentioned "in the latest version". Does that mean this used to work before?

We haven't tested it in the versions that are earlier than v1.8.4 and v1.8.5. This bug occurs in both versions.

As a quick work around, if you really only want to hide and show points, scatterplot.filter([]) and scatterplot.unfilter() should be more performant.

Thank you, will try to use that instead.

Sagva commented 4 months ago

Looks like repeatedly setting params causes the bug:

scatterplot.set({
  pointColor: ['#FF0000', '#0000FF', '#008000'],
  pointSize: 10,
  opacity: 1,
  colorBy: "valueA"
});

In the Codepen https://codepen.io/Sagva/pen/wvOxvdg that uses scaterplot.filter([]) and calls scatterplot.set({...}) each time when the button is clicked, also has the same result with broken dot displaying.

flekschas commented 4 months ago

Yeah, I suspect it has to do with repeated uploads of a new texture to the GPU, which happens in both scenarios that you describe.

flekschas commented 4 months ago

I think the issue is that you're calling draw() prematurely before the previous draw call was finished. When I change your demo as follows, I cannot reproduce the error anymore:

import createScatterplot from "https://esm.sh/regl-scatterplot";

let isHidden = false;
const canvas = document.querySelector('#canvas');
const { width, height } = canvas.getBoundingClientRect();
const scatterplot = createScatterplot({
  canvas,
  width,
  height,
  pointColor: ['#FF0000', '#0000FF', '#008000'],
  pointSize: 10,
  opacity: 1,
  colorBy: "valueA"
});

const points = [
  [0.2, -0.1, 0, 0.1337],
  [0.3, 0.1, 1, 0.3371],
  [-0.9, 0.8, 2, 0.3713],
]

function drawPoints() {
  scatterplot.draw([
  [0.2, -0.1, 0, 0.1337],
  [0.3, 0.1, 1, 0.3371],
  [-0.9, 0.8, 2, 0.3713],
]);
scatterplot.set({
  pointColor: ['#FF0000', '#0000FF', '#008000'],
  pointSize: 10,
  opacity: 1,
  colorBy: "valueA"
});
}
drawPoints()

const btn = document.querySelector('#button');
btn.addEventListener("click", async () => {
  btn.disabled = true;
  if (!isHidden) {
    await scatterplot.draw([])
  } else {
    await scatterplot.draw(points);
  }
  isHidden = !isHidden 
  btn.disabled = false;
});

The key difference here is that the click handler is async and awaits the finishing of the draw call before accepting a new draw call by disabling the button in between.

There should be a way for regl-scatterplot to handle this internally by ignoring premature draw() calls but the easier solution is to embrace that draw() is an asynchronous call.

Sagva commented 4 months ago

Thank you for your time and answer! It was helpful.