MalphasWats MalphasWats - 27 days ago 9
Javascript Question

Why is my simple webgl demo so slow

I've been trying to learn Web GL using these awesome tutorials. My goal is to make a very simple 2D game framework to replace the canvas-based jawsJS.

I basically just want to be able to create a bunch of sprites and move them around, and then maybe some tiles later.

I put together a basic demo that does this, but I hit a performance problem that I can't track down. once I get to ~2000 or so sprites on screen, the frame rate tanks and I can't work out why. Compared to this demo of the pixi.js webgl framework, which starts losing frames at about ~30000 bunnies or so (on my machine), I'm a bit disappointed.

My demo (framework source) has 5002 sprites, two of which are moving, and the frame rate is in the toilet.

I've tried working through the pixi.js framework to try to work out what they do differently, but it's 500kloc and does so much more than mine that I can't work it out.

I found this answer that basically confirmed that what I'm doing is roughly right - my algorithm is pretty much the same as the one in the answer, but there must be more to it.

I have so far tried a few things - using just a single 'frame buffer' with a single shape defined which then gets translated 5000 times for each sprite. This did help the frame rate a little bit, but nothing close the the pixi demo (it then meant that all sprites had to be the same shape!). I cut out all of the matrix maths for anything that doesn't move, so it's not that either. It all seems to come down to the

drawArrays()
function - it's just going really slow for me, but only for my demo!

I've also tried removing all of the texture based stuff, replacing the fragment shader with a simple block colour for everything instead. It made virtually no difference so I eliminated dodgy texture handling as a culprit.

I'd really appreciate some help in tracking down what incredibly stupid thing I've done!

Edit: I'm definitely misunderstanding something key here. I stripped the whole thing right back to basics, changing the vertex and fragment shaders to super simple:

attribute vec2 a_position;

void main() {
gl_Position = vec4(a_position, 0, 1);
}


and:

void main() {
gl_FragColor = vec4(0,1,0,1); // green
}


then set the sprites up to draw to (0,0), (1,1).

With 5000 sprites, it takes about 5 seconds to draw a single frame. What is going on here?

Answer Source

A look at a the frame calls using WebGLInspector or the experimental canvas inspector in chrome reveals a totally not optimized rendering loop.

You can and should use one and the same vertexbuffer to render all your geometry, this way you can save the bindBuffer aswell as the vertexAttribPointer calls. You can also save 99% of your texture binds as you're repetively rebinding one and the same texture. A texture remains bound as long as you do not bind something else to the same texture unit.

Having a state cache is helpful to avoid binding data that is already bound.

Take a look at my answer here about the gpu as a statemachine.

Once your rendering loop is optimized you can go ahead and consider the following things:

  • Use ANGLE_instanced_arrays extension
  • Avoid constructing data in your render loop.
  • Use an interlaced vertexbuffer.
  • In some cases not using an indexbuffer also increases performance.
  • Check if you can shave off a few GPU cycles in your shaders
  • Break up your objects into chunks and do view frustum culling on the CPU side.