KhronosGroup / KhronosGroup/WebGL

CSS pixel size -> WebGL canvas size example is subtly wrong?

Open
#2,460 50 comments 4 reactions 0 assignees View on GitHub
Dominant language
HTML
Stars
2.9k
Forks
703
Avg merge
2d 9h
Merged PRs (30d)
4

Description

Consider the example of choosing a WebGL backbuffer size based on the CSS pixel size of the canvas `window.devicePixelRatio` to match 1:1 with high DPI/physical screen pixels at

https://www.khronos.org/webgl/wiki/HandlingHighDPI

The example reads

```javascript

#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>
```

Here `window.devicePixelRatio` can be an arbitrary number like `1.353239x` (it is affected by page zoom level) and the CSS pixel size (`canvas.clientWidth/Height`) can also be arbitrary float numbers like `732.4315135px` as they come from `width: 50%, height: 50%` sizes, but `canvas.width/.height` need to be integers, so there's a double->int conversion that takes place.

In this example the physical size of the canvas is set to truncate/round down the computed CSS pixel size to integer units at

```javascript
canvas.width = canvas.clientWidth * devicePixelRatio;
canvas.height = canvas.clientHeight * devicePixelRatio;
```

however to my understanding there is no guarantee that rounding down is the correct action to perform, nor that rounding to nearest, or rounding up would be the correct action to perform either, in order to get a WebGL rendering output that matches up 1:1 with the pixel size on screen.

In practice this kind of "I don't know whether I should round down, round up or round to nearest" problem can result in off-by-one pixel scenarios for the WebGL render target in different browser implementations, and as result, either the whole WebGL canvas output becomes blurry, as its composited to widen/tallen/shrink by one pixel, or one column or row of the WebGL canvas is duplicated in the middle, or there will be a one-pixel black/white border on the canvas.

To my experience the only correct way to choose the backbuffer size of a WebGL canvas, given an (arbitrary) CSS pixel size, is to first compute the WebGL backbuffer size from the CSS pixel size, e.g. using the above formula, but then after having set the WebGL backbuffer size, propagate that integer size back to the CSS pixel size to ensure that the two match up. That is,

```javascript

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

// propagate the integer size back to CSS pixels to ensure they align up 1:1.
canvas.style.width = (canvas.width / devicePixelRatio) + 'px';
canvas.style.height = (canvas.height / devicePixelRatio) + 'px';
```

A result of this issue is that to avoid compositing artifacts, it's not possible to use automatic CSS directives such as `width: 50%` to size a canvas, while expecting a 1:1 match with physical screen size.

Is there anything better to ensure 1:1 match for a canvas size without glitches, or if not, perhaps the example should be updated to instruct developers with this gotcha?

Contributor guide

Open the contributing guide

Research direction

Start with the HandlingHighDPI example linked in the issue and review the JavaScript canvas sizing snippet, including the CSS percentage dimensions. Check the reported fractional CSS-pixel and devicePixelRatio cases across relevant browser behavior. Done means the example accurately documents the sizing limitation or a supported way to avoid the described compositing artifacts.

Written by the indexing model from the issue text.

Assessment

Tech stack
css, html, javascript
Domain
computer-graphics, documentation, web-dev
Issue type
Documentation
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.