Is anyone else seeing a major memory leak in the Chromium GPU process on large HTML canvases?
I have code such as the following which uses the standard technique to "sharpen" a canvas on high DPI displays by scaling it:
var dpiScale = ...; // e.g. 2, 2.25, 3
var width = 1500, height = 800;
canvas.width = width * dpiScale;
canvas.style.width = width + "px";
canvas.height = height * dpiScale;
canvas.style.height = height + "px";
canvas.getContext("2d").scale(dpiScale, dpiScale);
What I am seeing is an epic memory leak in the Chromium GPU process if there are frequent updates to the canvas (where frequent is a few times per second, potentially much more if there is user interaction such as scrolling or pinch-and-zoom which repeatedly and quickly changes the canvas contents). The GPU process allocates memory in large quantities, never frees it, and quite quickly falls over.
The issue appears to have the following characteristics:
- Applies to Chrome and Edge, but not to Safari or Firefox (hence I'm classing this as "Chromium")
- Seems to be a recent change, or at least to have worsened significantly in recent versions, or else I believe I'd've spotted it before
- Seems to be related to the (scaled) canvas size; a threshold beyond which problems start. Removing the scaling (
dpiScale = 1
) removes the problem. Using scaling but with a smaller canvas also seems to remove the problem. - Doesn't appear to relate to the use of any specific canvas primitives. I've reduced the canvas activity to basics such as moveTo(), lineTo(), stroke(), and fill() and can still observe the problem. I'm not drawing images on the canvas. I've specifically (and unsuccessfully) tried removing all uses of fillText(). Despite the mention of
{willReadFrequently: true}
below, I am not reading from the canvas usinggetImageData()
.
What I find very interesting is that there are two workarounds which are effective:
- Throttle the frequency of canvas updates. Instead of using requestAnimationFrame(), artificially throttle updates so that they occur much less frequently, such as 20fps.
- Use
getContext("2d", {willReadFrequently: true})
The second of these is particularly interesting. The documentation isn't great, but I believe that it's turning off hardware acceleration. To my surprise, there seems to be no real CPU penalty associated with this in the context of the sort of drawing that I'm doing. What happens with this turned on is that the GPU process still starts allocating large amounts of memory during frequent updates (e.g. scrolling), but does then free it when things quieten down again.
I can't absolutely rule out the notion that this is in fact a graphics driver issue rather than a browser issue, or the interaction of Chromium with the graphics driver. I don't seem to be able to replicate the problem on all computers, and it doesn't happen absolutely 100% of the time on my test machine.
While I have the workaround of {willReadFrequently: true}
, I'm wondering if anyone has experienced the same thing, and has:
- Any further insights
- A better workaround than
{willReadFrequently: true}
- A link to a specific canvas action - something which I shouldn't be doing, or could stop doing - which I've overlooked, and is the specific cause of the problem
?