aframevr / aframe

:a: Web framework for building virtual reality experiences.
https://aframe.io/
MIT License
16.65k stars 3.96k forks source link

screenschot.getCanvas("perspective"): Script terminated by timeout on firefox #4736

Closed ghost closed 3 years ago

ghost commented 3 years ago

Description: When trying to take screenshot from one scene, firefox reports a termination by timeout.

I'm using AR.js with Aframe.

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): image

From the link provided, you can just click on the middle button to take a screenshot.

dmarcos commented 3 years ago

Does the screenshot works in VR or Chrome?

Notice that even if it worked the ar.js video feed won't be captured since that's handled outside of the canvas used to render WebGL.

ghost commented 3 years ago

Does the screenshot works in VR or Chrome?

Yes, it works on Chrome, Samsung Internet and Edge (Chromium), VR as well.

I was trying to reproduce this on Fiferox's desktop version, but I've got another issue, with my webcam.


Notice that even if it worked the ar.js video feed won't be captured since that's handled outside of the canvas used to render WebGL.

I have that covered. I'm using a modified version of this code snippet with my wasm module to merge AR.js and Aframe canvases together.

dmarcos commented 3 years ago

If it works in all browsers except Firefox it looks like a browser bug. Flipping the image is an expensive operation. Maybe some issue with Firefox memory management on mobile.

FWIW, screen capture works fine for me on Firefox Desktop with any of the examples

ghost commented 3 years ago

If it works in all browsers except Firefox it looks like a browser bug. Flipping the image is an expensive operation. Maybe some issue with Firefox memory management on mobile.

Yes, I agree. I was going to close this issue with a similar statement.

Sorry about the noise and thanks.

ghost commented 3 years ago

From https://github.com/mozilla-mobile/fenix/issues/17058 :

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.

Working code for F.F. Fenix 83+ (Android 9)

Here's a walkthrough if anyone stumble on this issue:

  1. Copy the code that flips the image vertically and put it in a inline Worker:

    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, applying some changes (hopefully the changes are obvious):

    
    // `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.
}