Making use of the GPU to render huge amount of objects

Hi,

I’m new to p5.js and WebGL, so I’m not sure if I’m using the right terminology.

Let’s start with the problem:
We’re currently displaying a map of objects (simple rectangles with text in them, plus some single lines). Unfortunately, the number elements can go up to 25,000, probably even more, which completely crushes the performance.

At the moment, we do all the draw calls in the draw() function, which I assume, is the main problem. In an older version, there’s a manual check, if an object is visible, which helps a little, but the performance is still quite bad. And, depending on the zoom level, there’s still quite a lot of objects to render, even with the visibility check.

Since we’re talking WebGL here, my gut feeling is, that doing all the draw calls in JavaScript in not the correct way to approach this. So, my question is, is it possible to throw the objects at the GPU once and let it do all the rendering? The positioning is static, so we really only need to move around the camera (and maybe do a re-render of the text inside the rectangles, once the zoom level changes).

I also had the approach to render a layer in setup() and use that one for scrolling around, this worked really fine, but only to a certain zoom level and then the image starts to become blurry. My guess would be, that there’s an upper limit to the bitmap size of createGraphics(), so the created bitmap does not match the image size I’m displaying. It’s also cut off at the end, that would match my assumption, that I exceeded the max image size.

Thanks in advance

In my opinion, you were on the right track using p5.Graphics. If they blur too much you could make a ‘grid’ (side by side) of p5.Graphics and drag them as a whole. (or partially, only the ones needed)

1 Like

Hi,

Welcome to the community! :slight_smile:

By default, p5.js is not using the WEBGL mode for rendering but rather the standard canvas html element.

You said that you were using WEBGL but if you are only drawing lines, text and rectangles in two dimensions, there’s no need to use it.

For the canvas, it’s usually drawn using the CPU (in immediate mode) but today some browsers do hardware acceleration using the GPU for rendering :

On the other side WEBGL is using the gpu for rendering and you might actually wonder which one to choose as stated in this thread :

Then if you need more performance, there’s some tricks to optimize the canvas rendering (like the one you did by pre rendering static shapes on a separate layer) :

But the issue is that p5js takes care of the webgl rendering for you so you don’t have a direct low level access to the api. If you really need more performance, go with WEBGL directly rather than using p5js so you can do optimizations. (again makes more sense if you are rendering 3d but works for 2d)

1 Like

Hi all, thanks for the replies.

@noel Using multiple graphics sounds like a good idea, but I’m not sure yet, how deep the users want to zoom in, so that solution might turn out as quite a memory hog, when the zoom level is high. But the same would have been true for my one-bitmap-solution as well. But we’ll give that one a try, since it’s easy to implement.

@josephh I was afraid someone would say that we might need to use “real” WebGL - I was hoping there was a way to create a static scene in p5 that is completely handed out to the GPU with a moveable camera. But I’ll keep that in mind, if we can’t get it to work nicely in p5. I’ll probably look into Unity or Unreal Engine as well, since both seem to offer WebGL export, which might make things a little bit easier than coding it in WebGL directly.

Again if you are using WEBGL with p5.js it’s already the case. The limitation is due to your graphics card and the number of objects you want to draw. What I wanted to say is if p5.js is not giving enough performance, you might want to look into low level optimization tricks with webgl and rendering on different buffers…

You might also want to look into Godot game engine which open source and well suited for 2d and web export!

1 Like

@josephh Maybe I’m misunderstanding something here completely, but from every example I looked up so far, they all have the instructions to draw the elements in the draw() method. So, for every frame, JavaScript has to loop over all the elements and do all the draw instructions, which I would assume is the bottleneck here.What I’m looking for is a way to pre-buid the scene and then only have something like a translate() call in the draw() function to move the viewport over the pre-build WebGL scene, to take JavaScript out of the loop, once the scene has been prepared.

I already tried the translate() method and drew all the elements in the setup() method and then tried to do a translate() in the draw() method, but that did not work. From what I understand, translate() only sets the coordinates for the next instructions in the draw() method, so without re-drawing all the elements, nothing will happen.

One way to control when to run draw() manually is to use noLoop() and redraw():

function setup() {
  createCanvas(400, 400);
  background(220);

  noLoop()
}

function draw() {
  ellipse(random(width), random(height), 30, 30)
}

function mouseDragged() {
  redraw()
}
1 Like

Yes this is exactly what immediate mode rendering does whereas in retained mode, it’s the graphics API that holds your data and you interact with it through states and functions. OpenGL does that for example.

This is possible, I think that you did it by rendering the static objects onto a separate buffer then drawing that buffer instead of rendering every objects at each frame. The limitation here is the resolution of your buffer but only when doing scale. Otherwise with translate it works well.

The translate will move the coordinate system but then you can draw your buffer at that place.

1 Like

Ah, ok, then the createGraphics()-layers are not prerendered bitmaps, but are rendered only, when I actually draw the layer? In that case, that’s exactly what I was trying to achieve and did so by accident :smiley:

The createGraphics() function returns a p5.Graphics object which basically stores a canvas.

And a canvas is a bitmap image under the hood but you can define your bitmap resolution to be different than the size of the html element of the canvas (so you can zoom into it).

If you want infinite zoom, you might want to use SVG which is vector based graphics. Unfortunatly it’s not supported in p5.js : Render SVG in P5.js

This is very well explained on the Wikipedia page :

1 Like

Yes, found that out myself just now. Too bad, I was hoping we could do something like a “reverse” translate(), and move the viewport on a layer after we placed the objects. Meaning, place all the objects in setup(), regardless if they are on the canvas or not and then call myLayer.translate() later in the draw() method.

I don’t think that SVG can handle the amount of objects we’re planning to draw, so we would most likely face the same problem here and have to reduce the amount of objects drawn. In that case would could just stick with p5.

The fallback idea was to introduce something like level of detail in games, and reduce clusters of objects to single objects, when the graph is zoomed out. I was just hoping, we could avoid that and just throw everything at the GPU and let it do all the work.

Maybe we’ll look into one the game engines, but learning these will probably be at least as much work as filtering our objects to display.

Thanks again.

1 Like