Rendering bug in Chrome Android? (ok in Chrome Desktop and Firefox Android)

EDIT: posted in GitHub tracker: Chrome Android rendering is incorrect · Issue #24412 · mrdoob/three.js · GitHub


There’s something going on either in Three.js (seems unlikely in this case, but still a possibility), or with discrepancies between browsers (seems more likely).

Here is a simple Three.js scene: https://codepen.io/trusktr/pen/NWYXdoO/d4a556bd02a253cf510b971dfee4772f

Here is the “debug view” (full page without codepen UI): CodePen - Chrome Android bug with Three.js? v2

In Chrome desktop, the debug view will look like this:

Open Chrome devtools, turn on the device emulator, set the “Dimensions” to “Responsive”, set the values to 980 x 1851, and you will see this:

Now, open it in Firefox on Android, or Safari on iOS, and it works as expected in both, without any distortion:

Finally, open it in any Chrome/Blink-based browser including Chrome Android, Edge Android, Opera Android, and Samsung Internet Android, and it will be broken presumably because they all use the same browser engine. The position of the object will be off, and the object will be stretched (as if it were scaled on Y or similar). Here are those four browsers on Android, respectively:

Any ideas what the issue could be? Perhaps it is a bug in Chrome’s WebGL API.

I am able to reproduce the problem only on Chrome-based Android browsers so far.

Is there a bug in Chrome?

Ping, anyone?

I simplified the reproduction, the JSON is short and simple, just a scene with a cube and a camera.

1 Like

Posted in Three.js GitHub tracker, let’s see what they say there:

Test it:

"use strict";
const renderer = new THREE.WebGLRenderer();
const canvas = renderer.domElement;
const sceneW = 980;
const sceneH = 1851;
const sceneAspect = sceneW / sceneH;
let windowAspect = innerWidth / innerHeight;
renderer.setSize(sceneW, sceneH);


var camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 2000 );
camera.position.set(0,0,10);
var scene = new THREE.Scene();
var mesh=new THREE.Mesh(new THREE.BoxGeometry(1,1,1),new THREE.MeshBasicMaterial({color:0x009090}));
scene.add(mesh);


function resize() {
    let windowAspect = innerWidth / innerHeight;
    if (windowAspect >= sceneAspect) {
        canvas.width = (window.innerHeight * sceneAspect) ;
        canvas.height = window.innerHeight ;
    }
    else {
        canvas.width = window.innerWidth ;
        canvas.height = (window.innerWidth / sceneAspect) ;
    }


var width=canvas.width;
var height=canvas.height;


camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize( width, height );

}


resize();
window.addEventListener('resize', resize);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.append(canvas);


function render(){
requestAnimationFrame(render);
renderer.render( scene, camera );
}


render();
1 Like

@Chaser_Code Same issue: https://github.com/mrdoob/three.js/issues/24412#issuecomment-1200368268

Thanks for simplifying it!

@Mugen87 I feel that you prematurely closed that bug report before we have found out if the issue is in Chrome Android, or in Three.js.

Three.js will need to be instrumented so we can see which code paths differ between desktop and Android…

The three.js APIs you are using here do not have dedicated code paths for Android devices. It may be worth trying to recreate this with just Canvas 2D API instead of three.js. You may want to measure and log the sizes, aspect ratios, etc. on the device as well.

3 Likes

Compare window.innerHeight without “bug” and with “bug”.

I saw that the pixel sizes for the canvas differ by about 0.25px between mobile and desktop (when desktop is set to the identical size as mobile in the device emulator in devtools). F.e. XXX.5 on desktop and XXX.24567 or similar on mobile. Not sure that it would have any effect on the internal drawing of the canvas though.

Good idea, I’ll try

You’re sounding alarms that the Three.js team needs to investigate this bug, when it’s you who’s using very unorthodox and conflicting methods of sizing your canvas. You’re using hard values (1851), .setSize() then overriding it with styles. I recommend you plug in your Android to your computer via USB then use this method to debug your own device to see what your code is doing to your canvas.

When debugging, I noticed that you’re setting a height of sceneH = 1851 then using renderer.setPixelRatio(window.devicePixelRatio). A lot of Android devices have a pixel ratio of 3, which makes your scene height 1851 * 3 = 5553. This seems to be the root of your problem.

If you set renderer.setPixelRatio(2), then the stretching goes away. My guess is that you’re reaching some dimension limit, since window.innerHeight is typically less than 1851px on an Android. Mine is 1232px.

1 Like

Thanks, I appreciate the feedback. I’m already doing that remote debugging. The desktop Chrome inspector (connected to my Android device) shows the same result as I see on Android (scene content erroneously distorted).

i.e. The above screenshots of Android Chrome show the same result as when I view through the inspector. I believe Android simply pipes the screen data to the inspector, so inspecting with desktop Chrome has no effect on the result.

This should have no effect on the scene contents, it would only effect the resolution of the scene, but it should not stretch anything in the scene because the aspect ratio is always the same. Everything in the scene should be drawn using a WebGL viewport height of 2, and width of 2, regardless of the resolution, and regardless of the aspect ratio.

Given that the aspect ratio is the same on all devices, the rendering should be proportionally the same on all devices.

There’s nothing unorthodox in the way I’m sizing the canvas. It should not be distorting anything in the way I’ve rendered it.

It works on all devices, except Android Chrome (and derivatives Edge, Opera, Samsung Internet on Android), which definitely implies something (not sure what yet).

If this in fact turns out to be the problem (it shouldn’t be causing the problem), then the question would be is it Three.js or Chrome that the issue is coming from?

Here is iPhone Safari, with the correct result (hence all derivative browsers on iOS have the correct result because they all use Safari web views):

The issue happens only in Blink-based browsers on Android.

I’m pretty certain it’s a Chrome issue. I really do think you’re reaching some sort of maximum canvas or buffer size limit. But if you really want to pursue this further, you could re-create the bug using another WebGL library to rule out the possibility. TWGL.js is a very low-level lib that’s a very thin layer above WebGL. You could recreate your dimensions with this demo and compare:

2 Likes

They surely don’t need to. If I was core member of Three.js (I’m not), I certainly would want to. IMO, no harm sharing what I did.

Nice idea, thanks! I made a similar demo in TWGL and was able to reproduce the same issue: https://codepen.io/trusktr/pen/LYdQJBL/4a7332563e821c50c76f65c849dd1bb7?editors=1000

Expected result without distortion on desktop:

Distortion on Chrome Android:

It is looking like a Chrome bug.

It isn’t visible in the images, but the distortion happens along along all axes including Z towards the camera, so if we rotate the camera around the object (f.e. with OrbitControls) the object appears to be translated forward on Z. Strange.

They surely don’t need to. If I was core member of Three.js (I’m not), I certainly would want to. IMO, no harm sharing what I did.

I think if you’re going to report a bug to any library it’s your responsibility to have done some reasonable research to ensure it’s actually a bug with the library but you haven’t shown that, yet. Three.js maintainers have enough to work on without spending time trying to get to the bottom of every users misunderstanding of WebGL limitations and three.js implementation. You may disagree but you have understand that people may start taking your reports less seriously as a consequence.

At first brush here if this works on nearly every other device but one or two browser / device combos then you can be fairly sure that it’s not a three.js implementation issue. And if you do a little searching online you can see that there are canvas size limitations per device. If you’d like to see the limitations of your device you can run the following on the remote console:

c = document.createElement( 'canvas' );
ctx = c.getContext( 'webgl2' );
ctx.getParameter( ctx.MAX_VIEWPORT_DIMS );

// returns [ 32767, 32767 ] on Windows 10, Chrome, RTX 2070
// returns [ 16384, 16384 ] on Android Pixel 3, Chrome

You haven’t posted your device information but it’s possible you’re limited to a 4k canvas on your device which could be the source of the issue.

1 Like

It is a Pixel 6 Pro with

innerWidth
980
innerHeight
1851
devicePixelRatio
3.5
const gl = document.createElement('canvas').getContext('webgl')
gl.getParameter(gl.MAX_VIEWPORT_DIMS)
[8192, 8192]

And the canvas I see in the DOM after rendering is

<canvas width="3430" height="6478" style="display: block; width: 980px; height: 1851px;"></canvas>

If I understand correctly, the width and height attributes dictate the canvas drawing size. The physical dimensions of the canvas on screen are 980*3.5 by 1851*3.5, or 3430 x 6478, which is what the canvas size is set to, and those are lower than the limit.

It’s odd to me that your newer 2021 pixel device has a lower limit than my 2018 pixel 3 one…

If I understand correctly, the width and height attributes dictate the canvas drawing size.

These are values you can request the canvas size to be but there’s no guarantee the device can provide them. You can query these values to get the current drawing buffer size which will be clamped based on device capability. For some reason these seem to max out before hitting the MAX_VIEWPORT_DIMS values:

console.log( gl.drawingBufferWidth, gl.drawingBufferHeight );

See here for more context:

https://groups.google.com/g/webgl-dev-list/c/AHONvz3oQTo?pli=1