Does canvas have a display event handler? - javascript

I am used to using GLUT's PostRedisplay and (App|UI)Kit's setNeedsDisplay methods for requesting a new display event to be posted to the event queue when the app wishes to redraw a scene. I haven't found a counterpart with HTML5's Canvas element. For example, I am currently just calling my display() function directly when I want to redraw to the canvas:
function mouseMove(event) {
if (mouseDrag) {
// update camera position
display();
}
}
The display() function renders the scene (which can be expensive):
function display() {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// issue WebGL calls to redraw scene
gl.flush();
}
Most systems I am used to will coalesce multiple display events into one event if multiple rendering requests occur before the last rendering is finished. What I am doing above can be problematic if a long stream of display() calls outpace the rendering speed.
Anyway, what is the correct way to redraw canvas elements effeciently?
You can see the demo app I am working on here (if you have WebGL enabled).
Update : Following the answer below I now use RequestAnimationFrame and use the return value as a flag to indicate that a request has already been made. When the scene is rendered I clear the flag so further requests can be made:
var requestId = 0; // id of current requestAnimationFrame request (0 = no request)
function mouseMove(event) {
if (mouseDrag) {
// update camera position
if (requestId)
requestId = requestAnimationFrame(display);
else
console.log("request already in queued"); // test
}
}
function display() {
requestId = 0;
// redraw scene
}
Noting the log messages I get this does indeed coalesce multiple requests. This seems like a great approach. Are there any other solutions?

You can use requestAnimationFrame(display) to set a callback (in this case, display) to be called at the next repaint.
It's up to you to only call it once when waiting for a repaint (at the repaint, it will do the callback for each call you made to requestAnimationFrame). It's also up to you to request another animation frame if you want to animate continuously.

Related

Is setInterval cleared on scene change Phaser 3?

Are all setIntervals cleared on scene change in Phaser 3? For example, if I have this code:
class ExampleScene extends Phaser.Scene {
constructor () {
super();
}
preload () {
}
create () {
setInterval(() => console.log(true), 1000);
}
update () {
}
}
and I change scenes, will it continue to log true to the console? Is there an alternative in Phaser that doesn't require that I remove all intervals manually?
The short answer is, No. Since setInterval is a javascript function.
For details on the function: here in the documentation on mdn
What you can do is "save" the setInvertal calls/id's in a list and clear them on a specific scene event like, shutdown, pause, destroy ... . When that event fires you can then stop all saved intervals, or so (details to possible Scene events).
This is also considered good practice, since you always should cleanup resources, when leaving a phaser scene.
Example (here with the shutdown event):
...
// setup list for the intervals, that should be cleared later
constructor () {
super();
this.intervals = [];
}
create () {
...
// example of adding an interval, so that I can be cleanup later
this.intervals.push(setInterval(() => console.log(true), 1000));
...
// example listening to the shutdown Event
this.events.on('shutdown', this.stopAllIntervals, this);
}
...
// example "cleanup"-function, that is execute on the 'shutdown' Event
stopAllIntervals(){
for(let interval of this.intervals){
clearInterval(interval);
}
}
...
And now you just hat to call the stopAllIntervalsin the desired event function, when you want to stop them all.
From the offical documenation, on the shutdown Event: ... You should free-up any resources that may be in use by your Scene in this event handler, on the understanding that the Scene may, at any time, become active again. A shutdown Scene is not 'destroyed', it's simply not currently active. Use the DESTROY event to completely clear resources. ...

How to have a processor intensive function update every tick of the metronome and draw to canvas using web workers?

I have a cellular automaton which we can abbreviate like this:
// pretend slow update function called every tick of the metronome
const update = () => {
let i = 0
while (i < 10000) {
console.log(i)
i++
}
}
Then I have in the main thread a Tone.js "loop" running every tick of the metronome:
new Tone.Loop(time => {
// update() in worker
// then draw() in main thread
}, '4n').start(0)
This loop essentially runs every quarter note at 240 BPM. You can approximate it with setTimeout, but with some extra fancy logic around keeping track of the elapsed time.
My question is, what is the architecture to make sure this draws every tick of the beat and doesn't get out of sync?
If I do this in the web worker system, then I am not sure how it will behave:
// main.js
worker.onmessage = () => draw()
new Tone.Loop(time => worker.postMessage('update'), '4n').start(0)
// worker.js
worker.onmessage = () => {
update()
postMessage("draw")
}
Depending on the async nature of how long the postMessage takes in both directions, the draw will come way after the beat potentially.
How do I do this correctly, architecture wise?
Note, the drawing to canvas must happen in the main thread, while the update function of the (multiple instances of) cellular automata must be updated all at once in the worker, for performance. Then there will be a SharedArrayBuffer to read the final computed values in the main.js.
What is the general approach I should take to wire this up?
If I understand correctly you want to prevent frames from intertwining, preserve their order and do not delay them when they're ready, which means you need to skip those who don't respect the order.
You could pass the time back and forth, then use it to conditionally draw the frame.
// main.js
let lastFrameTime
worker.onmessage = ({ data: { time }}) => {
if(time > lastFrameTime) {
draw()
lastFrameTime = time
}
}
new Tone.Loop(time => worker.postMessage({ action: 'update', time }), '4n').start(0)
// worker.js
worker.onmessage = ({ data: { time }}) => {
update()
postMessage({ action: 'draw', time })
}
Edit: You actually need multiple workers per animation
If update takes more than 0.25s, then a callback will be queued in the worker before the previous one finishes, which will delay the next update call, and so on.
So you actually need multiple workers with some kind of round-robin dispatch between them, and use the code above in order to prevent frame intertwining.

Calling Javascript functions in async timing

I'm working on a canvas application that uses 2 web-workers to calculate some frames. You can see below how each worker sends a specific frame back to the main thread.
On the main thread I have an event listener for each worker. They listen for the frame, and when they receive it, they send it to a function called drawFrameToCanvas
This seems simple enough, but my problem is these webworkers can send their data out of order. So worker2 can finish before worker1
The way I thought to fix this problem is like this:
var desiredFrame = 0;
function drawFrameToCanvas(frameData, frameNumber){
if(frameNumber != desiredFrame){ //this is the wrong frame
//wait until it's the correct frame
setTimeout(function(){ drawFrameToCanvas(frame, frameNumber) }, 10);
}
}
However by the time frame 0 is finished, frame 1 has been requested to be drawn several times. So this didn't work. I then thought about making an object that would store the frame data and have the drawFrameToCanvas function call for this object.
However I couldn't think of a good way to do this without storing all the frames in memory as this object would always be referenced and never cleared for garbage collection.
Is there a better way to call javascript functions in these circumstances?
You can create queues for the results, and remove the data from the queues as they're processed to avoid the memory issues you mentioned. Something like this:
var worker1Results = [];
var worker2Results = [];
worker1.onmessage = function(e){
worker1Results.push(e.data);
performActionWhenBothDone();
}
worker2.onmessage = function(e){
worker2Results.push(e.data);
performActionWhenBothDone();
}
function performActionWhenBothDone(){
if (worker1Results.length && worker2Results.length) {
performAction(worker1Results.shift(), worker2Results.shift());
}
}
function performAction(worker1Result, worker2Result){
//drawFrameToCanvas code
}

Is generateMipmap done?

In my app I generate mipmaps for the 6x4096x4096 cube map texture. Next I need to undertake some other changes on that texture that are time dependent. All the drawing is done inside of the requestAnimationFrame's loop.
Depending on the browser, device, etc., sometimes it takes three, sometimes four or even five consecutive frames of the loop to finally generate those mipmaps, and I need to know in which frame exactly mipmaps are already done.
So the question is: how to check in which frame of the requestAnimationFrame's loop mipmaps generated for the "TEXTURE_CUBE_MAP" by the WebGL's command "generateMipmap" are ready? Is there some flag for checking status of the "generateMipmap" completion?
There is no way to find out when generateMipmap will finish or how long it will take. I'd be surprised it takes that long but if the point is that you want to avoid jank one solution would be to make your own mips. This way you can upload them one lod per face per frame or even in
smaller increments.
One other possible solution but probably only works on Chrome is to just guess and wait a few frames with code like
gl.generateMipmap(...);
gl.flush(); // make sure the previous command will be executed
// now do something to wait 4 frames like
var framesToWait = 4;
function render() {
if (framesToWait) {
--framesToWait;
} else {
... call drawArrays ...
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
This might work because chrome is multi-process. I'm not sure what the point would be though.
In WebGL2 you could use a FenceSync object to find out when something finished.
generateMipmap is a "synchronous" call. Whereas browsers can try to optimise it by returning from it immediately, before generating mip-levels is actually done, the first usage of a texture for which generateMipmap was called will make sure, that mip-levels're ready to use. To put it in code:
const texture = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, /* ... */);
gl.generateMipmap(gl.TEXTURE_2D);
gl.uniform1i(someSamplerUniform, 0);
gl.drawArrays(/* ... */); // Here WebGL implementation will make sure that
// mip-levels for tetxure are ready. If they aren't,
// this call will cause blocking.
If you change content of 0 mip-level of the texture, you need to call generateMipmap again.
Here's a bit more info about this behaviour.

Force canvas redraw from recursive function

I want to create a custom progress bar. But I'm using a recursive function that updates data for it, I can't update canvas that is in my progress bar.
Here are parts of my code:
var length = 0;
recursiveFunction = function(){
length++;
updateLength(length);
//some work
recursiveFunction();
}
updateLength = function(length){
setLength(length);
}
setLength(length){
var c = document.getElementById(canvas);
var ctx = c.getContext("2d");
ctx.fillStyle = "#fc0";
ctx.fillRect(0, 0, length, 10);
}
All these functions are in different JS files and in different classes.
Problem is that canvas doesn't redraw in setLength function.
This has to do with JavaScript being single-threaded and can only do one thing at the time. As long as that code is running everything else such as updating to screen has to wait.
To get around it you can introduce some asynchronicity to your code pausing the code enough to allow the screen to be updated.
For example (note: this change alone will probably not work without performing other changes):
recursiveFunction = function(){
length++;
updateLength(length);
//some work
requestAnimationFrame(recursiveFunction); // makes call async
}
The function will now end but an event is added for future use (usually 16.7ms in this case). In the mean time the canvas can be updated to screen.
But not without problems of course. Context is changing and since it's a recursive function you may want to pass in arguments. Although not shown in the post which ones if any, you could instead of requestAnimationFrame() use setTimeout() which allow you to pass in arguments. You can also use bind() if you're depending on context (i.e. this).
// example of bind
requestAnimationFrame(recursiveFunction.bind(this));
The setTimeout() can take more arguments than delay:
setTimeout(recursiveFunction.bind(this), 17, arg1, arg2, arg3, ...);
An alternative to this is to use Web Workers. This will allow you to run your code as fast as possible at a separate thread, and once in a while send back a message to main host containing progress so far which will allow canvas to be updated independently. This is the recommended path if the function is long-running. Web workers has good support but won't work with older IE or Opera Mini.

Categories