Bug 230766 - Canvas drawImage is very slow when used with video as source
Summary: Canvas drawImage is very slow when used with video as source
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Canvas (show other bugs)
Version: Safari Technology Preview
Hardware: Mac (Intel) macOS 11
: P2 Blocker
Assignee: Cameron McCormack (:heycam)
URL:
Keywords: InRadar
Depends on: 232695
Blocks:
  Show dependency treegraph
 
Reported: 2021-09-24 12:01 PDT by Patryk Rogalski
Modified: 2021-11-04 21:06 PDT (History)
14 users (show)

See Also:


Attachments
comparison between Chrome and Safari (41.09 MB, video/quicktime)
2021-09-24 12:01 PDT, Patryk Rogalski
no flags Details
Patch for EWS (2.21 KB, patch)
2021-10-24 18:49 PDT, Cameron McCormack (:heycam)
no flags Details | Formatted Diff | Diff
Patch (2.76 KB, patch)
2021-10-27 16:25 PDT, Cameron McCormack (:heycam)
no flags Details | Formatted Diff | Diff
Patch (4.78 KB, patch)
2021-10-28 23:28 PDT, Cameron McCormack (:heycam)
no flags Details | Formatted Diff | Diff
Patch for landing (2.72 KB, patch)
2021-11-04 20:35 PDT, Cameron McCormack (:heycam)
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Patryk Rogalski 2021-09-24 12:01:45 PDT
Created attachment 439181 [details]
comparison between Chrome and Safari

I'm running the latest Safari and Safari Technology Preview and I've run into a performance issue in Safari.

I have a video that is encoded in H264 2176x1088. The video itself plays smoothly however I have to cut the content of it into 8 different canvas elements.

I have a working demo of it working however the performance I'm getting is abysmal. I've ran the same code in Google Chrome 94 and it's perfect.

Here is the comparison:
Chrome takes about 0.5-2ms to draw
Safari takes 150-330ms to draw
Firefox takes 100-150ms

And it has to run at 30 fps... which is completely impossible right now.

Here is a demo to showcase the issue: https://cdn.zu.casa/dev/apple/index.html

If open developer console it'll print out to the console the time it took to render.
Comment 1 Patryk Rogalski 2021-09-24 12:12:14 PDT
I had an older version of Safari on my iMac and it ran super fast (1.5-4ms) - as fast as in Google Chrome. After updating to the latest version it's terrible (200-350ms).
Comment 2 Sam Sneddon [:gsnedders] 2021-09-27 01:22:01 PDT
Quick look at a sample suggests we're blocking on cross-thread/process communication a bunch. Probably a regression caused by moving more things out-of-process?
Comment 3 Radar WebKit Bug Importer 2021-09-27 09:12:51 PDT
<rdar://problem/83576009>
Comment 4 Cameron McCormack (:heycam) 2021-10-24 16:51:44 PDT
Thanks for the bug report.  Sam is right: this is because with GPU process canvas rendering enabled, we end up doing some copies of the HTMLVideoElement frame data (which lives in the GPU process) to share it (synchronously) to the Web process, only to ship it back to the GPU process again for the drawImage() call.  We should be able to avoid any of this copying.
Comment 5 Cameron McCormack (:heycam) 2021-10-24 18:49:46 PDT
Created attachment 442334 [details]
Patch for EWS
Comment 6 Cameron McCormack (:heycam) 2021-10-27 16:25:38 PDT
Created attachment 442649 [details]
Patch
Comment 7 Cameron McCormack (:heycam) 2021-10-27 17:16:28 PDT
Simon pointed out to me there's another call to nativeImageForCurrentTime(), in createPattern().  But patterns currently can't avoid copying the pattern source image data from the GPU process, so I'll leave that be for now.  And filed https://bugs.webkit.org/show_bug.cgi?id=232411 for the more general problem.
Comment 8 Cameron McCormack (:heycam) 2021-10-28 23:28:38 PDT
Created attachment 442787 [details]
Patch
Comment 9 EWS 2021-10-29 14:45:47 PDT
Committed r285055 (243698@main): <https://commits.webkit.org/243698@main>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 442787 [details].
Comment 10 ayumi_kojima 2021-11-01 09:28:08 PDT
Reverted r285055 for reason:

Reverting because this commit may have caused webgl/1.0.x/conformance/textures/misc/texture-corner-case-videos.html and webgl/2.0.y/conformance/textures/misc/texture-corner-case-videos.html to time out

Committed r285110 (243751@main): <https://commits.webkit.org/243751@main>
Comment 11 Said Abou-Hallawa 2021-11-01 11:13:48 PDT
Comment on attachment 442787 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=442787&action=review

> Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp:1680
> +    if (!canvasBase().buffer()->isRemote()) {

We have been trying to avoid adding this condition in WebCore. Can't we do this by checking whether 'c', which is the GraphicsContext of canvasBase().buffer(), is a recording GraphicsContext or not?

I think you can replace this condition by 

if (c->hasPlatformContext()) {
    ...
    return { };
}

This function has been used for this purpose in WebCore in a few places. Just search for it.

> Source/WebCore/platform/graphics/ImageBuffer.h:74
> +    virtual bool isRemote() const { return false; }

Please avoid adding such check in WebCore.

> Source/WebKit/WebProcess/GPU/graphics/RemoteImageBufferProxy.h:98
> +    bool isRemote() const final { return true; }

This method can be private.
Comment 12 Cameron McCormack (:heycam) 2021-11-01 14:48:10 PDT
(In reply to Said Abou-Hallawa from comment #11)
> > Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp:1680
> > +    if (!canvasBase().buffer()->isRemote()) {
> 
> We have been trying to avoid adding this condition in WebCore. Can't we do
> this by checking whether 'c', which is the GraphicsContext of
> canvasBase().buffer(), is a recording GraphicsContext or not?
> 
> I think you can replace this condition by 
> 
> if (c->hasPlatformContext()) {
>     ...
>     return { };
> }

I had that in my original patch but Simon was not sure about it, so I changed it to this more direct check on the ImageBuffer.  I can change it back.
Comment 13 Wenson Hsieh 2021-11-01 16:16:22 PDT
(In reply to Cameron McCormack (:heycam) from comment #12)
> (In reply to Said Abou-Hallawa from comment #11)
> > > Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp:1680
> > > +    if (!canvasBase().buffer()->isRemote()) {
> > 
> > We have been trying to avoid adding this condition in WebCore. Can't we do
> > this by checking whether 'c', which is the GraphicsContext of
> > canvasBase().buffer(), is a recording GraphicsContext or not?
> > 
> > I think you can replace this condition by 
> > 
> > if (c->hasPlatformContext()) {
> >     ...
> >     return { };
> > }
> 
> I had that in my original patch but Simon was not sure about it, so I
> changed it to this more direct check on the ImageBuffer.  I can change it
> back.

Generally speaking, methods that explicitly reference the WebKit process model (e.g. the notion of "remote" or "proxy" objects) normally exist in the WebKit2 layer, with client layer hooks (on WebChromeClient, for instance) that WebCore code may use in order to delegate decision-making to things that know about the process model.

That said, there are some exceptions to this. But in general, ImageBuffer and its subclasses/backends have preserved this separation — it's also how we've (previously) avoided adding a method like `isRemote()` to image buffer.
Comment 14 Cameron McCormack (:heycam) 2021-11-04 20:35:42 PDT
Created attachment 443372 [details]
Patch for landing
Comment 15 EWS 2021-11-04 21:06:19 PDT
Committed r285333 (243896@main): <https://commits.webkit.org/243896@main>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 443372 [details].