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

Re: [Public WebGL] How to unambiguously set canvas pixel size to be pixel-perfect?



I tried getBoundingClientRect yesterday, only to realize that that or others can't solve the issue. If I build a canvas element like

<canvas style="width: 800px; height: 600px;"> and even if my page has no scroll offset or zoom, getBoundingClientRect will of course return 800x600 pixels, but I can't know what the device pixel size is that I should set to canvas to make it work. I would recommend having an api that can query for a given dom element, what its size is in device pixels when viewed unzoomed, call it domElement.devicePixelSizeWhenViewedUnzoomed() that returns an object { width: x; height: y}.

I can understand the need for two different zooming: page zoom being one that affects layout and the page is aware of, and pinch zoom being one that the page doesn't know about. Under page zoom, the values returned by devicePixelSizeWhenViewedUnzoomed() would be allowed to change, and under pinch zoom, they shouldn't.

Now, coming back to the specific use case where an application simply wants to have a mobile web page with a single canvas element that fills the whole screen, the following scenario seems to work at least on Firefox OS mobile devices:

1. Set CSS style of <body> to "margin: 0px; width: 100%; height: 100%;".
2. Set <canvas> as child of body, and make sure it expands to cover its parent fully by setting its style to "width: 100%; height: 100%;" as well.
3. Make sure there are no other elements whatsoever on the page that would affect the layout.
4. Then you can call document.documentElement.getBoundingClientRect(), and it will properly return fractional CSS pixel size of the device, which one can convert to device pixel sizes with the formula

var devicePixelWidth = Math.round(document.documentElement.getBoundingClientRect().width * window.devicePixelRatio);
var devicePixelHeight = Math.round(document.documentElement.getBoundingClientRect().height * window.devicePixelRatio);

the .round() is not to round due to off-by-one-pixel issues, but to round to nearest due to imprecise floating point computation, e.g. when testing on a FFOS device with window.devicePixelRatio of 1.5, I would otherwise get a screen width of 399.99999999998, so the purpose of .round() is just to remove that.

In that scheme, I am able to reconstruct back the real pixel resolution of the mobile device and size my canvas accordingly. This is enabled by the fact that document.documentElement.getBoundingClientRect().height will return a fractional value because 
   a) I did not set any styles to body or canvas in absolute pixels and
   b) I made sure that there's just one DOM element that fills the whole screen.

However these are pretty heavy restrictions, since some pages want to have other web content in it as well. Also I observed that the scheme is very brittle, since document.documentElement.getBoundingClientRect() stops working as soon as any other elements disrupt the layout, or if I failed to style the canvas such that it really causes the body to expand to full screen. It would be much more ideal if I could ask any arbitrarily placed dom element for domElement.devicePixelSizeWhenViewedUnzoomed() or domElement.bestMatchingDevicePixelSizeForPixelPerfectCanvasRendering().


2014-06-12 0:09 GMT+03:00 Kenneth Russell <kbr@google.com>:
This issue is a deep one which can't be solved in isolation in the
WebGL spec. Even ignoring page zoom, adding a context creation
attribute like "scaled: false" will break many invariants in the web
platform. For example, it would cause changes to the CSS width and
height properties to have the side-effect of resizing the WebGL
canvas's backing store. Resizes would start coming in asynchronously
to the WebGL app. I don't think any browser implements per-element
resize events. There would likely be other surprising and undesirable
side-effects.

It's possible that https://www.khronos.org/webgl/wiki/HandlingHighDPI
just needs to be updated. If there are better ways to measure the
number of device pixels that a given region or canvas will cover then
let's update the page. Boris Zbarsky pointed out the
getBoundingClientRect, getClientRects, and getBoxQuads APIs. Could
someone who's been investigating this in depth please try them and see
whether they solve the problem? I suggest considering the unzoomed
case first because it seems to be the most common case.

-Ken


On Wed, Jun 11, 2014 at 12:05 AM, Evan Wallace <evan.exe@gmail.com> wrote:
> This is an issue I'm struggling with too. There aren't any problems with
> framebuffer size in my case though. I'm trying to cover most of the screen
> with a WebGL canvas and that area has the same number of physical pixels
> regardless of zoom level (well, it's really a little less after you zoom in
> because the UI around the canvas grows in size a bit).
>
> DPI handling works by setting the canvas width/height to the CSS
> width/height times the devicePixelRatio, so the canvas framebuffer is
> approximately scaled 1:1 with physical pixels. For fullscreen WebGL apps,
> zooming in makes the window size go down and the devicePixelRatio go up to
> match.
>
> The problem is that CSS only lets you set the canvas size to integer values,
> and most of the time it's impossible to specify an integer width/height such
> that the zoomed canvas is exactly the size you need. Demo showing the
> problem: http://fiddle.jshell.net/V884T/show/ (zoom in and the grid won't
> match 1:1).
>
> Strawman API proposal: add a "scaled: false" parameter to the WebGL context
> creation attributes that would always render the framebuffer from the top
> left corner at 1:1 with physical pixels (i.e. not scaled) without affecting
> the size of the canvas element itself.
>
>
> On Tuesday, June 10, 2014, Rik Cabanier <cabanier@gmail.com> wrote:
>>
>>
>>
>>
>> On Tue, Jun 10, 2014 at 10:41 PM, Tony Parisi <tparisi@gmail.com> wrote:
>>>
>>> I am skeptical of that figure... Where is the data to support it?
>>>
>>> My guess is that less than 5% of users even know about the zoom
>>> features...
>>
>>
>> I was very surprised about that figure too. It's been over a year since he
>> sent that out and I'm having trouble finding his message.
>>
>>>
>>> And yeah it's bad for WebGL graphics
>>>
>>> Tony Parisi
>>> CTO at Large
>>> 415.902.8002
>>>
>>>
>>> On Jun 10, 2014, at 10:31 PM, Florian Bösch <pyalot@gmail.com> wrote:
>>>
>>> On Wed, Jun 11, 2014 at 7:25 AM, Rik Cabanier <cabanier@gmail.com> wrote:
>>>>
>>>> Browser zoom (= ctrl/cmd +/-) also affects DPR.
>>>> Someone from Google maps stated that 15% of all users have some level of
>>>> browser zoom enabled.
>>>
>>> I didn't know that. That's quite odd, and completely unusable for WebGL
>>> because there are limits to the size you can set a framebuffer to as well as
>>> that rendering to ridiculous resolutions would become very slow, very
>>> quickly.
>>
>>
>