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.
Related
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.
I have a UI where I need animations to run smoothly. Every so often, I need to do a semi-large data calculation that makes the animation skip until it is this calculation is completed.
I am trying to get around this by making the data calculation async with setTimeout. Something like setTimeout(calcData(), 0);
The whole code is something like this (simplified):
while (animating) {
performAnimation();
if (needCalc) {
setTimeout(calcData(), 0);
}
}
But I still get a skip in the animation. It runs smoothly when I do not need to do any data calculations. How can I do this effectively? Thanks!
You are seeing the skip because only one javascript thread is run at once. When something is done asynchronously the javascript engine puts it a queue to be ran later, then finds something else to execute. When something in the queue needs to be done the engine will pull it out and execute it, blocking all other operations until it completes.The engine then pulls something else out of its queue to execute.
So if you want to allow your render to run smoothly you must break up your calculation into multiple async calls, allowing the engine to schedule the render operation in between calculations. This is easy to accomplish if you are just iterating over a array, so you can do something like:
var now=Date.now;
if(window.performance&&performance.now){//use performace.now if we can
now=performance.now;
}
function calculate(){
var batchSize=10;//If you have a exceptionally long operation you may want to make this lower.
var i=0;
var next=function(){
var start=now();
while(now()-start<14){//14ms / frame
var end=Math.min(i+batchSize,data.length);
for(;i<end;i++){//do batches to reduce time overhead
do_calc(data[i]);
}
}
if(i<data.length) setTimeout(next,1)//defer to next tick
};
next();
}
calculate();
function render(){
do_render_stuff();
if(animating) {
requestAnimationFrame(render);//use requestAnimationFrame rather then setTimeout for rendering
}
}
render();
Better yet, if you can, you should use WebWorkers which work in a different thread, completely separate from the main js engine. However you are stuck with this if you need to do something you cant do in a WebWorker, such as manipulating the DOM tree.
Firstly, let's talk what's going on in your code:
while (animating) {
performAnimation();
if (needCalc) {
// it should be setTimeout(calcData, 0);
setTimeout(calcData(), 0);
}
}
In line setTimeout(calcData(), 0); really you don't defer calling of calcData function, you call it, because you use () operator after function name.
Secondly, lets think, what's going on when you really make defer calling for calcData in the code above: commonly JavaScript is running in one thread, so, if you have code like this:
setTimeout(doSomething, 0);
while (true) {};
doSomething will never be called, because interpreter of javascript executes while loop forever and it hasn't "free time" to execute other things (even UI) . setTimeout - just say to schedule execution of doSomething when interpreter will be free and it's time to execute this function.
So, when browser executes javascript function, all other stuff become freezing.
Solution:
If you have big data that you need to process, maybe it would be better to make calculations on backend and after send results to frontend.
Usually when you need to make some calculation and render results it's better to use requestAnimationFrame than while loop. Browser will execute function passed in requestAnimationFrame as soon as possible, but also you give browser a time to handle other events. You can see smooth redrawing using requestAnimationFrame for game (step-by-step tutorial here).
If you really want to process huge amount of data at frontend part and you want to make ui works smooth, you can try to use WebWorkers. WebWorkers look like threads in JavaScript, you need to communicate between main UI "thread" and WebWorker by passing messages from one to another and back and calculations on WebWorker don't affect UI thread.
Mostly, your problem boils down to your incorrect usage of setTimeout()
setTimeout(calcData(), 0);
The first argument to setTimeout is a REFERENCE to the function that you wish to call. In your code, you are not referencing the calcData function, you are invoking it because you've included () after the function name.
Second, the fact that you've put 0 for the delay does not mean you will have a 0 second delay before the function runs. JavaScript runs in a single threaded context. The setTimeout function is placed in a queue and executed when the JavaScript engine is available, but no sooner than a minimum of 10ms or the amount you specify (whichever is less).
Realistically, your line should be:
setTimeout(calcData(),10);
I have some pretty simple 3D objects (basically THREE.Sphere which is a parent of 1-30 other THREE.Spheres, with RGB color and no texture) to display in somewhat real time animation.
The rendering is done in no time indeed, but I have some simple iterative calculation (for-loops) that are disturbing my display capabilities.
The rendering itself is not the problem, but the computation for the next frame vertices is what causing the pain
Meaning, when I just run the script, I can see that the for-loops are taking too much time to compute, and just then it goes to the rendering section which is done in no time.
I was thinking of dealing with this problem in a parallel computing manner- running a thread (worker, whatever it is called in JS) that would calculate the for-loop, but I thought that considering my lack of experience in computer graphocs, perhaps there is a more "graphic"ed way of doing so. Meaning a more elegant/performance-improved/natural way of dealing with such a fundamental problem of computer graphics design.
This is the basic idea I am using to do long slow calculations that don't get caught by the watchdog timers and bog down the browser or the render rate.
The basic idea is that you use a javascript generator that allows you to yield in the middle of a computation, and continue later on. Then, I run the generator pump on a timeout chain.
The key to this method is the "yield" operator that saves the function state and returns awaiting a future call to the .next() function. (btw this demo code might have to be re arraged to handle firefoxs forward references)
//function Chain represents the slow code
function *Chain()
{
for(i=0;i<1000000;i++) //this represents a long slow calculation
{
if(i%100==0) //on occassion, yield
yield;
}
}
console.log("starting chain");
gChain=Chain(); //startup and get the next generator pointer
timeout = setTimeout(function () { ChainStart(); }, 1);
//function ChainStart is a pump that runs the generator using a timeout chain so other threads can run
function ChainStart(){
if(gChain.next().done){
clearTimeout(timeout);
console.log("endingchain");
}
else{
clearTimeout(timeout);
timeout = setTimeout(function () { ChainStart(); }, 1);
}
}
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.
I am working on an HTML5 game which uses a lot of images.
However, sometimes the game does not load properly and throws some error messages that suggest that some images have not been loaded correctly.
I use a preloading function, incrementing a variable before starting to set the src of an image and decrementing it in the onload() function. Only when this variable reached 0, i start drawing. Still sometimes I (and other users) see errors and the game doesnt load. Most of the time it works though.
Now I wonder... technically this should not be possible. Does the call of the onload function guaratee the image is loaded ? Because I feel it doesnt.
Here the code although I dont think it matters:
var ressourcesToLoad = 1;
// all the loadImage() calls
// ...
ressourceLoaded();
function ressourceLoaded()
{
ressourcesToLoad--;
// if(ressourcesToLoad == 0) start main loop
}
function loadImage(imgFile)
{
ressourcesToLoad++;
var img = new Image();
img.onload = ressourceLoaded();
img.src = imgFile;
return img;
}
Yes, BUT as far as I remember:
1 In some browsers it can fire twice in a row for the same image
2 In some browsers it doesn't fire when image is loaded from cache
3 I'm not sure if it fires when server returns 404 for the image
And probably you should start loading next image only after loading previous. If you have a lot of big images on the same domain and start them loading simultaneously, the "two connections per page" rule can break something for you.
P.S.: By some "some browsers" I mean "some browsers or their outdated versions".
No the complete property tells you more accurate if the image has finished loading. Unfortunately there is no event that will be triggered when loading is done. One way could be to poll this property until it is set to true.