deviceNormalPixelRatio: proposal for zoom-independent devicePixelRatio for HD Retina games

This is a proposal for addition of a zoom-independent version of window.devicePixelRatio to HTML5.

The issue

Before Firefox 18 and Chrome 25, to make a 3D (WebGL) game drawn in HD, we could set the scale in <meta name="viewport"> to 1 and multiply the size of 2D things (HUD elements, menus) by window.devicePixelRatio.

However, since these versions of the browsers, devicePixelRatio started to take browser zoom level into account.

While the change works perfectly for loading of high-resolution versions of <img> images and background-images, it has been impossible to draw HUD elements in HD 3D games with correct sizes since that.

Glossary of this proposal

DPR – zoom-dependent window.devicePixelRatio.
Old DPR – devicePixelRatio behavior before Firefox 18 and Chrome 25.
New DPR – devicePixelRatio behavior since Firefox 18 and Chrome 25.

Issue details

With the old DPR, when <meta name="viewport"> scale is 1, we could simply multiply the size of HUD elements by DPR to get resolution-independent size of the element (so that it doesn’t look too small too dense displays):

//drawing a 48dip cross at (16dip;16dip)
ctx.fillRect(32 * devicePixelRatio, 16 * devicePixelRatio, 16 * devicePixelRatio, 48 * devicePixelRatio);
ctx.fillRect(16 * devicePixelRatio, 32 * devicePixelRatio, 48 * devicePixelRatio, 16 * devicePixelRatio);

On screen, the cross would have the following size with the old behavior:

Display DPI Zoom level devicePixelRatio Output size on screen
96 (1x) 100% 1 48×48
96 (1x) 200% 1 96×96
96 (1x) 400% 1 192×192
192 (2x) 100% 2 96×96
192 (2x) 200% 2 192×192
192 (2x) 400% 2 384×384

This is fine, on high-DPI displays 384×384 looks like 192×192 on low-DPI displays, so it’s correctly zoomed by 400% (48 * 4 = 192).

However, with the new DPI, DPR is multiplied by the zoom level too, so if we multiply the size by DPR, we get the element scaled twice (in the script by DPR and in the browser by zoom):

Display DPI Zoom level devicePixelRatio Output size on screen
96 (1x) 100% 1 48×48
96 (1x) 200% 2 192×192
96 (1x) 400% 4 768×768
192 (2x) 100% 2 96×96
192 (2x) 200% 4 384×384
192 (2x) 400% 8 1536×1536

On high-DPI displays, 1536×1536 looks like 768×768 on low-DPI displays. 768×768 is 1600% larger than 48×48, while we only wanted to zoom by 400%.

So, as I said before, we get the element zoomed by zoomLevel^2 instead of zoomLevel.

Use case example

If you open WebQuake on a high-DPI display, you can see that the game is rendered in HD, but the HUD and the menus appear too small.

This is because I set <meta viewport> scale to 1. If I didn’t do this, the HUD size would be correct, but the game would be blurry.

What exactly I want is to render the game in HD while having the HUD drawn at correct size.

The solution

The solution is very simple.

A constant value which behaves the same way as the old DPR – doesn’t take zoom level into account.

Let it be named window.deviceNormalPixelRatio or navigator.deviceNormalPixelRatio.

Another important thing is to make it viewport scale-independent. It would have no point then, since it is to be used when viewport scale is forced to 1 (for manual scaling of selected elements).

It must be accessible from CSS queries too so we won’t have to use JavaScript to resize every element.

For HD image loading, the old devicePixelRatio would be used, and for manual <meta name="viewport"> correction, deviceNormalPixelRatio would be.

Another possible use

Along with DPI scaling, deviceNormalPixelRatio can be used to retrieve zoom level in a cross-browser way (there’s no way to retrieve zoom level yet!):

var zoom = devicePixelRatio / deviceNormalPixelRatio;

Leave a Reply

Your email address will not be published. Required fields are marked *


You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Before you comment here, note that this forum is moderated and your IP address is sent to Akismet, the plugin we use to mitigate spam comments.