tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit eec2ce52b9109e821ed96ff25ec11592147a7b30
parent cdfd617bd79bbc4649825f02c464de5b9e879f71
Author: Markus Stange <mstange.moz@gmail.com>
Date:   Wed,  8 Oct 2025 16:23:55 +0000

Bug 1992279 - Make WebRenderBridgeParent::FlushFramePresentation() wait until any native layer updates have arrived in the parent. r=bradwerth,nical,ipc-reviewers,nika

With the macOS GPU process enabled, if the parent process compositor thread is slow to
receive the CommitNativeLayerCommands message, and the parent process main thread wants
to wait for the composite to be finished, this achieves the right synchronous blocking.
Without this, the parent process main thread would proceed before the native layers
would be put into the CALayer tree, which would be a behavior change compared to the
no-GPU-process configuration.

Concretely, this patch has the following effects in the scenario where
the parent is slow to process the native layers:

- When opening a new window, the main thread now delays opening the window
  rather than proceeding immediately and showing a window with blank contents.
- When resizing a window, the UI rendering will no longer lag behind the
  window frame, instead the window frame will remain at its previous size until
  the frame has been processed.

The implementation is unfortunately is poking through quite a few layers of abstraction.
I'd be happy to hear any ideas for how to do this more cleanly.

The full "Sync paint" chain is the following:

```
Parent process main thread
  [PCompositorBridge::FlushRendering]
GPU process compositor thread
  [SynchronousTask::Wait in WebRenderAPI::WaitUntilPresentationFlushed]
GPU process renderer thread
  [PNativeLayerRemote::Flush]
Parent process compositor thread
```

Differential Revision: https://phabricator.services.mozilla.com/D267690

Diffstat:
Mgfx/layers/NativeLayer.h | 4++++
Mgfx/layers/NativeLayerRemoteParent.h | 2++
Mgfx/layers/NativeLayerRootRemoteMacChild.h | 4++++
Mgfx/layers/NativeLayerRootRemoteMacChild.mm | 4++++
Mgfx/layers/NativeLayerRootRemoteMacParent.h | 2++
Mgfx/layers/NativeLayerRootRemoteMacParent.mm | 6++++++
Mgfx/layers/ipc/PNativeLayerRemote.ipdl | 6++++++
Mgfx/layers/wr/WebRenderBridgeParent.cpp | 2+-
Mgfx/webrender_bindings/RenderCompositor.h | 6++++++
Mgfx/webrender_bindings/RenderCompositorNative.cpp | 4++++
Mgfx/webrender_bindings/RenderCompositorNative.h | 2++
Mgfx/webrender_bindings/WebRenderAPI.cpp | 9+++++++--
Mgfx/webrender_bindings/WebRenderAPI.h | 4++--
Mipc/ipdl/sync-messages.ini | 6++++--
14 files changed, 54 insertions(+), 7 deletions(-)

diff --git a/gfx/layers/NativeLayer.h b/gfx/layers/NativeLayer.h @@ -75,6 +75,10 @@ class NativeLayerRoot { // successful. virtual bool CommitToScreen() = 0; + // When called on a remote instance, synchronously wait until the other side + // has processed any previous commits. + virtual void WaitUntilCommitToScreenHasBeenProcessed() {} + // Returns a new NativeLayerRootSnapshotter that can be used to read back the // visual output of this NativeLayerRoot. The snapshotter needs to be // destroyed on the same thread that CreateSnapshotter() was called on. Only diff --git a/gfx/layers/NativeLayerRemoteParent.h b/gfx/layers/NativeLayerRemoteParent.h @@ -17,6 +17,8 @@ class NativeLayerRemoteParent : public PNativeLayerRemoteParent { virtual mozilla::ipc::IPCResult RecvRequestReadback(IntSize aSize, Shmem* const aPixels) = 0; + + virtual mozilla::ipc::IPCResult RecvFlush() = 0; }; } // namespace layers diff --git a/gfx/layers/NativeLayerRootRemoteMacChild.h b/gfx/layers/NativeLayerRootRemoteMacChild.h @@ -40,6 +40,10 @@ class NativeLayerRootRemoteMacChild final : public NativeLayerRoot { // successful. bool CommitToScreen() override; + // Send a sync message to the parent to make sure it has seen our layer + // commands message. + void WaitUntilCommitToScreenHasBeenProcessed() override; + RefPtr<NativeLayerRemoteChild> GetRemoteChild() { return mRemoteChild; } protected: diff --git a/gfx/layers/NativeLayerRootRemoteMacChild.mm b/gfx/layers/NativeLayerRootRemoteMacChild.mm @@ -137,6 +137,10 @@ bool NativeLayerRootRemoteMacChild::CommitToScreen() { return true; } +void NativeLayerRootRemoteMacChild::WaitUntilCommitToScreenHasBeenProcessed() { + mRemoteChild->SendFlush(); +} + void NativeLayerRootRemoteMacChild::CommitForSnapshot(CALayer* aRootCALayer) { [CATransaction begin]; [CATransaction setDisableActions:YES]; // disable cross-fade diff --git a/gfx/layers/NativeLayerRootRemoteMacParent.h b/gfx/layers/NativeLayerRootRemoteMacParent.h @@ -30,6 +30,8 @@ class NativeLayerRootRemoteMacParent final : public NativeLayerRemoteParent { mozilla::ipc::IPCResult RecvRequestReadback(IntSize aSize, Shmem* const aPixels) override; + mozilla::ipc::IPCResult RecvFlush() override; + protected: ~NativeLayerRootRemoteMacParent() = default; diff --git a/gfx/layers/NativeLayerRootRemoteMacParent.mm b/gfx/layers/NativeLayerRootRemoteMacParent.mm @@ -119,6 +119,12 @@ mozilla::ipc::IPCResult NativeLayerRootRemoteMacParent::RecvRequestReadback( return IPC_OK(); } +mozilla::ipc::IPCResult NativeLayerRootRemoteMacParent::RecvFlush() { + // No-op message; when this returns, the other side knows that any + // preceding messages have finished processing. + return IPC_OK(); +} + void NativeLayerRootRemoteMacParent::HandleCreateLayer(uint64_t aID, IntSize aSize, bool aOpaque) { diff --git a/gfx/layers/ipc/PNativeLayerRemote.ipdl b/gfx/layers/ipc/PNativeLayerRemote.ipdl @@ -78,6 +78,12 @@ sync protocol PNativeLayerRemote parent: async CommitNativeLayerCommands(NativeLayerCommand[] commands); sync RequestReadback(IntSize size) returns (Shmem pixels); + + // A synchronous no-op message, to make sure that any preceding + // CommitNativeLayerCommands messages have been processed. + // Used for synchronous widget-initiated paints, most importantly + // during window resize or when a new window is opened. + sync Flush(); }; } // layers diff --git a/gfx/layers/wr/WebRenderBridgeParent.cpp b/gfx/layers/wr/WebRenderBridgeParent.cpp @@ -1682,7 +1682,7 @@ void WebRenderBridgeParent::FlushFramePresentation() { // this effectively blocks on the render backend and renderer threads, // following the same codepath that WebRender takes to render and composite // a frame. - mApi->WaitFlushed(); + mApi->WaitUntilPresentationFlushed(); } void WebRenderBridgeParent::DisableNativeCompositor() { diff --git a/gfx/webrender_bindings/RenderCompositor.h b/gfx/webrender_bindings/RenderCompositor.h @@ -59,6 +59,12 @@ class RenderCompositor { // It might happen when rendering context is lost. virtual bool WaitForGPU() { return true; } + // On platforms where putting the frame onto the screen involves work in other + // processes, wait until those other processes have completed that work. + // Specifically, on macOS, we have to send surfaces to the parent process and + // it will put them into CALayers, and we want to wait until that's done. + virtual void WaitUntilPresentationFlushed() {} + // Check for and return the last completed frame. // @return the last (highest) completed RenderedFrameId virtual RenderedFrameId GetLastCompletedFrameId() { diff --git a/gfx/webrender_bindings/RenderCompositorNative.cpp b/gfx/webrender_bindings/RenderCompositorNative.cpp @@ -236,6 +236,10 @@ bool RenderCompositorNative::MaybeProcessScreenshotQueue() { return true; } +void RenderCompositorNative::WaitUntilPresentationFlushed() { + mNativeLayerRoot->WaitUntilCommitToScreenHasBeenProcessed(); +} + void RenderCompositorNative::CompositorBeginFrame() { mAddedLayers.Clear(); mAddedTilePixelCount = 0; diff --git a/gfx/webrender_bindings/RenderCompositorNative.h b/gfx/webrender_bindings/RenderCompositorNative.h @@ -59,6 +59,8 @@ class RenderCompositorNative : public RenderCompositor { bool MaybeGrabScreenshot(const gfx::IntSize& aWindowSize) override; bool MaybeProcessScreenshotQueue() override; + void WaitUntilPresentationFlushed() override; + // Interface for wr::Compositor void CompositorBeginFrame() override; void CompositorEndFrame() override; diff --git a/gfx/webrender_bindings/WebRenderAPI.cpp b/gfx/webrender_bindings/WebRenderAPI.cpp @@ -866,7 +866,7 @@ void WebRenderAPI::FlushSceneBuilder() { wr_api_flush_scene_builder(mDocHandle); } -void WebRenderAPI::WaitFlushed() { +void WebRenderAPI::WaitUntilPresentationFlushed() { class WaitFlushedEvent : public RendererEvent { public: explicit WaitFlushedEvent(layers::SynchronousTask* aTask) : mTask(aTask) { @@ -876,6 +876,11 @@ void WebRenderAPI::WaitFlushed() { MOZ_COUNTED_DTOR_OVERRIDE(WaitFlushedEvent) void Run(RenderThread& aRenderThread, WindowId aWindowId) override { + if (RendererOGL* renderer = aRenderThread.GetRenderer(aWindowId)) { + if (RenderCompositor* compositor = renderer->GetCompositor()) { + compositor->WaitUntilPresentationFlushed(); + } + } layers::AutoCompleteTask complete(mTask); } @@ -885,7 +890,7 @@ void WebRenderAPI::WaitFlushed() { layers::SynchronousTask* mTask; }; - layers::SynchronousTask task("WaitFlushed"); + layers::SynchronousTask task("WaitUntilPresentationFlushed"); auto event = MakeUnique<WaitFlushedEvent>(&task); // This event will be passed from wr_backend thread to renderer thread. That // implies that all frame data have been processed when the renderer runs this diff --git a/gfx/webrender_bindings/WebRenderAPI.h b/gfx/webrender_bindings/WebRenderAPI.h @@ -348,8 +348,8 @@ class WebRenderAPI final { wr::WebRenderAPI* aRootDocumentApi = nullptr); ~WebRenderAPI(); - // Should be used only for shutdown handling - void WaitFlushed(); + + void WaitUntilPresentationFlushed(); void UpdateDebugFlags(uint32_t aFlags); bool CheckIsRemoteTextureReady(layers::RemoteTextureInfoList* aList, diff --git a/ipc/ipdl/sync-messages.ini b/ipc/ipdl/sync-messages.ini @@ -274,6 +274,10 @@ description = test only description = bug 1377024 [PWebRenderBridge::SyncWithCompositor] description = WebRender equivalent for PCompositorBridge::SyncWithCompositor +[PNativeLayerRemote::Flush] +description = Used by the GPU process during window resizes and when opening new windows, for synchronous widget-initiated paints. +[PNativeLayerRemote::RequestReadback] +description = Only used during reftests. The GPU process synchronously requests the output pixels from the parent process. [PHal::GetCurrentBatteryInformation] description = legacy sync IPC - please add detailed description [PHal::GetCurrentNetworkInformation] @@ -368,8 +372,6 @@ description = Performs a signature on given data with a key corresponding to the [PRemoteQuotaObject::MaybeUpdateSize] description = This must be synchronous until quota file streams become non-blocking. The synchronous message is never used on the main thread or the PBackground thread or a DOM worker threed. -[PNativeLayerRemote::RequestReadback] -description = Only used during reftests. The GPU process synchronously requests the output pixels from the parent process. ############################################################# # AVOID ADDING NEW MESSAGES TO THIS FILE # # see comment at top of file #