Chrome warning willReadFrequently attribute set to true - javascript

Chrome keep printing this warning: "Canvas2D: Multiple readback operations using getImageData are faster with the willReadFrequently attribute set to true.". I checked the code, where the warning triggers, and you can see I set the willReadFrequently attribute to true. What could be the problem? There was this warning in other places, but there the willReadFrequently attribute solved it.
The problem in Chrome 104-108 exists for sure. Btw, I am in a WebWorker. Could this be a chrome bug?
const offdesireCtx = offDesire.getContext("2d", { willReadFrequently: true });
if (!offdesireCtx) {
throw new Error("Desired OffscrenCanvas ctx undefined");
}
const offGetCtx = offGet.getContext("2d", { willReadFrequently: true });
if (!offGetCtx) {
throw new Error("Get OffscrenCanvas ctx undefined");
}
var imgd = offdesireCtx.getImageData(0, 0, tileSize, tileSize), pix = imgd.data; //Warning triggers
var imgdGet = offGetCtx.getImageData(0, 0, tileSize, tileSize), pixGet = imgdGet.data; //Warning triggers

As MDN says about willReadFrequently:
This will force the use of a software (instead of hardware accelerated) 2D canvas and can save memory when calling getImageData() frequently.
This means the canvas must be created, drawn to, and read from entirely on the CPU. Calling getContext provides a handle to the canvas buffer on the GPU by default, and if that call ever occurs with this canvas earlier on, that data is already on the GPU and it would have to be copied back to the CPU, defeating the goal of the performance warning.
I found in my case, I was creating the canvas and writing to it in one function, then returning the canvas. Later in my code, the result of that function call took the same canvas and created another context. It applied the
{ willReadFrequently: true } options argument to that call, but that was the second time getContext was called for this canvas. That means the texture buffer was already living on the GPU by this point and the second getContext call was ignoring the willReadFrequently suggestion as the data was already on the GPU.
So you need to trace back to where your canvas is first created, and then where its first getContext is called and drawn to. Subsequent canvas.getContext("2d", { willReadFrequently: true }) calls are too late to matter (and are likely even fine to omit the option). Think about when the texture buffer is being created and trace through your data flow to make sure it all lives on the CPU since its inception.

Related

How to tell if 2D context on offscreen canvas is available?

I'm trying to use offscreen canvas for rendering in webworker, which works perfectly fine in Chrome, but it seems that 2D context is not yet available in Firefox. How do I know that in advance? By the time I get to creating the context, it is already too late and the original canvas is no longer functional for drawing.
Basically what I'm doing
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({canvas: offscreen});
...
// in worker
canvas.getContext("2d") // exception!
// but there's no fallback now, because we already called transferControlToOffscreen
Edit: somehow it didn't occur to me to create another canvas and make it offscreen before "experimenting" with the main ones. That works, but seems like a hack.
The context returned by OffscreenCanvas.getContext('2d') is an OffscreenCanvasRenderingContext2D instance.
Browsers that will support this call, will have this interface available, so all you need is:
const supports = typeof OffscreenCanvasRenderingContext2D === "function";
console.log(`Your browser ${supports ? "supports" : "doesn't support" } the 2d context of the OffscreenCanvas`);
You have to concatenate both getContext and getContext('2d'):
function isCanvasSupported(){
var elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
}

Try Catch Errors many script components - JavaScript

I have a page that contains many script components (50+) and I am getting an error when using IE at some random instance (doesn't happen in Chrome or Firefox).
"Out of Memory at line: 1"
I've done some google search too and that reveals issues with IE handling things differently to Chrome and FF. I would like to catch this error and know exactly what the cause of that script error is.
What would be the best way to use a global try-catch block on that many script components? All these script components are on the same page. Looking forward to your suggestions.
You might want to try window.onerror as a starting point. It will need to be added before the <script> tags that load the components.
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror
If that fails, you might try reducing the components loaded by half until the error no longer occurs. Then, profile the page (you may have to reduce further due to the demand of profiling). Look for a memory leak as #Bergi suggested. If there is in fact a leak, it will likely occur in all browsers, so you can trouble-shoot in Chrome, as well.
If that still fails to yield anything interesting, the issue may be in one particular component that was not in the set of components you were loading. Ideally, anytime that component is included you see the issue. You could repeatedly bisect the loaded components until you isolate the culprit.
Finally, forgot to mention, your home-base for all of this should be the browser's developer tools, e.g. Chrome dev tools, or if it is unique to Edge, Edge debugger.
And FYI, Edge is the browser that crashes, but that does not mean the issue is not present in Chrome or FF.
One important thing that is missing in your question is if the error happens during the page loading or initialization or if it happens after some time while you browse the page.
If it's during loading or initialization, it's probably caused by the fact that your page contains too many components and uses much more memory than the browser is willing to accept (and IE is simply the first one to give up).
In such case there is no helping but reduce the page size. One possible way is to create only objects (components) that are currently visible (in viewport) and as soon as they get out of the viewport remove them from JS and DOM again (replacing the with empty DIVs sized to the size of the components).
In case the error happens while browsing the page, it may be caused by a memory leak. You may use Process Explorer to watch the memory used by your browser and check if the memory constantly increase - which would indicate the memory leak.
Memory leak in Internet Explorer may happen because it contains 2 separate garbage collectors (aka GC): one for DOM objects and other for JS properties. Other browsers (FF, Webkit, Chromium, etc.; not sure about the Edge) contains only one GC for both DOM and JS.
So when you create circular reference between DOM object and JS object, IE's GC cannot correctly release the memory and creates a memory leak.
var myGlobalObject;
function SetupLeak()
{
myGlobalObject = document.getElementById("LeakDiv");
document.getElementById("LeakDiv").expandoProperty = myGlobalObject;
//When reference is not required anymore, make sure to release it
myGlobalObject = null;
}
After this code it seems the LeakDiv reference was freed but LeakDiv still reference the myGlobalObject in its expandoProperty which in turn reference the LeakDiv. In other browsers their GC can recognize such situation and release both myGlobalObject and LeakDiv but IE's GCs cannot because they don't know if the referenced object is still in use or not (because it's the other GC's responsibility).
Even less visible is a circular reference created by a closure:
function SetupLeak()
{
// The leak happens all at once
AttachEvents( document.getElementById("LeakedDiv"));
}
function AttachEvents(element)
{
//attach event to the element
element.attachEvent("onclick", function {
element.style.display = 'none';
});
}
In this case the LeakedDiv's onclick property references the handler function whose closure element property reference the LeakedDiv.
To fix these situations you need to properly remove all references between DOM objects and JS variables:
function FreeLeak()
{
myGlobalObject = null;
document.getElementById("LeakDiv").expandoProperty = null;
}
And you may want to reduce (or remove completely) closures created on DOM elements:
function SetupLeak()
{
// There is no leak anymore
AttachEvents( "LeakedDiv" );
}
function AttachEvents(element)
{
//attach event to the element
document.getElementById(element).attachEvent("onclick", function {
document.getElementById(element).style.display = 'none';
});
}
In both cases using try-catch is not the option because the Out of memory may happen on random places in code and even if you find one line of code where it's happened the next time it may be elsewhere. The Process Explorer is the best chance to find the situations when the memory increase and and trying to guess what may be causing it.
For example: if the memory increase every time you open and close the menu (if you have one) then you should look how it's being opened and closed and look for the situations described above.
You could check your localStorage before and after any components called.
Something like:
function getLocalStorage() {
return JSON.stringify(localStorage).length;
}
function addScript(src, log) {
if(log){
console.log("Adding " + src + ", local storage size: " + getLocalStorage());
}
var s = document.createElement( 'script' );
s.setAttribute( 'src', src );
document.body.appendChild( s );
}
function callFunction(func, log){
if(log){
console.log("Calling " + func.name + ", local storage size: " + getLocalStorage());
}
func();
}
try {
addScript(src1, true);
addScript(src2, true);
callFunction(func1, true);
callFunction(func2, true);
}
catch(err) {
console.log(err.message);
}
I hope it helps you. Bye.

How to delete the state stack for a JavaScript canvas rendering context?

I was recently working with <canvas> in JavaScript, and discovered the possibility to create a really bad "memory leak" (more like a memory explosion). When working with the canvas context, you have the ability to do context.save() to add the drawing styles to the "state stack" and context.restore() to remove it. (See the documentation for the rendering context on MDN.)
The problem occurs when you happen to continually save to the state stack without restoring. In Chrome v50 and Firefox v45 this seems to just take up more and more private memory, eventually crashing the browser tab. (Incidentally the JavaScript memory is unaffected in Chrome, so it's very hard to debug this with the profiler/timeline tools.)
My question: How can you clear out or delete the state stack for a canvas context? With a normal array, you would be able to check on the length, trim it with splice or simply reset is back to empty [], but I haven't seen a way to do any of this with the state stack.
[I].. discovered the possibility to create a really bad "memory leak"
This is technically not a memory leak. A leak would be to allocate memory and loose the pointer to it so it could not be freed. In this case the pointer is tracked but the memory not released.
The problem occurs when you happen to continually save to the state stack without restoring.
That is to be expected. Allocating memory without freeing it will accumulate allocated memory blocks.
How can you clear out or delete the state stack for a canvas context?
The only way is to either restore all saved states, or to reset the context by setting some size to the canvas element (ie. canvas.width = canvas.width).
It's also safe to call restore() more times than save() (in which case it just returns without doing anything) so you could in theory run it through a loop of n number of iterations. This latter would be more in the bad practice category though.
But with that being said: if there is a mismatch in numbers of save and restore when it's suppose to be equal, usually indicates a problem somewhere else in the code. Working around the problem with a reset or running multiple restores in post probably will only contribute to cover up the actual problem.
Here's an example on how to track the count of save/restore calls -
// NOTE: this code needs to run before a canvas context is created
CanvasRenderingContext2D.prototype.__save = CanvasRenderingContext2D.prototype.save;
CanvasRenderingContext2D.prototype.__restore = CanvasRenderingContext2D.prototype.restore;
// Our patch vectors
CanvasRenderingContext2D.prototype.__tracker = 0;
CanvasRenderingContext2D.prototype.save = function() {
this.__tracker++;
console.log("Track save:", this.__tracker);
this.__save()
}
CanvasRenderingContext2D.prototype.restore = function() {
this.__tracker--;
console.log("Track restore:", this.__tracker);
this.__restore()
}
// custom method to dump status
CanvasRenderingContext2D.prototype.trackstat = function() {
if (this.__tracker)
console.warn("Track stat:", this.__tracker);
else
console.log("Track stat: OK");
}
var ctx = document.createElement("canvas").getContext("2d");
ctx.save(); // do a couple of save()s
ctx.save();
ctx.restore(); // single restore()
ctx.trackstat(); // should report mismatch of 1
ctx.restore(); // last restore()
ctx.trackstat(); // should report OK

requestAnimationFrame called right before the end of the frame?

I've been experimenting with jank-free rendering of complex scenes on HTML5 canvas. The idea is to split rendering into multiple batches with each batch taking a maximum of, say 12 ms, so that the concurrently running animations (very cheap to execute) are not interrupted.
Conceptually, batch-rendering is implemented like this:
function draw(ctx) {
var deadline = window.performance.now() + 12; // inaccurate, but enough for the example
var i = 0;
requestAnimationFrame(function drawWithDeadline() {
for (; i < itemsToRender.length; i++) {
if (window.performance.now() >= deadline) {
requestAnimationFrame(drawWithDeadline);
return;
}
var item = itemsToDraw[i];
// Draw item
}
});
}
The complete code is in this JSFiddle: https://jsfiddle.net/fkfnjrc2/5/. The code does the following things:
On each frame, modify the CSS transform property of the canvas (which is an example of the concurrently-running fast-to-execute animation).
Once in a while, initiate re-rendering of the canvas in the batched fashion as shown above.
Unfortunately, I'm seeing horrible janks exactly at the times when canvas contents is re-rendered. What I don't seem to be able to explain is what the Chrome Developer Tools timeline looks like:
The dropped frames seem to be caused by the fact that the requestAnimationFrame is not called right at the start of the frame, but towards the end of the ideal 16ms period. If the callback started right after the previous frame completed rendering, the code would most likely complete in time.
Rendering to an off-screen canvas (https://jsfiddle.net/fkfnjrc2/6/) and then copying the complete image to the screen canvas helps a little, but the janks are still there with exactly the same characteristics (delayed execution of rAF callback).
What is wrong with that code? Or maybe something's wrong with my browser / machine? I'm seeing the same behaviour on Chrome (49.0.2623.112) on Windows 10 and Mac OS.
The issues seem to be caused by Chrome's specific requestAnimationFrame callback scheduling. I've filed a bug to track this, it contains some simpler reproduction code examples.

Reference Error: 'Variable_Name' is undefined - HTML5 Canvas Image

Background: For a university project I have been tasked with creating a Flappy Bird clone. Originally it worked fine, then as I began adding more images, they stopped loading within Chrome. Firefox was fine (unless I did a fresh reload, but every subsequent reload was fine).
I was making a new 'Sprite' object for each image and within that sprite object's initialiser I would create an Image object and assign the src. I eventually realised this was the problem, so I rearranged my structure like this:
var ent_player_img = new Image();
ent_player_img.onload = function()
{
window.ent_player = new Sprite(ent_player_img);
};
ent_player_img.src = "../images/ent_player.png";
I would assign ent_player.img to the new Image object within the constructor. However, this was throwing reference errors within my game loop.
To combat this I put all of the functions in my game loop that called these objects within an if statement.
if (typeof ent_player !== undefined)
{
//Draw image
}
And yet I was still getting the errors. Inside the if statement I used a console.log saying typeof ent_player which returned as undefined.
Very puzzled as to why this code was still running and causing issues, I used my debugger of choice (Firebug).
I put a breakpoint on the if statement, however, whenever I step through the code with the debugger it works perfectly. Correct outputs, etc. Then as soon as I try it again without the debugger, same issue.
Can anyone give me an idea as to why this is happening and how I can fix it?
Thank you very much.
Edit: Sprite constructor looks like this:
function Sprite(image)
{
this.img = image;
this.height = this.img.height;
this.width = this.img.width;
this.x = 0;
this.y = 0;
}

Categories

Resources