junov / offscreencanvasanimation Goto Github PK
View Code? Open in Web Editor NEWProposal for driving animations in a Worker for OffscreenCanvas
License: Other
Proposal for driving animations in a Worker for OffscreenCanvas
License: Other
In 5f969dd#diff-fe83e208ade4a4d8de4dc610010e1271R91, there is an example to illustrate how to set up a synchronously blocking main loop:
"Another possibility is to use the async/await syntax:"
async function animationLoop() {
var promise;
do {
//draw stuff
(...)
promise = ctx.commit()
// do post commit work
(...)
} while (await promise);
}
However this does not actually do what readers expect - and especially - it does not solve what @OleksandrChekhovskyi was asking for in https://discourse.wicg.io/t/offscreencanvas-animations-in-workers/1989/15. That is, the comment
"Okay, I think we've got it. Basically, the syntax for getting a commit that throttles by blocking (as Oleksadr suggests) would simply be "await context.commit();". And of course, this is much better than hard-blocking the entire thread."
unfortunately does not solve the use case for a synchronous main loop.
The subtle issue is that an async function
is a function that returns a Promise, and calling await
immediately returns from the calling function (chain of async functions) and continues execution from the first non-async function. This effect breaks the computation model that is required for WebAssembly/Emscripten-based applications that set up their own main loops.
Here is a more concrete example based on OffscreenCanvas prototypes, which attempts to set up a blocking main loop, but fails due to the subtlety:
webgl_modal_loop.html
:
<html><body><canvas id='canvas'>
<script>
var htmlCanvas = document.getElementById("canvas");
var offscreen = htmlCanvas.transferControlToOffscreen();
var worker = new Worker("webgl_worker.js");
worker.postMessage({canvas: offscreen}, [offscreen]);
</script>
</body></html>
webgl_worker.js
:
/*
// This is a simulation of a blocking GL.commit(): (can try as alternative to real GL.commit() if OffscreenCanvas not yet implemented)
function commit() {
var displayRefreshRate = 1; // Simulate a 1Hz display for easy observing
return new Promise(function (resolve, reject) {
setTimeout(function() {
resolve();
}, 1000/displayRefreshRate);
});
}
*/
var gl = null;
async function renderLoop() {
var frameNumber = 0;
// Start our modal rendering loop
for(;;) {
gl.clearColor(performance.now(), performance.now(), performance.now(), performance.now());
gl.clear(gl.COLOR_BUFFER_BIT);
await gl.commit();
// await commit(); // Alternatively to try out simulated gl.commit() in the absence of real thing
console.log('rendered frame ' + frameNumber++);
if (frameNumber > 10) break; // Stop test after 10 frames to not flood the browser
}
}
function init(evt) {
console.log('init');
// Startup initialization for the application
var canvas = evt.data.canvas;
gl = canvas.getContext("webgl");
}
function runGame() {
console.log('runGame');
renderLoop();
}
function deinit() {
console.log('deinit');
gl = null; // tear down
}
onmessage = function(evt) {
// Game "main": init, run loop, and deinit
init(evt);
runGame();
deinit();
};
The expectation from a synchronously blocking execution is that the above application should print out
init
runGame
rendered frame 0
rendered frame 1
rendered frame 2
rendered frame 3
rendered frame 4
rendered frame 5
rendered frame 6
rendered frame 7
rendered frame 8
rendered frame 9
rendered frame 10
deinit
but instead, running the page will print out
init
runGame
deinit
rendered frame 0
<throw since gl is null>
This is because the onmessage
function will continue executing immediately after the the await gl.commit();
is called, and deinit()
will be called, breaking down the synchronous programming model.
I am currently implementing OffscreenCanvas support to Emscripten, and trying to figure out how to implement vsync synchronization when a Worker is rendering via OffscreenCanvas. In the absence of a GL.commit(blockUntilVsyncFinished=true)
type of API or similar help from the OffscreenCanvas spec, my current thinking is to set up a requestAnimationFrame
loop in the main browser thread, and use that to ping "vsync finished" events to a Worker, via SharedArrayBuffer. This will work if OffscreenCanvas based rendering is guaranteed to still allow observing "proper" requestAnimationFrame
synchronization on the main browser thread, though I am not sure if OffscreenCanvas currently says anything about this?
I'm hoping OffscreenCanvas allows one WebGL context to be used to efficiently update multiple canvases and it's not clear to me how that is solved in the current proposal.
Unrelated to the proposal two solutions come to mind.
A callback for each canvas
In this case when you get the callback the WebGLContext is setup to render to current canvas
A way to manually bind a WebGLContext to any canvas.
Note that currently MDN lists code like this as the way to draw to multiple canvases
var one = document.getElementById("one").getContext("bitmaprenderer");
var two = document.getElementById("two").getContext("bitmaprenderer");
var offscreen = new OffscreenCanvas(256, 256);
var gl = offscreen.getContext('webgl');
// ... some drawing for the first canvas using the gl context ...
// Commit rendering to the first canvas
var bitmapOne = offscreen.transferToImageBitmap();
one.transferImageBitmap(bitmapOne);
// ... some more drawing for the second canvas using the gl context ...
// Commit rendering to the second canvas
var bitmapTwo = offscreen.transferToImageBitmap();
two.transferImageBitmap(bitmapTwo);
But that seems likely to be super inefficient unless I'm missing something. In order to be able to draw to multiple canvases following the MDN style API you end up needed to set the size your rendering to. In other world you'd have to do this
offscreen.width = widthOfOne; // EXPENSIVE
offscreen.height = heightOfOne; // EXPENSIVE
renderSceneForOne();
var bitmapOne = offscreen.transferToImageBitmap();
one.transferImageBitmap(bitmapOne);
offscreen.width = widthOfTwo; // EXPENSIVE
offscreen.height = heightOfTwo; // EXPENSIVE
renderSceneForTwo();
var bitmapTwo = offscreen.transferToImageBitmap();
two.transferImageBitmap(bitmapTwo);
Where as if you can either bind to the canvas OR get callback then WebGL knows what canvas it's being asked to render to and if every canvas is double buffered there are no expensive operations.
Maybe there are other solutions? Or maybe the current proposal does this already?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.