Open Bug 1831609 Opened 1 year ago Updated 2 months ago

Canvas drawing outside requestAnimationFrame callbacks leads to overproduction (needs more backpressure) - paints become slower and slower

Categories

(Core :: Graphics: Canvas2D, defect)

All
macOS
defect

Tracking

()

Performance Impact low

People

(Reporter: mstange, Unassigned)

References

(Blocks 1 open bug)

Details

(Keywords: perf:responsiveness)

Attachments

(1 file, 1 obsolete file)

I originally noticed this while interacting with the profiler flame graph, for example when scrolling up in this profile: https://share.firefox.dev/3qi6hwO

Steps to reproduce:

  1. Load the attached testcase.
  2. Scroll over the canvas, up and down, repeatedly until drawing gets really delayed.

Expected results:
Drawing latency should not increase. It should be equally slow at the start and at the end of the scroll gesture.

Actual results:
Drawing latency gets worse over the course of the scroll gesture.

Here's a profile which I captured when the problem was happening in the profiler: https://share.firefox.dev/3HpTX3v
You can see that the CanvasRenderer thread has fallen way behind. But the main thread keeps adding more canvas work during the wheel event handler.

The current backpressure mechanism only delays refresh ticks. But wheel events are fired outside of refresh ticks. There needs to be another backpressure mechanism (e.g. during the canvas methods themselves) for non-refreshtick work.

(Bonus badness visible in the profile: There's sync IPC during GC in ~DrawTargetWebGL, visible in two of the background processes.)

Attached file testcase (obsolete) —
Attached file testcase
Attachment #9331877 - Attachment is obsolete: true
Severity: -- → S3
Performance Impact: --- → ?

Ah, this is an interesting case. There is nothing else in the main thread of the content process, so even very low priority input events can be processed, as one would expect.
We could suspend InputTaskManager temporarily when we know there will be a tick a bit later. But do we know in this case?
Hmm, I wonder what happens if we suspended input event handling until the next DidComposite or something like that.

But I can't reproduce this on Linux, but on Windows I can see max 2 wheel event per animation frame.

Performance Impact: ? → low
Blocks: 1879824

This also affects canvas animations running inside setTimeout callbacks, see bug 1879824 for an example.

Summary: Canvas drawing during wheel events can fall behind (needs more backpressure) → Canvas drawing outside requestAnimationFrame callbacks leads to overproduction (needs more backpressure) - paints become slower and slower

Bob, do we still have any limit on the number of bytes we have in-flight for canvas drawing? I wonder if we can just pick some large but not infinite size and be in a better place.

Flags: needinfo?(bobowencode)

(In reply to Markus Stange [:mstange] from comment #5)

Bob, do we still have any limit on the number of bytes we have in-flight for canvas drawing? I wonder if we can just pick some large but not infinite size and be in a better place.

Yes we added a maximum number of buffers in bug 1872705.
Currently 256 x 32K. (8MB)

Flags: needinfo?(bobowencode)

Ah, nice, that probably explains why the testcase I attached to this bug runs much better today than when I filed this bug.

Then I wonder how the overproduction problem shown in the profiles from bug 1879824 comment 7 can be addressed. Do we have to dynamically adapt the maximum size based on the observed consumption rate?

Depends on: 1872705

(In reply to Markus Stange [:mstange] from comment #7)

Ah, nice, that probably explains why the testcase I attached to this bug runs much better today than when I filed this bug.

Then I wonder how the overproduction problem shown in the profiles from bug 1879824 comment 7 can be addressed. Do we have to dynamically adapt the maximum size based on the observed consumption rate?

Possibly, if I drop the max buffers to 64 or even 32 for bug 1879824, the animation appears to run more smoothly.
It might be good if we had a more intelligent way of throttling the JS in these circumstances, instead of stopping the main thread in its tracks.

I can't work out why it is so slow.
The CPU is doing a fair amount of work, but doesn't appear to be maxing out on one thread.
The GPU also doesn't appear that taxed, with a small amount on one of the smaller engines in process explorer.
It mainly seems to be sitting in the function FillRoundedRectangleTessellator::SendGeometry and below, most of that in RoundedRectangleTessellationSink::AddBeziers and below.

Update: to future readers, looks like the reasons are set out in bug 1879824 comment 11 and bugs that it links to.

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

Attachment

General

Created:
Updated:
Size: