Why is my simple webgl demo so slow - javascript

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?

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.

The problem is probably this line in render: glixl.context.uniformMatrix3fv(glixl.matrix, false, this.matrix);.
In my experience, passing uniforms for each model is very slow in webGL, and I was unable to maintain 60FPS after ~1,000 unique models. Unfortunately there is no uniform buffers in webgl to alleviate this problem.
I solved my problem by just calculating all the vertex positions on the CPU and draw them all using one drawArray call. This should work if the vertex count isnt overwhelming. I can draw 2k moving + rotating cubes at 60 FPS. I dont recall exactly how many cubes you can draw at 60 FPS but it is quite a bit higher than 2k. If that isnt fast enough then you have to look into drawArrayInstanced. Basically, store all the matrices on an arraybuffer and draw all your models using one drawArrayInstanced call with correct offset and such.
EDIT: also to the OP, if you want to see how PIXI does the vertex update rendering (NOT uniform instancing), see https://github.com/GoodBoyDigital/pixi.js/blob/master/src/pixi/renderers/webgl/utils/WebGLFastSpriteBatch.js.

Related

Why is GPU render time inconsistent?

I'm building a WebGL application using THREE and am noticing some odd timing on the GPU. I don't have repro code available at the moment but I thought I'd ask the question in case it's a known browser quirk or something common and fixable.
Scene Setup
Scene with ~2,000,000 polygons, 136 Meshes, and 568 Object3D instances.
Using THREE.Composer with the FXAA and Unreal Bloom passes.
Using THREE.OrbitControls.
The scene is only rendered when something is known to have changed. For example, a draw is scheduled when the user drags the scene to move the camera with the controls or something in the scene moves. The scene is often static so we try not to render unnecessarily in those cases.
The Problem
The issue happens when the scene has been static (not drawn for a bit) and then the user changes the camera position by dragging. Once the user starts dragging the framerate is very choppy -- maybe 10-20 fps or lower -- for several frames before smoothing back out to something closer to 60. This happens consistently when leaving the scene alone for several seconds and then dragging again. If the mouse is dragged consistently after the initial stutter then the framerate stays smooth. Nothing different is being rendered for these frames.
This stuttering doesn't happen and the scene remains snappy if it's rendered every frame using requestAnimationFrame.
Here's the performance profiler with the stutter when the scene is only being rendered when something changes. You can see that there is a lot more time spent on the GPU during the frames that stutter before smoothing out again:
And the profiler when the scene is rendered at 60 fps:
Any thoughts? Why is there so much more GPU work happening suddenly on drag? Could the draw be blocked by some other rendering process? Why would it happen so consistently after not rendering for a few seconds? I've profiled using the latest version of Chrome but the stutter is present in Firefox, as well.
Thank you!
without a live sample there is no easy way to know BUT....
1 Three.js can do frustum culling on objects.
That means if some objects are off outside of the view they won't get drawn. So, put the camera in such a way that all objects are visible will run slower than if only some objects are visible
2 Primitive Clipping
Same as above except at the GPU level. The GPU clips primitives (it doesn't draw or compute pixels outside the view) so similar to above, if the lots of the things you're trying to draw happen to be outside the view it will run faster than if everything is inside the view.
3 Depth(Z) Buffer rejection
Similar to above again, if your objects are opaque then if a pixel is is behind an existing pixel via the depth test the GPU will skip calling the pixel shader if it can. This means if you draw 568 things and the first one you draw is the closest thing to the camera and covers up many things behind it than it will run faster than if all those things behind it draw drawn first. Three.js has the option to sort before drawing. Usually sorting is turned on for transparency since transparent objects need to be drawn back to front. For opaque objects though drawing front to back will be faster if any front objects occlude objects further back.
4 Drawing too many frames?
Another question is how are you queuing your draws? ideally you only queue a single draw and until the drawing has happened don't queue any more.
So
// bad
someElement.addEventListener('mousemove', render);
The code above will try to render for every mouse move even if that's > 60 fps
// bad
someElement.addEventListener('mousemove', () => {
requestAnimationFrame(render);
});
The code above may queue up lots and lots of requestAnimationFrames all of which will get executed on the next frame, drawing your scene multiple times per frame
// good?
let frameQueued = false;
function requestFrame() {
if (!frameQueued) {
frameQueued = true;
requestAnimationFrame(render);
}
}
function render(time) {
frameQueued = false;
...
}
someElement.addEventListener('mousemove', () => {
requestFrame();
});
Or something along those lines so that at most you only queue on render and don't queue any more until that render has completed. The code above is just one example of a way to structure your code so that you don't draw more frames than you need to.

Canvas HTML5 animation with GSAP/d3.js

I want to create a HTML5 canvas animation likely the one on this site: https://flowstudio.co/.
I have started with GSAP, but it looks like creating something like this, is really a big task.
I have to create mostly every point/move singular and i have no idea if there is a faster/better way.
Currently i only have looked at GSAP without plugins.
Is there some special tool/(GSAP) plugin that can help to create this?
Or should i maybe use d3.js?
I also tried to find an tutorial for this, but it looks like there is nothing for this more advanced case.
Thanks for the help!
The example you provided is using THREE.js and I would suggest you to use it too since you want to operate in 3D space also.
When you want to animate a large ammount of points you will need to use a vertex shader. That's because vertex shader will allow you to calculate all of the points positions in one step (thanks to parallel computing on the GPU), whereas doing it the 'normal way' (on the CPU) is very bad on performance, since every single point has to be calculated one by one. (here you can see the difference)
The way you animate the points is a little different than you might think- you don't want to apply animation to every. single. point...
Instead you will need three things that you will pass to the shader:
-array containing starting points position,
-array containing final points position,
-blend parameter (just a float variable taking values from 0 to 1).
Then you use GSAP to animate only the blend parameter, and shader does the rest (for example when the blend parameter is 0.5 the point position is exactly halfway between starting position and final position that you provided to the shader)
The example you provided is also using some kind of Perlin Noise function which you will have to implement in the shader also.
Its a lot to bite at one time but here's some great tutorials from Yuri Artyukh which will help you achieve something similiar:
https://www.youtube.com/watch?v=XjZ9iu_Z9G8&t=5713s
https://www.youtube.com/watch?v=QGMygnzlifk
https://www.youtube.com/watch?v=RKjfryYz1qY
https://www.youtube.com/watch?v=WVTLnYL84hQ&t=4452s
Hope it helps and...good luck!

JavaScript canvas game development

Ive been having a really baffling slow down issue with the game I am working on probably because I am unsure how to handle graphics (most likely responsible for the slow down) in javascript without using a third party framework like Phaser/ImapactJS/EaselJS etc)*. The following is the low down on how I am approaching my graphics. I would be very thankful for some tips or methods on how to do this right.
My game is tile based - using tiles designed at 500x500 px because I want them to display decently on high definition devices.
I am using a spritesheet to load all (most of) my tiles before the main loop is run. This image is roughly 4000 x 4000 (keeping it below 4096 because the GPU cant handle texture sizes larger than that).
I then use the drawImage function to cycle through and draw each tile on a part of the canvas using information (w, h, x, y) stored in the tile array. I do this on every cycle of the main loop using my drawMap function.
The map is currently 6x6 tiles in size
A character spritesheet is also loaded and drawn on to the canvas after the map has been drawn. The character displays a different frame of the animation on every cycle of the main loop. There are sets of animations for the character each contained in the same spritesheet.
The character sprite sheet is roughly 4000x3500
The character is roughly 350x250 px
Other objects also use the same sprite sheet. Currently there is only one object.
Possibly helpful questions:
Am I using too many spritesheets or too few?
Should I only draw something if it's coordinates are in bounds of the screen?
How should I go about garbage collection? Do I need to set image objects to null when no longer in use?
Thanks in advance for input. I would just like to know if I am going about it the right way and pick your brains as how to speed it up enough.
*Note that I plan to port the JS game to cocoonJS which provides graphics acceleration for the canvas element on mobile.
** If interested please visit my Patreon page for fun!
You have asked lots of questions here, I'll address the ones I've run into.
I would like to start out by saying very clearly,
Use a profiler
Find out whether each thing you are advised to do, by anybody, is making an improvement. Unless we work on your code, we can only give you theories on how to optimise it.
How should I go about garbage collection? Do I need to set image objects to null when no longer in use?
If you are no longer using an object, setting its reference to null will probably mean it gets garbage collected. Having nulls around is not necessarily good but this is not within the scope of this question.
For high performance applications, you want to avoid too much allocation and therefore too much garbage collection activity. See what your profiler says - the chrome profiler can tell you how much CPU time the garbage collector is taking up. You might be OK at the moment.
I then use the drawImage function to cycle through and draw each tile on a part of the canvas using information (w, h, x, y) stored in the tile array. I do this on every cycle of the main loop using my drawMap function.
This is quite slow - instead consider drawing the current on screen tiles to a background canvas, and then only drawing areas which were previously obscured.
For example, if your player walks to the left, there is going to be a lot of tiles on the left hand side of the screen which have come into view; you will need to draw the background buffer onto the screen, offset to account for the movement, and then draw the missing tiles.
My game is tile based - using tiles designed at 500x500 px because I want them to display decently on high definition devices
If I interpret this right, your tiles are 500x500px in diameter, and you are drawing a small number of these on screen. and then for devices without such a high resolution, the canvas renderer is going to be scaling these down. You really want to be drawing pixels 1:1 on each device.
Would you be able, instead, to have a larger number of smaller tiles on screen - thereby avoiding the extra drawing at the edges? Its likely that the tiles around the edges will sometimes draw only a few pixels of one edge, and the rest of the image will be cropped anyway, so why not break them up further?
Should I only draw something if it's coordinates are in bounds of the screen?
Yes, this is a very common and good optimisation to take. You'll find it makes a big difference.
Am I using too many spritesheets or too few?
I have found that when I have a small number of sprite sheets, the big performance hit is when I frequently switch between them. If during one draw phase, you draw all your characters from character_sheet.png and then draw all the plants from plant_sheet.png you'll be ok. Switching between them can cause lots of trouble and you'll see a slow down. You will know this is happening if your profiler tells you that drawImage is taking a big proportion of your frame.

How can I increase map rendering performance in HTML Canvas?

We are developing a web-based game. The map has a fixed size and is procedually generated.
At the moment, all these polygons are stored in one array and checked whether they should be drawn or not. This requires a lot of performance. Which is the best rendering / buffering solution for big maps?
What I've tried:
Quadtrees. Problem: Performance still not as great because there are so many polygons.
Drawing sections of the map to offscreen-canvases. A test run: http://norizon.ch/repo/buffered-map-rendering/ Problem: The browser crashes when trying to buffer that much data and such big images (maybe 2000x2000) still seem to perform badly on a canvas.
(posting comments as an answer for convenience)
One idea could be, when the user is translating the map, to re-use the part that will still be in view, and to draw only the stripe(s) that are no longer corrects.
I believe (do you confirm ?) that the most costly operation is the drawing, not to find which polygon to draw.
If so, you should use your QuadTree to find the polygons that are within the strips. Notice that, given Javascript's overhead, a simple 2D bucket that contains the polygons that are within a given (x,y) tile might be faster to use (if the cost of the quadtree is too high).
Now i have a doubt about the precise way you should do that, i'm afraid you'll have to experiment / benchmark, and maybe choose a prefered browser.
Problems :
• Copying a canvas on itself can be very slow depending on devices/Browsers. (might require to do 2 copy, in fact)
• Using an offscreen canvas can be very slow depending on devices/Browsers. (might not use hardware acceleration when off-screen).
If you are drawing things on top of the map, you can either use a secondary canvas on top of the map canvas, or you'll be forced to use an off-screen canvas that you'll copy on each frame.
I have tried a lot of things and this solution turned out to be the best for us.
Because our map has a fixed size, it is calculated server-side.
One big image atlas with all the required tiles will be loaded at the beginning of the game. For each image on the atlas, a seperate canvas is created. The client loads the whole map data into one two-dimensional array. The values determine, which tile has to be loaded. Maybe it would be even better if the map was drawn on a seperate canvas, so that only the stripes have to be painted. But the performance is really good, so we won't change that.
Three conclusions:
Images are fast. GetImageData is not!
JavaScript has not yet great support for multi threading, so we don't calculate the map client-side in game-time.
Quadtrees are fast. Arrays are faster.

What is the real benefit of using canvas for games?

I'm currently reading up on the canvas, but I'm finding it hard to find practical benefits of using canvas, when a lot can be done using simple css overlays/JavaScript (+ jquery lib).
This is probably because I don't know the FULL practicalities of using canvas.
Looking at this game:
http://www.pirateslovedaisies.com/
Could someone help explain how and why canvas is being used as opposed to just css?
This is a 4k js/canvas demo I wrote to experiment with the 2d context (here is a video if your browser doesn't work). I tested it only on chrome, opera, firefox, safari and nexus one browser.
Note that no external resources are loaded (i.e. the texture and the raytraced envmap are built dynamically), so this is just a single self-contained 4096 bytes HTML file.
You can do something like that with DIVs?
But indeed I agree that the game you linked IMO could be done also with DIVs; apparently there are no transformations - not even in the falling daisy loading scene - and the action areas for the pirates are just circles. Not sure but could be that even shooting only happens at fixed angles.
Canvas could have been used instead for:
Drawing general sloped lines and polygons (the map could be created dinamically from a compact description or could have been generated randomly). Shooting could be done at any angle...
Procedural image creation (e.g. textures or special pixel effects)
Gradients, texture mapping
General 2d matrix transforms
Of course a game using an image+DIVs approach is probably way easier to make (a lot of photoshop and simple xy animation).
Creating tons of HTML elements is extremely slow and memory-hungry. The canvas object is made for graphics operations and thus optimized for it. Besides that.. how would you draw a curve with plain HTML/CSS? ;)
Using <canvas> you have a per-pixel control of what's shown on the screen. You don't have to deal with specific browser CSS or DOM compatibility.
Also, that's actually a pretty similar programming model to 2D non-browser games, like those created using SDL o DirectDraw.
Here's a game I wrote in a few hours using Canvas; note that the scaling of the tiles, the anti-aliasing of the lines, is perfect. This would not be the case with image tiles that were being resized by the browser.
Click the tiles to rotate them in an attempt to make all connections. Click the row of buttons at the top for a new board of a different size; click the row of buttons below that for a new board with different numbers of connections.
The game concept is not mine, only the implementation.

Categories

Resources