[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Public WebGL] HI-DPI displays and WebGL issues

Sorry to be the person who comes in at the end of a huge thread and replies out of line (I wasn't subscribed to this list).

I wanted to explain what Apple does for canvas elements in the 2d case on our retina (hidpi) and non-retina displays. Unfortunately it is a little confusing, and does not align with what the HTML5 spec recommends. In all cases our goal was to break as little existing content as possible.

As many people have mentioned, the width and height of the canvas element is in CSS pixels. Nothing changes here on hidpi displays.

The key point is the size of the backing store. Typically for best output (ignoring page zoom, viewport, etc) you want a 1:1 match between device pixels and your backing store. So, on a 2x hidpi display, a 400x250 canvas should have a backing store of 800x500 pixels.

Normal resolution devices: you want 400x250 pixels, just ask for that

iOS retina devices: you want 800x500 pixels, you must ask for it

OS X retina devices: you really want 800x500, but you probably asked for 400x250, so we autodouble for you. We also set up the context scale so that drawing code need not change.

Yes, this is confusing. But the end result is that desktop systems usually get better output without any changes in code. There is the risk that you'll sniff for high-resolution, ask for twice the backing-store and then end up with 4x too much on a desktop system. That's why we exposed a webkitBackingStorePixelRatio property - the multiplier between what you request as backing store and what you'll get.

Code probably ends up something like this:

const CANVAS_WIDTH = 400;
const CANVAS_HEIGHT = 250;

function backingScale(ctx) {
  if (window.devicePixelRatio > 1 && ctx.webkitBackingStorePixelRatio < 2) {
    return window.devicePixelRatio;
  return 1;

function init() {
  var canvas = document.getElementById("myCanvas");
  var ctx = canvas.getContext("2d");
  var scale = backingScale(ctx);
  canvas.width = scale * CANVAS_WIDTH;
  canvas.height = scale * CANVAS_HEIGHT;

function draw(ctx) {
  var scale = backingScale(ctx);
  ctx.scale(scale, scale);

  // do stuff


There are three trouble spots:

- get/putImageData. We treat the parameters here as if they are in CSS units. So, against the HTML5 spec, getImageData will always return a buffer the size you requested. We subsample the device resolution into the result. Similarly, putImageData will scale the result up on high-resolution displays. Why did we do this? Despite the example code in the HTML5 spec, nearly every piece of content we found did something like this:

  var buffer = ctx.getImageData(0, 0, w, h);
  for (var i=0; i < w * h; i++) {  // this should be buffer.width * buffer.height
    // do something

If you really want access to the device pixels, use the probably badly named webkitGetImageDataHD and webkitPutImageDataHD. The parameters here are all in backing store coordinates, so it is up to you to manage the transformation.

- The simplest form of drawImage(im, x, y) is problematic. What scale should we use for the output? What if the input is a doubled canvas and the output isn't? I can't remember what we do exactly, but people should use the other forms of this method!!

- canvas.toDataURL() returns an image sized into CSS pixels. We don't return a high-resolution image because we feared that it would potentially break image upload. There is currently no way to request the full image. (you'd have to create another canvas that is 4x the size, draw the original into it… etc. yuck)

As for WebGL, I'm not sure what to recommend. I would prefer it if it was always in the hand of the page author to decide how large the backing store should be. They are the ones who have the best idea what scale the user will view the content. It could be that the page is always zoomed out - why create a bigger backing store in that case?


You are currently subscribed to public_webgl@khronos.org.
To unsubscribe, send an email to majordomo@khronos.org with
the following command in the body of your email:
unsubscribe public_webgl