HandlingHighDPI: Difference between revisions

From WebGL Public Wiki
Jump to navigation Jump to search
Updated link to point to newer location
No edit summary
Line 6: Line 6:


==Resizing the Canvas==
==Resizing the Canvas==
On High DPI displays browsers will automatically upscale most content to ensure that it appears the right size on screen, which is done behind the scenes to prevent the majority of web sites from breaking. In the case of WebGL content, this causes the canvas to render at it's usual resolution and then upscale to fit the on-screen canvas bounds. The practical effect of this is that an unmodified WebGL application will appear to be rendering at a lower-than-native resolution, which can introduce undesired aliasing.


To avoid this developers should scale the canvas width and height by the value of window.devicePixelRatio during setup.  
It's important to note that canvas elements, like most graphics elements have 2 sizes.
 
* the size they are displayed in the page
* the size of their content
 
For a canvas element, the size of the content or drawingBuffer is determined by the width and height attributes of the canvas. The display size is determined by the CSS attributes applied to the canvas. For example:


<source lang="javascript">
<source lang="javascript">
var desiredWidthPixels = 800;
<canvas width="111" height="222" style="width: 333px; height: 444px;"></canvas>
var desiredHeightPixels = 600;
</source>
 
Defines a canvas that has a drawingBuffer, its content, of size 111x222 device pixels but is displayed at 333x444 CSS pixels.
 
On High DPI displays browsers will automatically upscale most content to ensure that it appears the right size on screen, which is done behind the scenes to prevent the majority of web sites from breaking. In the case of WebGL content, this causes the canvas to render at its usual resolution and then upscale to fit the canvas's display size. The practical effect of this is that an unmodified WebGL application may appear to be rendering at a lower-than-native resolution, which can introduce undesired aliasing.
 
To avoid this developers can scale the canvas width and height by the value of '''window.devicePixelRatio'''.
 
<source lang="javascript">
var desiredWidthInCSSPixels = 800;
var desiredHeightInCSSPixels = 600;


var canvas = document.getElementById("myCanvas");
var canvas = document.getElementById("myCanvas");
canvas.width = desiredWidthPixels * window.devicePixelRatio;
 
canvas.height = desiredHeightPixels * window.devicePixelRatio;
// set the display size of the canvas.
canvas.style.width = desiredWidthInCSSPixels + "px";
canvas.style.height = desiredHeightInCSSPixels + "px";
 
// set the size of the drawingBuffer
var devicePixelRatio = window.devicePixelRatio || 1;
canvas.width = desiredWidthInCSSPixels * devicePixelRatio;
canvas.height = desiredHeightInCSSPixels * devicePixelRatio;


var gl = canvas.getContext("webgl");
var gl = canvas.getContext("webgl");
Line 22: Line 43:


The devicePixelRatio describes the ratio between CSS pixels and native device pixels. On most desktops and laptops with low DPI displays devicePixelRatio is 1, which means nothing will be scaled. On devices like the Retina Macbook Pro or Galaxy Nexus the devicePixelRatio is 2. These values aren't required to be integers, however: On the Nexus One the devicePixelRatio reports as 1.5.
The devicePixelRatio describes the ratio between CSS pixels and native device pixels. On most desktops and laptops with low DPI displays devicePixelRatio is 1, which means nothing will be scaled. On devices like the Retina Macbook Pro or Galaxy Nexus the devicePixelRatio is 2. These values aren't required to be integers, however: On the Nexus One the devicePixelRatio reports as 1.5.
Another method is to ask the browser what size the canvas is displayed and using that to set the size of the drawingBuffer.
<source lang="javascript">
<style>
#theCanvas {
    width: 50%;
    height: 50%;
}
<style>
<canvas id="theCanvas"></canvas>
<script>
window.onload = main();
function main() {
  var canvas = document.getElementById("theCanvas");
  var devicePixelRatio = window.devicePixelRatio || 1;
  // set the size of the drawingBuffer based on the size it's displayed.
  canvas.width = canvas.clientWidth * devicePixelRatio;
  canvas.height = canvas.clientHeight * devicePixelRatio;
  var gl = canvas.getContext("webgl");
  ...
}
</script>
</source>
Several samples make these adjustments including the [https://www.khronos.org/registry/webgl/sdk/demos/google/san-angeles/index.html san angeles demo], the [https://www.khronos.org/registry/webgl/sdk/demos/google/shiny-teapot/index.html teapot demo], and the [https://www.khronos.org/registry/webgl/sdk/demos/google/particles/index.html particles demo]


==Handling Input==
==Handling Input==
Line 28: Line 78:
<source lang="javascript">
<source lang="javascript">
canvas.addEventListener("mousemove", function(e) {
canvas.addEventListener("mousemove", function(e) {
   var canvasX = (e.pageX - canvas.offsetLeft) * window.devicePixelRatio;
  var devicePixelRatio = window.devicePixelRatio || 1;
   var canvasY = (e.pageY - canvas.offsetTop) * window.devicePixelRatio;
   var canvasX = (e.pageX - canvas.offsetLeft) * devicePixelRatio;
   var canvasY = (e.pageY - canvas.offsetTop) * devicePixelRatio;
   // Perform some operation with the transformed coordinates
   // Perform some operation with the transformed coordinates
}, false);
}, false);
</source>
</source>
==Rational==
Why doesn't the browser automatically make the drawingBuffer the size needed so that 1 pixel in the drawingBuffer equals 1 pixel displayed. The reason is the WebGL API has several features which are always in devices pixels. If the browser automatically created a drawingBuffer of a different size than requested many programs would break.
Examples include but are not limited to '''lineWidth''', '''scissor''', '''viewport''', '''gl_PointSize''', '''gl_FragCoord''', '''copyTexImage2D''', '''copyTexSubImage2D'''.

Revision as of 01:28, 19 October 2012

Handling High DPI (Retina) displays in WebGL

There are an increasing number of devices on the market that have displays with very high pixel densities (DPI), such as the Macbook Pro with Retina display. WebGL applications can take advantage of the higher resolution these devices provide but doing so requires some code changes to the application itself. Fortunately these changes are minor and generally non-disruptive, which makes it easy to prepare your WebGL code for these new devices.

You can see a demo highlighting the difference accounting for High DPI displays can make at this location. (The two meshes will only appear different on a high DPI display.)

Resizing the Canvas

It's important to note that canvas elements, like most graphics elements have 2 sizes.

  • the size they are displayed in the page
  • the size of their content

For a canvas element, the size of the content or drawingBuffer is determined by the width and height attributes of the canvas. The display size is determined by the CSS attributes applied to the canvas. For example:

<canvas width="111" height="222" style="width: 333px; height: 444px;"></canvas>

Defines a canvas that has a drawingBuffer, its content, of size 111x222 device pixels but is displayed at 333x444 CSS pixels.

On High DPI displays browsers will automatically upscale most content to ensure that it appears the right size on screen, which is done behind the scenes to prevent the majority of web sites from breaking. In the case of WebGL content, this causes the canvas to render at its usual resolution and then upscale to fit the canvas's display size. The practical effect of this is that an unmodified WebGL application may appear to be rendering at a lower-than-native resolution, which can introduce undesired aliasing.

To avoid this developers can scale the canvas width and height by the value of window.devicePixelRatio.

var desiredWidthInCSSPixels = 800;
var desiredHeightInCSSPixels = 600;

var canvas = document.getElementById("myCanvas");

// set the display size of the canvas.
canvas.style.width = desiredWidthInCSSPixels + "px";
canvas.style.height = desiredHeightInCSSPixels + "px";

// set the size of the drawingBuffer
var devicePixelRatio = window.devicePixelRatio || 1;
canvas.width = desiredWidthInCSSPixels * devicePixelRatio;
canvas.height = desiredHeightInCSSPixels * devicePixelRatio;

var gl = canvas.getContext("webgl");

The devicePixelRatio describes the ratio between CSS pixels and native device pixels. On most desktops and laptops with low DPI displays devicePixelRatio is 1, which means nothing will be scaled. On devices like the Retina Macbook Pro or Galaxy Nexus the devicePixelRatio is 2. These values aren't required to be integers, however: On the Nexus One the devicePixelRatio reports as 1.5.

Another method is to ask the browser what size the canvas is displayed and using that to set the size of the drawingBuffer.

<style>
 #theCanvas {
    width: 50%;
    height: 50%;
}
<style>
<canvas id="theCanvas"></canvas>
<script>
window.onload = main();

function main() {
  var canvas = document.getElementById("theCanvas");
  var devicePixelRatio = window.devicePixelRatio || 1;

  // set the size of the drawingBuffer based on the size it's displayed.
  canvas.width = canvas.clientWidth * devicePixelRatio;
  canvas.height = canvas.clientHeight * devicePixelRatio;

  var gl = canvas.getContext("webgl");
  ...
}
</script>

Several samples make these adjustments including the san angeles demo, the teapot demo, and the particles demo

Handling Input

It's important to note that even though the resolution of the canvas has increased other values provided by the DOM will not adjust accordingly. A good example is mouse or touch input. Coordinates are still delivered in terms of CSS pixels, and thus must be transformed if you need them to line up exactly with the resized canvas pixels. You also need to keep in mind that the canvas position will still be reported in CSS pixels, so adjustments to account for element offsets should not use the devicePixelRatio.

canvas.addEventListener("mousemove", function(e) {
   var devicePixelRatio = window.devicePixelRatio || 1;
   var canvasX = (e.pageX - canvas.offsetLeft) * devicePixelRatio;
   var canvasY = (e.pageY - canvas.offsetTop) * devicePixelRatio;
   // Perform some operation with the transformed coordinates
}, false);

Rational

Why doesn't the browser automatically make the drawingBuffer the size needed so that 1 pixel in the drawingBuffer equals 1 pixel displayed. The reason is the WebGL API has several features which are always in devices pixels. If the browser automatically created a drawingBuffer of a different size than requested many programs would break.

Examples include but are not limited to lineWidth, scissor, viewport, gl_PointSize, gl_FragCoord, copyTexImage2D, copyTexSubImage2D.