How to separate canvas update logic from rAF drawing updates - javascript

I'm working on my second big canvas project (a game) and are really trying to optimize everything.
I have my mouse and keyboard listeners set up so they change the state of my canvas layers but all drawing is done through my rAF call. This works well.
However, I'd like to do a similar thing with the rest of my game. Could I add an event listener or somehow update all my states/variables after each rAF call completes drawing?
For example, if something must move five pixels left I currently do the subtraction then draw. I would like to be able to do the subtraction after each frame outside the rAF and then inside of it just draw, draw, draw. No calculations.
Thanks!

Combine both. Save variables based on events and draw then with rAF.
see the link below. (the 2 sec loop should be your rAF)
Canvas animation frame rendering: Infinite Loop vs. triggering on mousemover()
(not my best code work)

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.

How to animate using requestAnimationFrame?

I am trying to use requestAnimationFrame to animate my canvas when I click on + and - buttons to zoom in and zoom out like the Google Maps does (When you click on the + button to zoom in, you see a slight transition or a delay). Unlike the traditional canvas implementations, I have a pretty complicated structure in the application.
When the button is clicked, zoomin() function inside a service is called where the values are calculated and then the resultant value is emitted which is captured by a different component A which in turn calls a different class method redraw() which redraws the canvas.
In every example I saw, the images are being animated by manipulating the pixel values with time and the draw() method is available right there. But could anyone tell me where I should handle this and is there any alternative? And also how could I animate like the google maps does in JavaScript?
Could anyone guide me?
Thank you.
The demos that you have seen are right. requestAnimationFrame is a means and not an end. You use it to make your application more smooth and performant by limiting how often a method can be called, not just for the sake of using it to draw because that will not help.
If you want to use requestAnimationFrame for throttling (ie doing a redraw update every 1/60 of a second instead of whenever zoomin is called), you will have to update your logic and indeed store and update the zoom values in the background in a data service somewhere. The requestAnimationFrame loop should indeed have access to and call redraw so it is called every 1/60 of a second instead of whenever something updates. Redraw should have access to all data needed to draw the map (like the current zoom level).
The only alternative is NOT using requestAnimationFrame and doing the debouncing yourself when receiving mousewheel events to prevent to much draws. It sounds like your application could benefit from a more simple structure that would allow the use of requestAnimationFrame.
For the animation part, I would ask a separate question as that is a whole other topic.

Why there is no method draw() in KineticJS documentation?

I've spent hours googling about Kinetic.Layer.draw() method. All that I've found is use-cases - no documentation about how, when and why to use it. Maybe it's deprecated already?
These are primary links which I use while learning and playing with this wonderful framework:
http://kineticjs.com/docs/index.html
http://www.html5canvastutorials.com/kineticjs/html5-canvas-events-tutorials-introduction-with-kineticjs/
It will be really helpful if somebody explains to me such misunderstanding.
Actually draw() and drawHit() are in the docs, but they are poorly documented:
http://kineticjs.com/docs/Kinetic.Stage.html#draw
draw()
draw layer scene graphs
http://kineticjs.com/docs/Kinetic.Stage.html#drawHit
drawHit()
draw layer hit graphs
Surprisingly I was unable to find the 3rd and last draw method: drawScene() in the Kinetic Docs. Also to my surprise, these 3 functions were not found to be extended from the parent class of Kinetic.Stage: Kinetic.Container
Anyways, I think this SO question explains the differences of the methods perfectly: What is the difference between KineticJS draw methods?
And definitely, there's no avoiding using these functions, you'll need to use one of them eventually unless your canvas/stage is static during your entire application. (*There may be an exception, see below)
To answer your questions:
How:
Call .draw() on any Kinetic.Container which includes: stage layer and group, or any Kinetic.Node which includes all the Kinetic.Shape
Examples:
stage.draw(); //Updates the scene renderer and hit graph for the stage
layer.drawHit(); //Updates the hit graph for layer
rect.drawScene(); //Updates the scene renderer for this Kinetic.Rect
Why:
I would think it's a performance thing to not have everything redraw on the Kinetic.Stage every single time there is a change. The use of the draw methods this way we can control programatically when we want the stage to be updated and rendered. As you might imagine, it is quite expensive to have to draw the stage all the time if we have say 10000 nodes in the scene.
When:
drawScene()
Anytime you need to update either the scene renderer (for example using .setFill() to change the fill of a shape)
drawHit()
To update the hit graph if you're binding events to your shapes so that the hit area for any events will be updated to the node changes.
draw()
Whenever you need to do both of the above.
Finally, perhaps an example/lab will be the most beneficial learning tool here, so I've prepared a JSFIDDLE for you to test out the differences. Follow the instructions and read my comments inside to get a better understanding of what's going on.
*NOTE: I mentioned above there was an exception to having to use the draw methods. That is because whenever you add a layer to the stage, everything in the layer is automatically drawn. There is small example of this described at the bottom of the fiddle.
The draw() method is basically used for drawing all the (visible) elements associated with the container you call the method on.
It is therefore not just limited to Kinetic.Layer but can also be used on Kinetic.Group, Kinetic.Container and so on...
When & Why to use:
Whenever you make any change to the canvas, you call the appropriate container's Draw() method. KineticJS does not refresh the canvas unless you explicitly say it using Draw(). In general, try to call the smallest container affected by your changes to make use of the efficient caching and redrawing only a part of canvas that was affected.
Take for instance:
You have 2 layers in your application. Layer1 is used for a static background and some other static items that need not be redrawn everytime.
And Layer2 contains your moving elements, or active objects. Then you can simply make a call to Layer2.draw()
To add the complexity, you have a group of objects, lets say all menu items. When a user presses any menu btn, its better to call menuGroup.draw() rather than the draw function of the its parent layer.

Canvas layers and game animation

I am wondering how to go about this?
draw, wait 1 second
draw frame2 wait 1 second
draw frame3 wait 1 second.
clear animation canvas
now I assume you have to do this on a overlapping canvas
so that it wont be wiped by my current game refresh at 60
fps.
I will also be having the animation frames be a transparent png
so you can see whats behind it also.
think smoke animation.
What is the best way to wait 1 second, use set interval or set Timeout?
I already use set animframerate on my main canvas layer
I need to know how to animate while other animation is going on, as my head tells me that painting to screen is procedural so if a method is getting called to animate something everything else stops while its getting animated.
Drawing onto a separate canvas is a good way to have multiple layers, but there are several ways to do what you would like:
If your PNG image is pre-defined, you can just render it directly onto the canvas with context.drawImage(). The transparency will be preserved. As long as your image is loaded onto the page before it is rendered, there will be little overhead in re-rendering it every frame.
You can draw to a hidden canvas and then draw that canvas onto your main canvas. You can do this by creating a canvas in JavaScript and never writing it to the page. For example: var layer = document.createElement('canvas'); and then call context.drawImage() with layer as the image parameter. I have written an implementation of a Layer class you can use to make this easier.
You can draw directly onto your main canvas using transparency by setting the globalAlpha property of your graphics context object. (Just remember to set it back to 1 after you're done drawing your transparent stuff.)
You can draw onto a secondary, visible canvas that is absolutely positioned over your primary canvas. To do this you need to set the CSS background-color of the secondary canvas to transparent.
Similarly, there are two good ways to wait one second:
Use setTimeout or setInterval. This will wait as close to your delay period as possible (in this case one second) and then execute the callback asynchronously. Use setTimeout if you want to execute something once and setInterval if you want to execute something at a predefined interval indefinitely (i.e. every second).
Keep a variable that holds the last time you executed the function you want to run, and check whether you've waited long enough before running the code again. This will run the code synchronously. For example:
// Outside of your animation loop
var lastRun = Date.now();
// Inside your animation loop
if (lastRun + 1000 < Date.now()) {
/* Run you callback code */
}
Since you are using requestAnimationFrame to run your main animation loop, this will run at the soonest time after your delay (e.g. 1 second) at which the browser is ready to paint a new frame. Note that if you want code using this method to run asynchronously, you can also use web workers.
Unless you're drawing text onto a canvas without caching it or drawing thousands of objects onto your canvas, chances are pretty good that your performance is not draw-bound, so I would opt for the simplest solution even if it's not asynchronous. You can use the stats.js library to test your canvas performance.

RaphaelJS - Simultaneous Animation

I have pong balls that are being generated very consistently, but the
rates change dynamically. So in a given second, there could be 1 pong
ball that's being drawn and translating across the screen (constantly
from left to right), or 50.
I have a pong paddle that responds based on the generation of these
balls, and it's supposed to "catch" every one of the balls that's
being sent towards its destination. The x coordinate is always the
same, because the pong paddle never moves, but the y coordinate is
randomly generated.
Here's an extremely similar (if not identical) example of what I'm
doing: http://www.youtube.com/watch?v=HeWfkPeDQbY
I have a lot of this code already written, but I'm afraid my design
for catching the balls is incorrect/inefficient. It works, but the
paddle very easily becomes out of sync with the balls that are being
thrown towards it.
The way I'm currently doing this is by putting each ball object into a
global array, and the paddle pops the next ball off of this queue and
uses basic arithmetic to calculate the speed at which it needs to
translate to the y coordinate of the next ball.
Is there a more efficient way of doing this?
I'll assume the issue is that the motion of each ball (and the paddle) is controlled by a separate timer. Since there are no guarantees about the exactness of js timers, there are really no guarantees about how many, many timers will interact.
Two broad approaches to correct the problem are:
Instead of using raphaeljs animation primitives, implement a synchronous animation yourself with setTimer, updating the position of each ball (and the paddle) in sync. Then any timer-stutters apply consistently across all elements in your universe.
Use feedback to course-correct the paddle position, e.g. by having a special setTimer that periodically looks at how close the paddle is to where it needs to be, and if necessary calls .stop() on the paddle's animation in order to re-execute with more aggressive parameters, closing the gap.

Categories

Resources