Closed Bug 2060537 Opened 1 month ago Closed 1 month ago

Unnecessary render target switches when rendering picture cache tiles

Categories

(Core :: Graphics: WebRender, defect)

defect

Tracking

()

RESOLVED FIXED
155 Branch
Tracking Status
firefox155 --- fixed

People

(Reporter: jnicol, Assigned: jnicol, NeedInfo)

References

(Blocks 1 open bug)

Details

(Whiteboard: [fxpe])

Attachments

(1 file)

See this profile using Metal ANGLE: https://share.firefox.dev/3RIWMVF. This is scrolling up and down on a simple page, focused on Renderer::draw_frame().

There are quite a lot of calls to ANGLE's endEncoding(), which calls in to the metal driver.

The most common occurence of these fall within the glClear() call within draw_render_target(). I think these are expected and unavoidable: the glClear() is the first call that writes to a new render target, so we must end the encoding for the previous render target.

The next biggest chunk is within glInvalidateFramebuffer() at the end of draw_picture_cache_target(). Again, expected. Here we are invalidating the depth attachment at the end of the render pass. If we omit that, these samples will simply move somewhere else, and we'll additionally have to pay the cost of resolving the depth attachment contents to main memory.

The ones that stand out to me are within the glClear() call at the beginning of draw_picture_cache_target(). We should have already ended encoding for the previous picture cache target in glInvalidateFramebuffer(), so what encoding is there to end that has occurred since then?

The culprit is here. Before even calling draw_picture_cache_target() we call compositor.bind() to bind the compositor surface as the target. The C++ side then blits the dirty region from the previous frame's surface to the current frame's surface, so that webrender only has to render the current frame's dirty region.

That shouldn't matter, as ANGLE should be able to handle performing a clear after performing a blit to the same render target. The issue is that we use GLBlitHelper::BlitFramebufferToFramebuffer(), which binds/unbinds the FBO before and after blitting. So the sequence of calls looks like this:

  1. FBO X is bound
  2. compositor.bind()
    a) glBindFramebuffer(Y) // bind picture cache tile's framebuffer
    b) glBlitFramebuffer() // blit from previous frame's surface to current frame's surface
    c) glBindFramebuffer(X) // restore previously bound framebuffer
  3. draw_picture_cache_target()
    a) glBindFramebuffer(Y) // bind picture cache tile's framebuffer
    b) glClear() // ends encoding containing previous blit, starts new encoding

ANGLE isn't smart enough to realise that 2b and 3b are drawing to the same framebuffer. It just knows that the draw framebuffer was changed in between those calls, so step 3b ends the previous encoding and starts a new one.

Here's a profile with avoiding changing the framebuffer binding between the blit and the clear: https://share.firefox.dev/4cmhuBB

endEncoding() within draw_picture_cache_target()'s glClear() has fallen from ~50 samples to 19. I think the remaining samples must be cases where we did not perform any blits prior to clearing, and where this is the first picture cache tile being rendered, and therefore there was not already a glInvalidateFramebuffer to end the previous pass' encoding.

Component: Graphics → Graphics: WebRender

When webrender binds a native picture cache tile, the compositor will
in some cases blit regions from the tile surface used for the previous
frame to the tile surface being used for the current frame. This
ensures webrender only needs to render the current frame's dirty
region to the tile.

Both NativeLayerCA and NativeLayerWayland do so by calling
GLBlitHelper::BlitFramebufferToFramebuffer(). This binds the required
draw and read framebuffers, and then restores the previous bindings
after performing the blit. draw_picture_cache_target() will then
immediately bind the tile surface's FBO again, before rendering the
tile.

On Metal ANGLE, we observe that the first draw performed in
draw_picture_cache_target() (usually glClear()) causes ANGLE to end
encoding the previous command buffer (containing the blits) and start
encoding a new one. This is because the draw framebuffer was changed
in between the blits and the clear, even though it was immediately
changed back again.

We prevent this by manually binding the required draw and read
framebuffers when performing the partial update blits, and calling
GLBlitHelper::BlitFramebuffer() which does not change the
framebuffer bindings. This ensures the same draw framebuffer remains
bound throughout, allowing ANGLE to use a single command buffer per
tile.

Pushed by sstanca@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/7dc8e943276d https://hg.mozilla.org/integration/autoland/rev/bd57b971b1e2 Revert "Bug 2060537 - Avoid unbinding FBO after partial update blits. r=gfx-reviewers,bradwerth" for causing build bustages in NativeLayerWayland.cpp.

Backed out for causing build bustages in NativeLayerWayland.cpp.
Backout link
Push with failures
Failure log(s)

Flags: needinfo?(jnicol)
Status: NEW → RESOLVED
Closed: 1 month ago
Resolution: --- → FIXED
Target Milestone: --- → 155 Branch
QA Whiteboard: [qa-triage-done-c156/b155]
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: