mozilla-mobile / fenix

⚠️ Fenix (Firefox for Android) moved to a new repository. It is now developed and maintained as part of: https://github.com/mozilla-mobile/firefox-android
https://github.com/mozilla-mobile/firefox-android
Mozilla Public License 2.0
6.47k stars 1.27k forks source link

[Bug] AFRAME.js Screenshot: Script is terminated by timeout #17058

Closed ghost closed 3 years ago

ghost commented 3 years ago

Steps to reproduce

  1. Open https://aquele.cafe
  2. Click on the button to take a screenshot

    Expected behavior

    Take screenshot and show to the user.

Actual behavior

Script is terminated by timeout, no screenshot is taken.

Debug log Debugging logs (where `$0` is the scene):
Some cookies are misusing the recommended “SameSite“ attribute 3
Use of the motion sensor is deprecated. webvr-polyfill.js:2292:11
No DPDB device match. webvr-polyfill.js:1963:10
Failed to recalculate device parameters. webvr-polyfill.js:1915:12
Using fallback Android device measurements. webvr-polyfill.js:1699:14
A-Frame Version: 1.0.4 (Date 2020-02-05, Commit #2b359246) index.js:92:8
three Version (https://github.com/supermedium/three.js): ^0.111.6 index.js:93:8
WebVR Polyfill Version: ^0.10.10 index.js:95:8
asm.js type error: Disabled by debugger aframe-ar.js
webvr-polyfill: Invalid timestamps detected: non-monotonic timestamp from devicemotion webvr-polyfill.js:669:14
THREE.WebGLRenderer: OES_texture_float_linear extension not supported. three.js:15943:13
No DPDB device match. webvr-polyfill.js:1963:10
Failed to recalculate device parameters. webvr-polyfill.js:1915:12
AR.js 3.3.1 - trackingBackend: artoolkit aframe-ar.js:3994:10
THREE.WebGLRenderer: EXT_texture_filter_anisotropic extension not supported. three.js:15943:13
ARjs.Anchor - changeMatrixMode: modelViewMatrix / markersAreaEnabled: false aframe-ar.js:3528:10
THREEx.ArMarkerControls: 'markersAreaEnabled' is not a property of this material. aframe-ar.js:2023:13
THREEx.ArMarkerControls: 'minConfidence' parameter is undefined. aframe-ar.js:2016:13
*** Camera Parameter resized from 640, 480. *** aframe-ar.js:2:23009
Allocated videoFrameSize 307200 aframe-ar.js:2:23009
Use of mozImageSmoothingEnabled is deprecated. Please use the unprefixed imageSmoothingEnabled property instead. aframe-ar.js:2608:8
Pattern detection mode set to 4. aframe-ar.js:2:23009
Pattern ratio size set to 0.500000. aframe-ar.js:2:23009
$0.components.screenshot.getCanvas("perspective")
webvr-polyfill: Invalid timestamps detected: Timestamp from devicemotion outside expected range. webvr-polyfill.js:669:14
Script terminated by timeout at:
flipPixelsVertically@https://aframe.io/releases/1.0.4/aframe.min.js:859:3957
renderCapture@https://aframe.io/releases/1.0.4/aframe.min.js:859:3723
getCanvas@https://aframe.io/releases/1.0.4/aframe.min.js:859:3266
@debugger eval code:1:26
screenshot.js:234:22
undefined
Image (where $0 is the scene): ![debug log](https://user-images.githubusercontent.com/23178554/100291876-57669b80-2f55-11eb-972e-5d2c70d973fe.png)

Aditional information

This is the script that runs when trying to take a screenshot:

https://github.com/aframevr/aframe/blob/cfd869b271cef9454e0c7979af1fb59c880194bc/src/components/scene/screenshot.js#L239-L250

it is expensive, so maybe Firefox is not handling memory correctly.

In Chromium based browsers and Safari it works as expected. Samsung Internet example:

Loading (merging the canvases) Screenshot (canvases merged)
loading image image loaded

Related: https://github.com/aframevr/aframe/issues/4736

Device information

┆Issue is synchronized with this Jira Task

LaurentiuApahideanSV commented 3 years ago

I can confirm the issue is reproducible Firefox Preview Nighlty 201216 (Build #2015781675).

Devices used:

ghost commented 3 years ago

Though it is still an issue in 84.1.2, I've updated the code that provides the screenshot without errors, for my use case.

Since I've solved my issue and updated the production code, I'm going to close this. If the FF Fenix team want to have a reproducible code, I can provide a zip file containing the old version.

The current code doesn't improve the performance, it still takes a considerable amount of time, compared to other browsers, to process the image.

A small walkthrough of what I've done to get it working 1. Copy the [code](https://github.com/aframevr/aframe/blob/cfd869b271cef9454e0c7979af1fb59c880194bc/src/components/scene/screenshot.js#L239-L250) that flips the image vertically and put it in a inline Worker: ```javascript const flipper = (() => { const flipPixelsVertically = (msg) => { const {pixelsBuffer, width, height} = msg.data; const pixels = new Uint8Array(pixelsBuffer); const flippedPixels = pixels.slice(0); for (let x = 0; x < width; ++x) { for (let y = 0; y < height; ++y) { flippedPixels[x * 4 + y * width * 4] = pixels[x * 4 + (height - y) * width * 4]; flippedPixels[x * 4 + 1 + y * width * 4] = pixels[x * 4 + 1 + (height - y) * width * 4]; flippedPixels[x * 4 + 2 + y * width * 4] = pixels[x * 4 + 2 + (height - y) * width * 4]; flippedPixels[x * 4 + 3 + y * width * 4] = pixels[x * 4 + 3 + (height - y) * width * 4]; } } self.postMessage(flippedPixels.buffer, [flippedPixels.buffer]); }; const blob = new Blob(['self.onmessage = ', flipPixelsVertically.toString()], { type: 'text/javascript' }); const workerURL = URL.createObjectURL(blob); return new Worker(workerURL); })(); ``` 2. Copy the [`getCanvas` method](https://github.com/aframevr/aframe/blob/cfd869b271cef9454e0c7979af1fb59c880194bc/src/components/scene/screenshot.js#L196), applying some changes (hopefully the changes are obvious): ```javascript // `this` is the scene component. const screenshotComponent = this.el.components.screenshot; let isVREnabled; const flipScreenshotPixelsVertically = () => { // Save the state. isVREnabled = this.el.renderer.xr.enabled; this.el.renderer.xr.enabled = false; screenshotComponent.quad.visible = false; const camera = this.data.camera ? this.data.camera.components.camera.camera : this.el.camera; const [width, height] = [screenshotComponent.data.width, screenshotComponent.data.height]; const autoClear = this.el.renderer.autoClear; const renderer = this.el.renderer; // Create rendering target and buffer to store the read pixels. const output = screenshotComponent.getRenderTarget(width, height); const pixels = new Uint8Array(4 * width * height); // Resize quad, camera, and canvas. screenshotComponent.resize(width, height); // Render scene to render target. renderer.autoClear = true; renderer.clear(); renderer.setRenderTarget(output); renderer.render(this.el.object3D, camera); renderer.autoClear = autoClear; // Read image pizels back. renderer.readRenderTargetPixels(output, 0, 0, width, height, pixels); renderer.setRenderTarget(null); // send to the inline worker. flipper.postMessage({ pixelsBuffer: pixels.buffer, width, height }, [pixels.buffer]); // wait for the message. return new Promise((resolve) => { flipper.onmessage = msg => { const imageData = new ImageData(new Uint8ClampedArray(msg.data), screenshotComponent.data.width, screenshotComponent.data.height); // Hide quad after projecting the image. screenshotComponent.quad.visible = false; // Copy pixels into canvas. screenshotComponent.ctx.putImageData(imageData, 0, 0); this.el.renderer.xr.enabled = isVREnabled; resolve(); } }); }; ``` 3. Use where it is needed. Example: ```javascript async function screenshotOnClick() { await flipScreenshotPixelsVertically(); //... your code. } ```