Maximum WebGL canvas size

Hey all,

If you ever needed to use WebGL to render large HTML5 canvases, you might have noticed, that your image might be pixelated. It definitely happened to me, but thanks to feedback from Globus users, I figured out what the problem was.

It turns out that all browsers (at least at the moment of writing this article), have some form of limiting the size of the render. So they will reduce the size of the render buffer, and then scale the image to the size of the canvas. To make things better JavaScript might not tell you about it either. Everything appears to be fine, but your renders become ugly and pixelated. Worst thing is – you don’t know why.

While working on Globus, I wrote a little piece of JavaScript, that is able to tell you what is the maximum canvas size you can use. determineMaxCanvasSize()is the function you should be most interested in:

function makeGLCanvas()
{
// Get A WebGL context
var canvas = document.createElement('canvas');
var contextNames = ["webgl", "experimental-webgl"];
var gl = null;
for (var i = 0; i < contextNames.length; ++i) { try { gl = canvas.getContext(contextNames[i], { // Used so that the buffer contains valid information, and bytes can // be retrieved from it. Otherwise, WebGL will switch to the back buffer preserveDrawingBuffer: true }); } catch(e) {} if (gl != null) { break; } } if (gl == null) { alert("WebGL not supported.\nGlobus won't work\nTry using browsers such as Mozilla " + "Firefox, Google Chrome or Opera"); // TODO: Expecting that the canvas will be collected. If that is not the case, it will // need to be destroyed somehow. return; } return [canvas, gl]; } // From Wikipedia function gcd(a,b) { a = Math.abs(a); b = Math.abs(b); if (b > a) {var temp = a; a = b; b = temp;}
while (true) {
if (b == 0) return a;
a %= b;
if (a == 0) return b;
b %= a;
}
}

function isGlContextFillingTheCanvas(gl) {
return gl.canvas.width == gl.drawingBufferWidth && gl.canvas.height == gl.drawingBufferHeight;
}

// (See issue #2) All browsers reduce the size of the WebGL draw buffer for large canvases
// (usually over 4096px in width or height). This function uses a varian of binary search to
// find the maximum size for a canvas given the provided x to y size ratio.
//
// To produce exact results, this function expects an integer ratio. The ratio will be equal to:
// xRatio/yRatio.
function determineMaxCanvasSize(xRatio, yRatio) {
// This function works experimentally, by creating an actual canvas and finding the maximum
// value, the browser allows.
[canvas, gl] = makeGLCanvas();

// Reduce the ratio to minimum
gcdOfRatios = gcd(xRatio, yRatio);
[xRatio, yRatio] = [xRatio/gcdOfRatios, yRatio/gcdOfRatios];

// if the browser cannot handle the minimum ratio, there is not much we can do
canvas.width = xRatio;
canvas.height = yRatio;

if (!isGlContextFillingTheCanvas(gl)) {
throw "The browser is unable to use WebGL canvases with the specified ratio: " +
xRatio + ":" + yRatio;
}

// First find an upper bound
var ratioMultiple = 1; // to maintain the exact ratio, we will keep the multiplyer that
// resulted in the upper bound for the canvas size
while (isGlContextFillingTheCanvas(gl)) {
canvas.width *= 2;
canvas.height *= 2;
ratioMultiple *= 2;
}

// Search with minVal inclusive, maxVal exclusive
function binarySearch(minVal, maxVal) {
if (minVal == maxVal) {
return minVal;
}

middle = Math.floor((maxVal - minVal)/2) + minVal;

canvas.width = middle * xRatio;
canvas.height = middle * yRatio;

if (isGlContextFillingTheCanvas(gl)) {
return binarySearch(middle + 1, maxVal);
} else {
return binarySearch(minVal, middle);
}
}

ratioMultiple = binarySearch(1, ratioMultiple);
return [xRatio * ratioMultiple, yRatio * ratioMultiple];
}

You can also view it in a fiddle: https://jsfiddle.net/1sh47wfk/1/

Posted in Map projections.