Open Bug 1864882 Opened 2 years ago Updated 4 months ago

transferFromImageBitmap from OffscreenCanvas is unusably slow

Categories

(Core :: Graphics: Canvas2D, defect)

Firefox 119
defect

Tracking

()

People

(Reporter: ashley, Unassigned)

References

Details

Attachments

(1 file)

Steps to reproduce:

A useful technique for rendering a WebGL canvas to multiple views is to render each view and then copy the result to a different canvas. One way of doing this is to draw it to a 2D context, which is unusably slow (issue 1163426, filed back in 2015). Another way is to draw it to an OffscreenCanvas, and use imageBitmapRenderingCtx. transferFromImageBitmap(offscreenCanvas.transferToImageBitmap()). However this is also unusably slow.

Here is a minimal repro: http://localhost:63000/offscreencanvas-transfer-webgl-slow.html
View it in a maximized window in Firefox. It logs the FPS to the console once a second.

Actual results:

This approach can achieve barely 15 FPS on my system with a NVIDIA GeForce RTX 4070.

Expected results:

It should run smoothly, like it does in Chrome, which can achieve a steady 60 FPS on the same system.

We're planning on launching a new feature that relies on this multi-view rendering approach in the next couple of months. Due to both this issue and issue 1163426, it seems there is no way to achieve good performance with this feature in Firefox. This may block the feature or encourage switching to Chrome for better performance. I would guess it is doing a CPU copy rather than being GPU accelerated. Thanks for any help!

Component: Untriaged → Graphics: Canvas2D
Product: Firefox → Core

Your testcase is on localhost so only you can run it.

Flags: needinfo?(ashley)

D'oh, sorry, pasted the wrong link. Here's what I meant to share: https://downloads.scirra.com/labs/bugs/offscreencanvas-transfer-webgl-slow.html

Flags: needinfo?(ashley)

FWIW Safari 17.1 appears to also run this perfectly smoothly, so it seems Firefox is the only browser with a performance problem here.

Severity: -- → S3
Status: UNCONFIRMED → NEW
Ever confirmed: true

I investigated this locally with Firefox.

For the WebGL OffscreenCanvas case, the slow part is not transferFromImageBitmap() itself. In my logs, ImageBitmapRenderingContext::TransferFromImageBitmap() / TransferAsImage() / Redraw() are essentially ~0ms.

The expensive part is OffscreenCanvas.transferToImageBitmap() creating the ImageBitmap:

OffscreenCanvas::TransferToImageBitmap
ImageBitmap::CreateFromOffscreenCanvas
nsLayoutUtils::SurfaceFromOffscreenCanvas
ClientWebGLContext::GetSurfaceSnapshot
ClientWebGLContext::BackBufferSnapshot
DoReadPixels

On my machine with a 2560×1912 WebGL OffscreenCanvas, DoReadPixels alone commonly takes ~15–30ms per frame, with occasional higher spikes. The full BackBufferSnapshot / CreateFromOffscreenCanvas path is dominated by that CPU readback.

So Firefox appears to implement this path as: WebGL OffscreenCanvas → readPixels CPU DataSourceSurface → SourceSurfaceImage → ImageBitmap → bitmaprenderer

Chromium appears to avoid this CPU readback when GPU compositing is available. Its path is:

OffscreenCanvas.transferToImageBitmap
WebGLRenderingContextBase::TransferToImageBitmapBase
DrawingBuffer::TransferToStaticBitmapImage
ExportSharedImageFromBackBuffer
AcceleratedStaticBitmapImage::CreateFromCanvasSharedImage
ImageBitmapRenderingContext::SetImage

So Chromium effectively transfers/wraps a GPU SharedImage instead of reading the WebGL backbuffer to CPU memory.

Pinned Chromium references:

You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: