summaryrefslogtreecommitdiffstats
path: root/gfx/layers/wr/WebRenderBridgeParent.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /gfx/layers/wr/WebRenderBridgeParent.cpp
parentInitial commit. (diff)
downloadfirefox-e51783d008170d9ab27d25da98ca3a38b0a41b67.tar.xz
firefox-e51783d008170d9ab27d25da98ca3a38b0a41b67.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/layers/wr/WebRenderBridgeParent.cpp')
-rw-r--r--gfx/layers/wr/WebRenderBridgeParent.cpp2886
1 files changed, 2886 insertions, 0 deletions
diff --git a/gfx/layers/wr/WebRenderBridgeParent.cpp b/gfx/layers/wr/WebRenderBridgeParent.cpp
new file mode 100644
index 0000000000..789476cb6b
--- /dev/null
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -0,0 +1,2886 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/layers/WebRenderBridgeParent.h"
+
+#include "CompositableHost.h"
+#include "gfxEnv.h"
+#include "gfxPlatform.h"
+#include "gfxOTSUtils.h"
+#include "GeckoProfiler.h"
+#include "GLContext.h"
+#include "GLContextProvider.h"
+#include "GLLibraryLoader.h"
+#include "nsExceptionHandler.h"
+#include "mozilla/Range.h"
+#include "mozilla/EnumeratedRange.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_webgl.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/GPUParent.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/layers/AnimationHelper.h"
+#include "mozilla/layers/APZSampler.h"
+#include "mozilla/layers/APZUpdater.h"
+#include "mozilla/layers/Compositor.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorAnimationStorage.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/CompositorVsyncScheduler.h"
+#include "mozilla/layers/ImageBridgeParent.h"
+#include "mozilla/layers/ImageDataSerializer.h"
+#include "mozilla/layers/IpcResourceUpdateQueue.h"
+#include "mozilla/layers/OMTASampler.h"
+#include "mozilla/layers/RemoteTextureMap.h"
+#include "mozilla/layers/SharedSurfacesParent.h"
+#include "mozilla/layers/TextureHost.h"
+#include "mozilla/layers/AsyncImagePipelineManager.h"
+#include "mozilla/layers/UiCompositorControllerParent.h"
+#include "mozilla/layers/WebRenderImageHost.h"
+#include "mozilla/layers/WebRenderTextureHost.h"
+#include "mozilla/ProfilerMarkerTypes.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Unused.h"
+#include "mozilla/webrender/RenderTextureHostSWGL.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/widget/CompositorWidget.h"
+
+#ifdef XP_WIN
+# include "mozilla/gfx/DeviceManagerDx.h"
+# include "mozilla/widget/WinCompositorWidget.h"
+#endif
+#if defined(MOZ_WIDGET_GTK)
+# include "mozilla/widget/GtkCompositorWidget.h"
+#endif
+
+bool is_in_main_thread() { return NS_IsMainThread(); }
+
+bool is_in_compositor_thread() {
+ return mozilla::layers::CompositorThreadHolder::IsInCompositorThread();
+}
+
+bool is_in_render_thread() {
+ return mozilla::wr::RenderThread::IsInRenderThread();
+}
+
+bool gecko_profiler_thread_is_being_profiled() {
+ return profiler_thread_is_being_profiled(ThreadProfilingFeatures::Any);
+}
+
+bool is_glcontext_gles(void* const glcontext_ptr) {
+ MOZ_RELEASE_ASSERT(glcontext_ptr);
+ return reinterpret_cast<mozilla::gl::GLContext*>(glcontext_ptr)->IsGLES();
+}
+
+bool is_glcontext_angle(void* glcontext_ptr) {
+ MOZ_ASSERT(glcontext_ptr);
+
+ mozilla::gl::GLContext* glcontext =
+ reinterpret_cast<mozilla::gl::GLContext*>(glcontext_ptr);
+ if (!glcontext) {
+ return false;
+ }
+ return glcontext->IsANGLE();
+}
+
+const char* gfx_wr_resource_path_override() {
+ return gfxPlatform::WebRenderResourcePathOverride();
+}
+
+bool gfx_wr_use_optimized_shaders() {
+ return mozilla::gfx::gfxVars::UseWebRenderOptimizedShaders();
+}
+
+void gfx_critical_note(const char* msg) { gfxCriticalNote << msg; }
+
+void gfx_critical_error(const char* msg) { gfxCriticalError() << msg; }
+
+void gecko_printf_stderr_output(const char* msg) { printf_stderr("%s\n", msg); }
+
+void* get_proc_address_from_glcontext(void* glcontext_ptr,
+ const char* procname) {
+ mozilla::gl::GLContext* glcontext =
+ reinterpret_cast<mozilla::gl::GLContext*>(glcontext_ptr);
+ MOZ_ASSERT(glcontext);
+ if (!glcontext) {
+ return nullptr;
+ }
+ const auto& loader = glcontext->GetSymbolLoader();
+ MOZ_ASSERT(loader);
+
+ const auto ret = loader->GetProcAddress(procname);
+ return reinterpret_cast<void*>(ret);
+}
+
+static CrashReporter::Annotation FromWrCrashAnnotation(
+ mozilla::wr::CrashAnnotation aAnnotation) {
+ switch (aAnnotation) {
+ case mozilla::wr::CrashAnnotation::CompileShader:
+ return CrashReporter::Annotation::GraphicsCompileShader;
+ case mozilla::wr::CrashAnnotation::DrawShader:
+ return CrashReporter::Annotation::GraphicsDrawShader;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unhandled annotation!");
+ return CrashReporter::Annotation::Count;
+ }
+}
+
+extern "C" {
+
+void gfx_wr_set_crash_annotation(mozilla::wr::CrashAnnotation aAnnotation,
+ const char* aValue) {
+ MOZ_ASSERT(aValue);
+
+ auto annotation = FromWrCrashAnnotation(aAnnotation);
+ if (annotation == CrashReporter::Annotation::Count) {
+ return;
+ }
+
+ CrashReporter::AnnotateCrashReport(annotation, nsDependentCString(aValue));
+}
+
+void gfx_wr_clear_crash_annotation(mozilla::wr::CrashAnnotation aAnnotation) {
+ auto annotation = FromWrCrashAnnotation(aAnnotation);
+ if (annotation == CrashReporter::Annotation::Count) {
+ return;
+ }
+
+ CrashReporter::RemoveCrashReportAnnotation(annotation);
+}
+}
+
+namespace mozilla::layers {
+
+using namespace mozilla::gfx;
+
+LazyLogModule gWebRenderBridgeParentLog("WebRenderBridgeParent");
+#define LOG(...) \
+ MOZ_LOG(gWebRenderBridgeParentLog, LogLevel::Debug, (__VA_ARGS__))
+
+class ScheduleObserveLayersUpdate : public wr::NotificationHandler {
+ public:
+ ScheduleObserveLayersUpdate(RefPtr<CompositorBridgeParentBase> aBridge,
+ LayersId aLayersId, bool aIsActive)
+ : mBridge(std::move(aBridge)),
+ mLayersId(aLayersId),
+ mIsActive(aIsActive) {}
+
+ void Notify(wr::Checkpoint) override {
+ CompositorThread()->Dispatch(NewRunnableMethod<LayersId, int>(
+ "ObserveLayersUpdate", mBridge,
+ &CompositorBridgeParentBase::ObserveLayersUpdate, mLayersId,
+ mIsActive));
+ }
+
+ protected:
+ RefPtr<CompositorBridgeParentBase> mBridge;
+ LayersId mLayersId;
+ bool mIsActive;
+};
+
+class SceneBuiltNotification : public wr::NotificationHandler {
+ public:
+ SceneBuiltNotification(WebRenderBridgeParent* aParent, wr::Epoch aEpoch,
+ TimeStamp aTxnStartTime)
+ : mParent(aParent), mEpoch(aEpoch), mTxnStartTime(aTxnStartTime) {}
+
+ void Notify(wr::Checkpoint) override {
+ auto startTime = this->mTxnStartTime;
+ RefPtr<WebRenderBridgeParent> parent = mParent;
+ wr::Epoch epoch = mEpoch;
+ CompositorThread()->Dispatch(NS_NewRunnableFunction(
+ "SceneBuiltNotificationRunnable", [parent, epoch, startTime]() {
+ auto endTime = TimeStamp::Now();
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ PROFILER_MARKER("CONTENT_FULL_PAINT_TIME", GRAPHICS,
+ MarkerTiming::Interval(startTime, endTime),
+ ContentBuildMarker);
+ }
+ mozilla::glean::gfx_content::full_paint_time.AccumulateRawDuration(
+ endTime - startTime);
+ parent->NotifySceneBuiltForEpoch(epoch, endTime);
+ }));
+ }
+
+ protected:
+ RefPtr<WebRenderBridgeParent> mParent;
+ wr::Epoch mEpoch;
+ TimeStamp mTxnStartTime;
+};
+
+class WebRenderBridgeParent::ScheduleSharedSurfaceRelease final
+ : public wr::NotificationHandler {
+ public:
+ explicit ScheduleSharedSurfaceRelease(WebRenderBridgeParent* aWrBridge)
+ : mWrBridge(aWrBridge), mSurfaces(20) {}
+
+ ~ScheduleSharedSurfaceRelease() override {
+ if (!mSurfaces.IsEmpty()) {
+ MOZ_ASSERT_UNREACHABLE("Unreleased surfaces!");
+ gfxCriticalNote << "ScheduleSharedSurfaceRelease destroyed non-empty";
+ NotifyInternal(/* aFromCheckpoint */ false);
+ }
+ }
+
+ void Add(const wr::ImageKey& aKey, const wr::ExternalImageId& aId) {
+ mSurfaces.AppendElement(wr::ExternalImageKeyPair{aKey, aId});
+ }
+
+ void Notify(wr::Checkpoint) override {
+ NotifyInternal(/* aFromCheckpoint */ true);
+ }
+
+ private:
+ void NotifyInternal(bool aFromCheckpoint) {
+ CompositorThread()->Dispatch(
+ NewRunnableMethod<nsTArray<wr::ExternalImageKeyPair>, bool>(
+ "ObserveSharedSurfaceRelease", mWrBridge,
+ &WebRenderBridgeParent::ObserveSharedSurfaceRelease,
+ std::move(mSurfaces), aFromCheckpoint));
+ }
+
+ RefPtr<WebRenderBridgeParent> mWrBridge;
+ nsTArray<wr::ExternalImageKeyPair> mSurfaces;
+};
+
+class MOZ_STACK_CLASS AutoWebRenderBridgeParentAsyncMessageSender final {
+ public:
+ explicit AutoWebRenderBridgeParentAsyncMessageSender(
+ WebRenderBridgeParent* aWebRenderBridgeParent,
+ nsTArray<OpDestroy>* aDestroyActors = nullptr)
+ : mWebRenderBridgeParent(aWebRenderBridgeParent),
+ mActorsToDestroy(aDestroyActors) {
+ mWebRenderBridgeParent->SetAboutToSendAsyncMessages();
+ }
+
+ ~AutoWebRenderBridgeParentAsyncMessageSender() {
+ mWebRenderBridgeParent->SendPendingAsyncMessages();
+ if (mActorsToDestroy) {
+ // Destroy the actors after sending the async messages because the latter
+ // may contain references to some actors.
+ for (const auto& op : *mActorsToDestroy) {
+ mWebRenderBridgeParent->DestroyActor(op);
+ }
+ }
+ }
+
+ private:
+ WebRenderBridgeParent* mWebRenderBridgeParent;
+ nsTArray<OpDestroy>* mActorsToDestroy;
+};
+
+WebRenderBridgeParent::WebRenderBridgeParent(
+ CompositorBridgeParentBase* aCompositorBridge,
+ const wr::PipelineId& aPipelineId, widget::CompositorWidget* aWidget,
+ CompositorVsyncScheduler* aScheduler, RefPtr<wr::WebRenderAPI>&& aApi,
+ RefPtr<AsyncImagePipelineManager>&& aImageMgr, TimeDuration aVsyncRate)
+ : mCompositorBridge(aCompositorBridge),
+ mPipelineId(aPipelineId),
+ mWidget(aWidget),
+ mApi(aApi),
+ mAsyncImageManager(aImageMgr),
+ mCompositorScheduler(aScheduler),
+ mVsyncRate(aVsyncRate),
+ mWrEpoch{0},
+ mIdNamespace(aApi->GetNamespace()),
+#if defined(MOZ_WIDGET_ANDROID)
+ mScreenPixelsTarget(nullptr),
+#endif
+ mBlobTileSize(256),
+ mSkippedCompositeReasons(wr::RenderReasons::NONE),
+ mDestroyed(false),
+ mIsFirstPaint(true),
+ mPendingScrollPayloads("WebRenderBridgeParent::mPendingScrollPayloads") {
+ MOZ_ASSERT(mAsyncImageManager);
+ LOG("WebRenderBridgeParent::WebRenderBridgeParent() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ mAsyncImageManager->AddPipeline(mPipelineId, this);
+ if (IsRootWebRenderBridgeParent()) {
+ MOZ_ASSERT(!mCompositorScheduler);
+ mCompositorScheduler = new CompositorVsyncScheduler(this, mWidget);
+ UpdateDebugFlags();
+ UpdateQualitySettings();
+ UpdateProfilerUI();
+ UpdateParameters();
+ // Start with the cached bool parameter bits inverted so that we update them
+ // all.
+ mBoolParameterBits = ~gfxVars::WebRenderBoolParameters();
+ UpdateBoolParameters();
+ }
+ mRemoteTextureTxnScheduler =
+ RemoteTextureTxnScheduler::Create(aCompositorBridge);
+}
+
+WebRenderBridgeParent::WebRenderBridgeParent(const wr::PipelineId& aPipelineId,
+ nsCString&& aError)
+ : mCompositorBridge(nullptr),
+ mPipelineId(aPipelineId),
+ mWrEpoch{0},
+ mIdNamespace{0},
+ mInitError(aError),
+ mDestroyed(true),
+ mIsFirstPaint(false),
+ mPendingScrollPayloads("WebRenderBridgeParent::mPendingScrollPayloads") {
+ LOG("WebRenderBridgeParent::WebRenderBridgeParent() PipelineId %" PRIx64 "",
+ wr::AsUint64(mPipelineId));
+}
+
+WebRenderBridgeParent::~WebRenderBridgeParent() {
+ LOG("WebRenderBridgeParent::WebRenderBridgeParent() PipelineId %" PRIx64 "",
+ wr::AsUint64(mPipelineId));
+}
+
+/* static */
+WebRenderBridgeParent* WebRenderBridgeParent::CreateDestroyed(
+ const wr::PipelineId& aPipelineId, nsCString&& aError) {
+ return new WebRenderBridgeParent(aPipelineId, std::move(aError));
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvEnsureConnected(
+ TextureFactoryIdentifier* aTextureFactoryIdentifier,
+ MaybeIdNamespace* aMaybeIdNamespace, nsCString* aError) {
+ if (mDestroyed) {
+ *aTextureFactoryIdentifier =
+ TextureFactoryIdentifier(LayersBackend::LAYERS_NONE);
+ *aMaybeIdNamespace = Nothing();
+ if (mInitError.IsEmpty()) {
+ // Got destroyed after we initialized but before the handshake finished?
+ aError->AssignLiteral("FEATURE_FAILURE_WEBRENDER_INITIALIZE_RACE");
+ } else {
+ *aError = std::move(mInitError);
+ }
+ return IPC_OK();
+ }
+
+ MOZ_ASSERT(mIdNamespace.mHandle != 0);
+ *aTextureFactoryIdentifier = GetTextureFactoryIdentifier();
+ *aMaybeIdNamespace = Some(mIdNamespace);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvShutdown() {
+ return HandleShutdown();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvShutdownSync() {
+ return HandleShutdown();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::HandleShutdown() {
+ Destroy();
+ IProtocol* mgr = Manager();
+ if (!Send__delete__(this)) {
+ return IPC_FAIL_NO_REASON(mgr);
+ }
+ return IPC_OK();
+}
+
+void WebRenderBridgeParent::Destroy() {
+ if (mDestroyed) {
+ return;
+ }
+ LOG("WebRenderBridgeParent::Destroy() PipelineId %" PRIx64 " Id %" PRIx64
+ " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ mDestroyed = true;
+ if (mRemoteTextureTxnScheduler) {
+ mRemoteTextureTxnScheduler = nullptr;
+ }
+ if (mWebRenderBridgeRef) {
+ // Break mutual reference
+ mWebRenderBridgeRef->Clear();
+ mWebRenderBridgeRef = nullptr;
+ }
+ for (const auto& entry : mCompositables) {
+ entry.second->OnReleased();
+ }
+ mCompositables.clear();
+ ClearResources();
+}
+
+struct WROTSAlloc {
+ wr::Vec<uint8_t> mVec;
+
+ void* Grow(void* aPtr, size_t aLength) {
+ if (aLength > mVec.Capacity()) {
+ mVec.Reserve(aLength - mVec.Capacity());
+ }
+ return mVec.inner.data;
+ }
+ wr::Vec<uint8_t> ShrinkToFit(void* aPtr, size_t aLength) {
+ wr::Vec<uint8_t> result(std::move(mVec));
+ result.inner.length = aLength;
+ return result;
+ }
+ void Free(void* aPtr) {}
+};
+
+static bool ReadRawFont(const OpAddRawFont& aOp, wr::ShmSegmentsReader& aReader,
+ wr::TransactionBuilder& aUpdates) {
+ wr::Vec<uint8_t> sourceBytes;
+ Maybe<Range<uint8_t>> ptr =
+ aReader.GetReadPointerOrCopy(aOp.bytes(), sourceBytes);
+ if (ptr.isNothing()) {
+ gfxCriticalNote << "No read pointer from reader for sanitizing font "
+ << aOp.key().mHandle;
+ return false;
+ }
+ Range<uint8_t>& source = ptr.ref();
+ // Attempt to sanitize the font before passing it along for updating.
+ // Ensure that we're not strict here about font types, since any font that
+ // failed generating a descriptor might end up here as raw font data.
+ size_t lengthHint = gfxOTSContext::GuessSanitizedFontSize(
+ source.begin().get(), source.length(), false);
+ if (!lengthHint) {
+ gfxCriticalNote << "Could not determine font type for sanitizing font "
+ << aOp.key().mHandle;
+ return false;
+ }
+ gfxOTSExpandingMemoryStream<WROTSAlloc> output(lengthHint);
+ gfxOTSContext otsContext;
+ if (!otsContext.Process(&output, source.begin().get(), source.length())) {
+ gfxCriticalNote << "Failed sanitizing font " << aOp.key().mHandle;
+ return false;
+ }
+ wr::Vec<uint8_t> bytes = output.forget();
+
+ aUpdates.AddRawFont(aOp.key(), bytes, aOp.fontIndex());
+ return true;
+}
+
+bool WebRenderBridgeParent::UpdateResources(
+ const nsTArray<OpUpdateResource>& aResourceUpdates,
+ const nsTArray<RefCountedShmem>& aSmallShmems,
+ const nsTArray<ipc::Shmem>& aLargeShmems,
+ wr::TransactionBuilder& aUpdates) {
+ wr::ShmSegmentsReader reader(aSmallShmems, aLargeShmems);
+ UniquePtr<ScheduleSharedSurfaceRelease> scheduleRelease;
+
+ while (GPUParent::MaybeFlushMemory()) {
+ // If the GPU process has memory pressure, preemptively unmap some of our
+ // shared memory images. If we are in the parent process, the expiration
+ // tracker itself will listen for the memory pressure event.
+ if (!SharedSurfacesParent::AgeAndExpireOneGeneration()) {
+ break;
+ }
+ }
+
+ bool success = true;
+ for (const auto& cmd : aResourceUpdates) {
+ switch (cmd.type()) {
+ case OpUpdateResource::TOpAddImage: {
+ const auto& op = cmd.get_OpAddImage();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale image key (add)!");
+ break;
+ }
+
+ wr::Vec<uint8_t> bytes;
+ if (reader.Read(op.bytes(), bytes)) {
+ aUpdates.AddImage(op.key(), op.descriptor(), bytes);
+ } else {
+ gfxCriticalNote << "TOpAddImage failed";
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpUpdateImage: {
+ const auto& op = cmd.get_OpUpdateImage();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale image key (update)!");
+ break;
+ }
+
+ wr::Vec<uint8_t> bytes;
+ if (reader.Read(op.bytes(), bytes)) {
+ aUpdates.UpdateImageBuffer(op.key(), op.descriptor(), bytes);
+ } else {
+ gfxCriticalNote << "TOpUpdateImage failed";
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpAddBlobImage: {
+ const auto& op = cmd.get_OpAddBlobImage();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale blob image key (add)!");
+ break;
+ }
+
+ wr::Vec<uint8_t> bytes;
+ if (reader.Read(op.bytes(), bytes)) {
+ aUpdates.AddBlobImage(op.key(), op.descriptor(), mBlobTileSize, bytes,
+ wr::ToDeviceIntRect(op.visibleRect()));
+ } else {
+ gfxCriticalNote << "TOpAddBlobImage failed";
+ success = false;
+ }
+
+ break;
+ }
+ case OpUpdateResource::TOpUpdateBlobImage: {
+ const auto& op = cmd.get_OpUpdateBlobImage();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale blob image key (update)!");
+ break;
+ }
+
+ wr::Vec<uint8_t> bytes;
+ if (reader.Read(op.bytes(), bytes)) {
+ aUpdates.UpdateBlobImage(op.key(), op.descriptor(), bytes,
+ wr::ToDeviceIntRect(op.visibleRect()),
+ wr::ToLayoutIntRect(op.dirtyRect()));
+ } else {
+ gfxCriticalNote << "TOpUpdateBlobImage failed";
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpSetBlobImageVisibleArea: {
+ const auto& op = cmd.get_OpSetBlobImageVisibleArea();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale blob image key (visible)!");
+ break;
+ }
+ aUpdates.SetBlobImageVisibleArea(op.key(),
+ wr::ToDeviceIntRect(op.area()));
+ break;
+ }
+ case OpUpdateResource::TOpAddSharedExternalImage: {
+ const auto& op = cmd.get_OpAddSharedExternalImage();
+ // gfxCriticalNote is called on error
+ if (!AddSharedExternalImage(op.externalImageId(), op.key(), aUpdates)) {
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpPushExternalImageForTexture: {
+ const auto& op = cmd.get_OpPushExternalImageForTexture();
+ CompositableTextureHostRef texture;
+ texture = TextureHost::AsTextureHost(op.texture().AsParent());
+ // gfxCriticalNote is called on error
+ if (!PushExternalImageForTexture(op.externalImageId(), op.key(),
+ texture, op.isUpdate(), aUpdates)) {
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpUpdateSharedExternalImage: {
+ const auto& op = cmd.get_OpUpdateSharedExternalImage();
+ // gfxCriticalNote is called on error
+ if (!UpdateSharedExternalImage(op.externalImageId(), op.key(),
+ op.dirtyRect(), aUpdates,
+ scheduleRelease)) {
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpAddRawFont: {
+ if (!ReadRawFont(cmd.get_OpAddRawFont(), reader, aUpdates)) {
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpAddFontDescriptor: {
+ const auto& op = cmd.get_OpAddFontDescriptor();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale font key (add descriptor)!");
+ break;
+ }
+
+ wr::Vec<uint8_t> bytes;
+ if (reader.Read(op.bytes(), bytes)) {
+ aUpdates.AddFontDescriptor(op.key(), bytes, op.fontIndex());
+ } else {
+ gfxCriticalNote << "TOpAddFontDescriptor failed";
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpAddFontInstance: {
+ const auto& op = cmd.get_OpAddFontInstance();
+ if (!MatchesNamespace(op.instanceKey()) ||
+ !MatchesNamespace(op.fontKey())) {
+ MOZ_ASSERT_UNREACHABLE("Stale font key (add instance)!");
+ break;
+ }
+
+ wr::Vec<uint8_t> variations;
+ if (reader.Read(op.variations(), variations)) {
+ aUpdates.AddFontInstance(op.instanceKey(), op.fontKey(),
+ op.glyphSize(), op.options().ptrOr(nullptr),
+ op.platformOptions().ptrOr(nullptr),
+ variations);
+ } else {
+ gfxCriticalNote << "TOpAddFontInstance failed";
+ success = false;
+ }
+ break;
+ }
+ case OpUpdateResource::TOpDeleteImage: {
+ const auto& op = cmd.get_OpDeleteImage();
+ if (!MatchesNamespace(op.key())) {
+ // TODO(aosmond): We should also assert here, but the callers are less
+ // careful about checking when cleaning up their old keys. We should
+ // perform an audit on image key usage.
+ break;
+ }
+
+ DeleteImage(op.key(), aUpdates);
+ break;
+ }
+ case OpUpdateResource::TOpDeleteBlobImage: {
+ const auto& op = cmd.get_OpDeleteBlobImage();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale blob image key (delete)!");
+ break;
+ }
+
+ aUpdates.DeleteBlobImage(op.key());
+ break;
+ }
+ case OpUpdateResource::TOpDeleteFont: {
+ const auto& op = cmd.get_OpDeleteFont();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale font key (delete)!");
+ break;
+ }
+
+ aUpdates.DeleteFont(op.key());
+ break;
+ }
+ case OpUpdateResource::TOpDeleteFontInstance: {
+ const auto& op = cmd.get_OpDeleteFontInstance();
+ if (!MatchesNamespace(op.key())) {
+ MOZ_ASSERT_UNREACHABLE("Stale font instance key (delete)!");
+ break;
+ }
+
+ aUpdates.DeleteFontInstance(op.key());
+ break;
+ }
+ case OpUpdateResource::T__None:
+ break;
+ }
+ }
+
+ if (scheduleRelease) {
+ // When software WR is enabled, shared surfaces are read during rendering
+ // rather than copied to the texture cache.
+ wr::Checkpoint when = mApi->GetBackendType() == WebRenderBackend::SOFTWARE
+ ? wr::Checkpoint::FrameRendered
+ : wr::Checkpoint::FrameTexturesUpdated;
+ aUpdates.Notify(when, std::move(scheduleRelease));
+ }
+
+ MOZ_ASSERT(success);
+ return success;
+}
+
+bool WebRenderBridgeParent::AddSharedExternalImage(
+ wr::ExternalImageId aExtId, wr::ImageKey aKey,
+ wr::TransactionBuilder& aResources) {
+ if (!MatchesNamespace(aKey)) {
+ MOZ_ASSERT_UNREACHABLE("Stale shared external image key (add)!");
+ return true;
+ }
+
+ auto key = wr::AsUint64(aKey);
+ auto it = mSharedSurfaceIds.find(key);
+ if (it != mSharedSurfaceIds.end()) {
+ gfxCriticalNote << "Readding known shared surface: " << key;
+ return false;
+ }
+
+ RefPtr<DataSourceSurface> dSurf = SharedSurfacesParent::Acquire(aExtId);
+ if (!dSurf) {
+ gfxCriticalNote
+ << "DataSourceSurface of SharedSurfaces does not exist for extId:"
+ << wr::AsUint64(aExtId);
+ return false;
+ }
+
+ mSharedSurfaceIds.insert(std::make_pair(key, aExtId));
+
+ // Prefer raw buffers, unless our backend requires native textures.
+ IntSize surfaceSize = dSurf->GetSize();
+ TextureHost::NativeTexturePolicy policy =
+ TextureHost::BackendNativeTexturePolicy(mApi->GetBackendType(),
+ surfaceSize);
+ auto imageType =
+ policy == TextureHost::NativeTexturePolicy::REQUIRE
+ ? wr::ExternalImageType::TextureHandle(wr::ImageBufferKind::Texture2D)
+ : wr::ExternalImageType::Buffer();
+ wr::ImageDescriptor descriptor(surfaceSize, dSurf->Stride(),
+ dSurf->GetFormat());
+ aResources.AddExternalImage(aKey, descriptor, aExtId, imageType, 0);
+ return true;
+}
+
+bool WebRenderBridgeParent::PushExternalImageForTexture(
+ wr::ExternalImageId aExtId, wr::ImageKey aKey, TextureHost* aTexture,
+ bool aIsUpdate, wr::TransactionBuilder& aResources) {
+ if (!MatchesNamespace(aKey)) {
+ MOZ_ASSERT_UNREACHABLE("Stale texture external image key!");
+ return true;
+ }
+
+ if (!aTexture) {
+ gfxCriticalNote << "TextureHost does not exist for extId:"
+ << wr::AsUint64(aExtId);
+ return false;
+ }
+
+ auto op = aIsUpdate ? TextureHost::UPDATE_IMAGE : TextureHost::ADD_IMAGE;
+ WebRenderTextureHost* wrTexture = aTexture->AsWebRenderTextureHost();
+ if (wrTexture) {
+ Range<wr::ImageKey> keys(&aKey, 1);
+ wrTexture->PushResourceUpdates(aResources, op, keys,
+ wrTexture->GetExternalImageKey());
+ auto it = mTextureHosts.find(wr::AsUint64(aKey));
+ MOZ_ASSERT((it == mTextureHosts.end() && !aIsUpdate) ||
+ (it != mTextureHosts.end() && aIsUpdate));
+ if (it != mTextureHosts.end()) {
+ // Release Texture if it exists.
+ ReleaseTextureOfImage(aKey);
+ }
+ mTextureHosts.emplace(wr::AsUint64(aKey),
+ CompositableTextureHostRef(aTexture));
+ return true;
+ }
+
+ RefPtr<DataSourceSurface> dSurf = aTexture->GetAsSurface();
+ if (!dSurf) {
+ gfxCriticalNote
+ << "TextureHost does not return DataSourceSurface for extId:"
+ << wr::AsUint64(aExtId);
+ return false;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ if (!dSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
+ gfxCriticalNote << "DataSourceSurface failed to map for Image for extId:"
+ << wr::AsUint64(aExtId);
+ return false;
+ }
+
+ IntSize size = dSurf->GetSize();
+ wr::ImageDescriptor descriptor(size, map.mStride, dSurf->GetFormat());
+ wr::Vec<uint8_t> data;
+ data.PushBytes(Range<uint8_t>(map.mData, size.height * map.mStride));
+
+ if (op == TextureHost::UPDATE_IMAGE) {
+ aResources.UpdateImageBuffer(aKey, descriptor, data);
+ } else {
+ aResources.AddImage(aKey, descriptor, data);
+ }
+
+ dSurf->Unmap();
+
+ return true;
+}
+
+bool WebRenderBridgeParent::UpdateSharedExternalImage(
+ wr::ExternalImageId aExtId, wr::ImageKey aKey,
+ const ImageIntRect& aDirtyRect, wr::TransactionBuilder& aResources,
+ UniquePtr<ScheduleSharedSurfaceRelease>& aScheduleRelease) {
+ if (!MatchesNamespace(aKey)) {
+ MOZ_ASSERT_UNREACHABLE("Stale shared external image key (update)!");
+ return true;
+ }
+
+ auto key = wr::AsUint64(aKey);
+ auto it = mSharedSurfaceIds.find(key);
+ if (it == mSharedSurfaceIds.end()) {
+ gfxCriticalNote << "Updating unknown shared surface: " << key;
+ return false;
+ }
+
+ RefPtr<DataSourceSurface> dSurf;
+ if (it->second == aExtId) {
+ dSurf = SharedSurfacesParent::Get(aExtId);
+ } else {
+ dSurf = SharedSurfacesParent::Acquire(aExtId);
+ }
+
+ if (!dSurf) {
+ gfxCriticalNote << "Shared surface does not exist for extId:"
+ << wr::AsUint64(aExtId);
+ return false;
+ }
+
+ if (!(it->second == aExtId)) {
+ // We already have a mapping for this image key, so ensure we release the
+ // previous external image ID. This can happen when an image is animated,
+ // and it is changing the external image that the animation points to.
+ if (!aScheduleRelease) {
+ aScheduleRelease = MakeUnique<ScheduleSharedSurfaceRelease>(this);
+ }
+ aScheduleRelease->Add(aKey, it->second);
+ it->second = aExtId;
+ }
+
+ // Prefer raw buffers, unless our backend requires native textures.
+ IntSize surfaceSize = dSurf->GetSize();
+ TextureHost::NativeTexturePolicy policy =
+ TextureHost::BackendNativeTexturePolicy(mApi->GetBackendType(),
+ surfaceSize);
+ auto imageType =
+ policy == TextureHost::NativeTexturePolicy::REQUIRE
+ ? wr::ExternalImageType::TextureHandle(wr::ImageBufferKind::Texture2D)
+ : wr::ExternalImageType::Buffer();
+ wr::ImageDescriptor descriptor(surfaceSize, dSurf->Stride(),
+ dSurf->GetFormat());
+ aResources.UpdateExternalImageWithDirtyRect(
+ aKey, descriptor, aExtId, imageType, wr::ToDeviceIntRect(aDirtyRect), 0);
+
+ return true;
+}
+
+void WebRenderBridgeParent::ObserveSharedSurfaceRelease(
+ const nsTArray<wr::ExternalImageKeyPair>& aPairs,
+ const bool& aFromCheckpoint) {
+ if (!mDestroyed) {
+ Unused << SendWrReleasedImages(aPairs);
+ }
+
+ if (!aFromCheckpoint && mAsyncImageManager) {
+ // We failed to receive a checkpoint notification, so we are releasing these
+ // surfaces blind. Let's wait until the next epoch to complete releasing.
+ for (const auto& pair : aPairs) {
+ mAsyncImageManager->HoldExternalImage(mPipelineId, mWrEpoch, pair.id);
+ }
+ return;
+ }
+
+ // We hit the checkpoint, so we know we can safely release the surfaces now.
+ for (const auto& pair : aPairs) {
+ SharedSurfacesParent::Release(pair.id);
+ }
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvUpdateResources(
+ const wr::IdNamespace& aIdNamespace,
+ nsTArray<OpUpdateResource>&& aResourceUpdates,
+ nsTArray<RefCountedShmem>&& aSmallShmems,
+ nsTArray<ipc::Shmem>&& aLargeShmems) {
+ const bool isValidMessage = aIdNamespace == mIdNamespace;
+
+ if (mDestroyed || !isValidMessage) {
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aSmallShmems);
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aLargeShmems);
+ return IPC_OK();
+ }
+
+ LOG("WebRenderBridgeParent::RecvUpdateResources() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ wr::TransactionBuilder txn(mApi);
+ txn.SetLowPriority(!IsRootWebRenderBridgeParent());
+
+ Unused << GetNextWrEpoch();
+
+ bool success =
+ UpdateResources(aResourceUpdates, aSmallShmems, aLargeShmems, txn);
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aSmallShmems);
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aLargeShmems);
+
+ // Even when txn.IsResourceUpdatesEmpty() is true, there could be resource
+ // updates. It is handled by WebRenderTextureHostWrapper. In this case
+ // txn.IsRenderedFrameInvalidated() becomes true.
+ if (!txn.IsResourceUpdatesEmpty() || txn.IsRenderedFrameInvalidated()) {
+ // There are resource updates, then we update Epoch of transaction.
+ txn.UpdateEpoch(mPipelineId, mWrEpoch);
+ mAsyncImageManager->SetWillGenerateFrame();
+ ScheduleGenerateFrame(wr::RenderReasons::RESOURCE_UPDATE);
+ } else {
+ // If TransactionBuilder does not have resource updates nor display list,
+ // ScheduleGenerateFrame is not triggered via SceneBuilder and there is no
+ // need to update WrEpoch.
+ // Then we want to rollback WrEpoch. See Bug 1490117.
+ RollbackWrEpoch();
+ }
+
+ mApi->SendTransaction(txn);
+
+ if (!success) {
+ return IPC_FAIL(this, "Invalid WebRender resource data shmem or address.");
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvDeleteCompositorAnimations(
+ nsTArray<uint64_t>&& aIds) {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+
+ LOG("WebRenderBridgeParent::RecvDeleteCompositorAnimations() PipelineId "
+ "%" PRIx64 " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ // Once mWrEpoch has been rendered, we can delete these compositor animations
+ mCompositorAnimationsToDelete.push(
+ CompositorAnimationIdsForEpoch(mWrEpoch, std::move(aIds)));
+ return IPC_OK();
+}
+
+void WebRenderBridgeParent::RemoveEpochDataPriorTo(
+ const wr::Epoch& aRenderedEpoch) {
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ sampler->RemoveEpochDataPriorTo(mCompositorAnimationsToDelete,
+ mActiveAnimations, aRenderedEpoch);
+ }
+}
+
+bool WebRenderBridgeParent::IsRootWebRenderBridgeParent() const {
+ return !!mWidget;
+}
+
+void WebRenderBridgeParent::BeginRecording(const TimeStamp& aRecordingStart) {
+ mApi->BeginRecording(aRecordingStart, mPipelineId);
+}
+
+RefPtr<wr::WebRenderAPI::EndRecordingPromise>
+WebRenderBridgeParent::EndRecording() {
+ return mApi->EndRecording();
+}
+
+void WebRenderBridgeParent::AddPendingScrollPayload(
+ CompositionPayload& aPayload, const VsyncId& aCompositeStartId) {
+ auto pendingScrollPayloads = mPendingScrollPayloads.Lock();
+ nsTArray<CompositionPayload>* payloads =
+ pendingScrollPayloads->GetOrInsertNew(aCompositeStartId.mId);
+
+ payloads->AppendElement(aPayload);
+}
+
+nsTArray<CompositionPayload> WebRenderBridgeParent::TakePendingScrollPayload(
+ const VsyncId& aCompositeStartId) {
+ auto pendingScrollPayloads = mPendingScrollPayloads.Lock();
+ nsTArray<CompositionPayload> payload;
+ if (nsTArray<CompositionPayload>* storedPayload =
+ pendingScrollPayloads->Get(aCompositeStartId.mId)) {
+ payload.AppendElements(std::move(*storedPayload));
+ pendingScrollPayloads->Remove(aCompositeStartId.mId);
+ }
+ return payload;
+}
+
+CompositorBridgeParent* WebRenderBridgeParent::GetRootCompositorBridgeParent()
+ const {
+ if (!mCompositorBridge) {
+ return nullptr;
+ }
+
+ if (IsRootWebRenderBridgeParent()) {
+ // This WebRenderBridgeParent is attached to the root
+ // CompositorBridgeParent.
+ return static_cast<CompositorBridgeParent*>(mCompositorBridge);
+ }
+
+ // Otherwise, this WebRenderBridgeParent is attached to a
+ // ContentCompositorBridgeParent so we have an extra level of
+ // indirection to unravel.
+ CompositorBridgeParent::LayerTreeState* lts =
+ CompositorBridgeParent::GetIndirectShadowTree(GetLayersId());
+ if (!lts) {
+ return nullptr;
+ }
+ return lts->mParent;
+}
+
+RefPtr<WebRenderBridgeParent>
+WebRenderBridgeParent::GetRootWebRenderBridgeParent() const {
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ if (!cbp) {
+ return nullptr;
+ }
+
+ return cbp->GetWebRenderBridgeParent();
+}
+
+void WebRenderBridgeParent::UpdateAPZFocusState(const FocusTarget& aFocus) {
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ if (!cbp) {
+ return;
+ }
+ LayersId rootLayersId = cbp->RootLayerTreeId();
+ if (RefPtr<APZUpdater> apz = cbp->GetAPZUpdater()) {
+ apz->UpdateFocusState(rootLayersId, GetLayersId(), aFocus);
+ }
+}
+
+void WebRenderBridgeParent::UpdateAPZScrollData(const wr::Epoch& aEpoch,
+ WebRenderScrollData&& aData) {
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ if (!cbp) {
+ return;
+ }
+ LayersId rootLayersId = cbp->RootLayerTreeId();
+ if (RefPtr<APZUpdater> apz = cbp->GetAPZUpdater()) {
+ apz->UpdateScrollDataAndTreeState(rootLayersId, GetLayersId(), aEpoch,
+ std::move(aData));
+ }
+}
+
+void WebRenderBridgeParent::UpdateAPZScrollOffsets(
+ ScrollUpdatesMap&& aUpdates, uint32_t aPaintSequenceNumber) {
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ if (!cbp) {
+ return;
+ }
+ LayersId rootLayersId = cbp->RootLayerTreeId();
+ if (RefPtr<APZUpdater> apz = cbp->GetAPZUpdater()) {
+ apz->UpdateScrollOffsets(rootLayersId, GetLayersId(), std::move(aUpdates),
+ aPaintSequenceNumber);
+ }
+}
+
+void WebRenderBridgeParent::SetAPZSampleTime() {
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ if (!cbp) {
+ return;
+ }
+ if (RefPtr<APZSampler> apz = cbp->GetAPZSampler()) {
+ SampleTime animationTime;
+ if (Maybe<TimeStamp> testTime = cbp->GetTestingTimeStamp()) {
+ animationTime = SampleTime::FromTest(*testTime);
+ } else {
+ animationTime = mCompositorScheduler->GetLastComposeTime();
+ }
+ TimeDuration frameInterval = cbp->GetVsyncInterval();
+ // As with the non-webrender codepath in AsyncCompositionManager, we want to
+ // use the timestamp for the next vsync when advancing animations.
+ if (frameInterval != TimeDuration::Forever()) {
+ animationTime = animationTime + frameInterval;
+ }
+ apz->SetSampleTime(animationTime);
+ }
+}
+
+bool WebRenderBridgeParent::SetDisplayList(
+ const LayoutDeviceRect& aRect, ipc::ByteBuf&& aDLItems,
+ ipc::ByteBuf&& aDLCache, ipc::ByteBuf&& aSpatialTreeDL,
+ const wr::BuiltDisplayListDescriptor& aDLDesc,
+ const nsTArray<OpUpdateResource>& aResourceUpdates,
+ const nsTArray<RefCountedShmem>& aSmallShmems,
+ const nsTArray<ipc::Shmem>& aLargeShmems, const TimeStamp& aTxnStartTime,
+ wr::TransactionBuilder& aTxn, wr::Epoch aWrEpoch) {
+ bool success =
+ UpdateResources(aResourceUpdates, aSmallShmems, aLargeShmems, aTxn);
+
+ wr::Vec<uint8_t> dlItems(std::move(aDLItems));
+ wr::Vec<uint8_t> dlCache(std::move(aDLCache));
+ wr::Vec<uint8_t> dlSpatialTreeData(std::move(aSpatialTreeDL));
+
+ if (IsRootWebRenderBridgeParent()) {
+#ifdef MOZ_WIDGET_GTK
+ if (mWidget->AsGTK()) {
+ mWidget->AsGTK()->RemoteLayoutSizeUpdated(aRect);
+ }
+#endif
+ LayoutDeviceIntSize widgetSize = mWidget->GetClientSize();
+ LayoutDeviceIntRect rect =
+ LayoutDeviceIntRect(LayoutDeviceIntPoint(), widgetSize);
+ aTxn.SetDocumentView(rect);
+ }
+ aTxn.SetDisplayList(aWrEpoch, mPipelineId, aDLDesc, dlItems, dlCache,
+ dlSpatialTreeData);
+
+ MaybeNotifyOfLayers(aTxn, true);
+
+ if (!IsRootWebRenderBridgeParent()) {
+ aTxn.Notify(wr::Checkpoint::SceneBuilt, MakeUnique<SceneBuiltNotification>(
+ this, aWrEpoch, aTxnStartTime));
+ }
+
+ mApi->SendTransaction(aTxn);
+
+ // We will schedule generating a frame after the scene
+ // build is done, so we don't need to do it here.
+ return success;
+}
+
+bool WebRenderBridgeParent::ProcessDisplayListData(
+ DisplayListData& aDisplayList, wr::Epoch aWrEpoch,
+ const TimeStamp& aTxnStartTime, bool aValidTransaction) {
+ wr::TransactionBuilder txn(mApi);
+ Maybe<wr::AutoTransactionSender> sender;
+
+ if (aDisplayList.mScrollData && !aDisplayList.mScrollData->Validate()) {
+ // If the scroll data is invalid, the entire transaction needs to be dropped
+ // because the scroll data and the display list cross-reference each other.
+ MOZ_ASSERT(
+ false,
+ "Content sent malformed scroll data (or validation check has a bug)");
+ aValidTransaction = false;
+ }
+
+ if (!aValidTransaction) {
+ return true;
+ }
+
+ MOZ_ASSERT(aDisplayList.mIdNamespace == mIdNamespace);
+
+ // Note that this needs to happen before the display list transaction is
+ // sent to WebRender, so that the UpdateHitTestingTree call is guaranteed to
+ // be in the updater queue at the time that the scene swap completes.
+ if (aDisplayList.mScrollData) {
+ UpdateAPZScrollData(aWrEpoch, std::move(aDisplayList.mScrollData.ref()));
+ }
+
+ txn.SetLowPriority(!IsRootWebRenderBridgeParent());
+ sender.emplace(mApi, &txn);
+ bool success = true;
+
+ success =
+ ProcessWebRenderParentCommands(aDisplayList.mCommands, txn) && success;
+
+ if (aDisplayList.mDLItems && aDisplayList.mDLCache &&
+ aDisplayList.mDLSpatialTree) {
+ success =
+ SetDisplayList(
+ aDisplayList.mRect, std::move(aDisplayList.mDLItems.ref()),
+ std::move(aDisplayList.mDLCache.ref()),
+ std::move(aDisplayList.mDLSpatialTree.ref()), aDisplayList.mDLDesc,
+ aDisplayList.mResourceUpdates, aDisplayList.mSmallShmems,
+ aDisplayList.mLargeShmems, aTxnStartTime, txn, aWrEpoch) &&
+ success;
+ }
+ return success;
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetDisplayList(
+ DisplayListData&& aDisplayList, nsTArray<OpDestroy>&& aToDestroy,
+ const uint64_t& aFwdTransactionId, const TransactionId& aTransactionId,
+ const bool& aContainsSVGGroup, const VsyncId& aVsyncId,
+ const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime,
+ const TimeStamp& aTxnStartTime, const nsACString& aTxnURL,
+ const TimeStamp& aFwdTime, nsTArray<CompositionPayload>&& aPayloads) {
+ if (mDestroyed) {
+ for (const auto& op : aToDestroy) {
+ DestroyActor(op);
+ }
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mSmallShmems);
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mLargeShmems);
+ return IPC_OK();
+ }
+
+ LOG("WebRenderBridgeParent::RecvSetDisplayList() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ if (!IsRootWebRenderBridgeParent()) {
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, aTxnURL);
+ }
+
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ uint64_t innerWindowId = cbp ? cbp->GetInnerWindowId() : 0;
+ AUTO_PROFILER_TRACING_MARKER_INNERWINDOWID("Paint", "SetDisplayList",
+ GRAPHICS, innerWindowId);
+ UpdateFwdTransactionId(aFwdTransactionId);
+
+ // This ensures that destroy operations are always processed. It is not safe
+ // to early-return from RecvDPEnd without doing so.
+ AutoWebRenderBridgeParentAsyncMessageSender autoAsyncMessageSender(
+ this, &aToDestroy);
+
+ wr::Epoch wrEpoch = GetNextWrEpoch();
+
+ mReceivedDisplayList = true;
+
+ if (aDisplayList.mScrollData && aDisplayList.mScrollData->IsFirstPaint()) {
+ mIsFirstPaint = true;
+ }
+
+ bool validTransaction = aDisplayList.mIdNamespace == mIdNamespace;
+ bool success = ProcessDisplayListData(aDisplayList, wrEpoch, aTxnStartTime,
+ validTransaction);
+
+ if (!IsRootWebRenderBridgeParent()) {
+ aPayloads.AppendElement(
+ CompositionPayload{CompositionPayloadType::eContentPaint, aFwdTime});
+ }
+
+ HoldPendingTransactionId(wrEpoch, aTransactionId, aContainsSVGGroup, aVsyncId,
+ aVsyncStartTime, aRefreshStartTime, aTxnStartTime,
+ aTxnURL, aFwdTime, mIsFirstPaint,
+ std::move(aPayloads));
+ mIsFirstPaint = false;
+
+ if (!validTransaction) {
+ // Pretend we composited since someone is wating for this event,
+ // though DisplayList was not pushed to webrender.
+ if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
+ TimeStamp now = TimeStamp::Now();
+ cbp->NotifyPipelineRendered(mPipelineId, wrEpoch, VsyncId(), now, now,
+ now);
+ }
+ }
+
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mSmallShmems);
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mLargeShmems);
+
+ if (mRemoteTextureTxnScheduler) {
+ mRemoteTextureTxnScheduler->NotifyTxn(aFwdTransactionId);
+ }
+
+ if (!success) {
+ return IPC_FAIL(this, "Failed to process DisplayListData.");
+ }
+
+ return IPC_OK();
+}
+
+bool WebRenderBridgeParent::ProcessEmptyTransactionUpdates(
+ TransactionData& aData, bool* aScheduleComposite) {
+ *aScheduleComposite = false;
+ wr::TransactionBuilder txn(mApi);
+ txn.SetLowPriority(!IsRootWebRenderBridgeParent());
+
+ if (!aData.mScrollUpdates.IsEmpty()) {
+ UpdateAPZScrollOffsets(std::move(aData.mScrollUpdates),
+ aData.mPaintSequenceNumber);
+ }
+
+ // Update WrEpoch for UpdateResources() and ProcessWebRenderParentCommands().
+ // WrEpoch is used to manage ExternalImages lifetimes in
+ // AsyncImagePipelineManager.
+ Unused << GetNextWrEpoch();
+
+ const bool validTransaction = aData.mIdNamespace == mIdNamespace;
+ bool success = true;
+
+ if (validTransaction) {
+ success = UpdateResources(aData.mResourceUpdates, aData.mSmallShmems,
+ aData.mLargeShmems, txn);
+ if (!aData.mCommands.IsEmpty()) {
+ success = ProcessWebRenderParentCommands(aData.mCommands, txn) && success;
+ }
+ }
+
+ MaybeNotifyOfLayers(txn, true);
+
+ // Even when txn.IsResourceUpdatesEmpty() is true, there could be resource
+ // updates. It is handled by WebRenderTextureHostWrapper. In this case
+ // txn.IsRenderedFrameInvalidated() becomes true.
+ if (!txn.IsResourceUpdatesEmpty() || txn.IsRenderedFrameInvalidated()) {
+ // There are resource updates, then we update Epoch of transaction.
+ txn.UpdateEpoch(mPipelineId, mWrEpoch);
+ *aScheduleComposite = true;
+ } else {
+ // If TransactionBuilder does not have resource updates nor display list,
+ // ScheduleGenerateFrame is not triggered via SceneBuilder and there is no
+ // need to update WrEpoch.
+ // Then we want to rollback WrEpoch. See Bug 1490117.
+ RollbackWrEpoch();
+ }
+
+ if (!txn.IsEmpty()) {
+ mApi->SendTransaction(txn);
+ }
+
+ if (*aScheduleComposite) {
+ mAsyncImageManager->SetWillGenerateFrame();
+ }
+
+ return success;
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvEmptyTransaction(
+ const FocusTarget& aFocusTarget, Maybe<TransactionData>&& aTransactionData,
+ nsTArray<OpDestroy>&& aToDestroy, const uint64_t& aFwdTransactionId,
+ const TransactionId& aTransactionId, const VsyncId& aVsyncId,
+ const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime,
+ const TimeStamp& aTxnStartTime, const nsACString& aTxnURL,
+ const TimeStamp& aFwdTime, nsTArray<CompositionPayload>&& aPayloads) {
+ if (mDestroyed) {
+ for (const auto& op : aToDestroy) {
+ DestroyActor(op);
+ }
+ if (aTransactionData) {
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this,
+ aTransactionData->mSmallShmems);
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this,
+ aTransactionData->mLargeShmems);
+ }
+ return IPC_OK();
+ }
+
+ LOG("WebRenderBridgeParent::RecvEmptyTransaction() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ if (!IsRootWebRenderBridgeParent()) {
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, aTxnURL);
+ }
+
+ AUTO_PROFILER_TRACING_MARKER("Paint", "EmptyTransaction", GRAPHICS);
+ UpdateFwdTransactionId(aFwdTransactionId);
+
+ // This ensures that destroy operations are always processed. It is not safe
+ // to early-return without doing so.
+ AutoWebRenderBridgeParentAsyncMessageSender autoAsyncMessageSender(
+ this, &aToDestroy);
+
+ UpdateAPZFocusState(aFocusTarget);
+
+ bool scheduleAnyComposite = false;
+ wr::RenderReasons renderReasons = wr::RenderReasons::NONE;
+
+ bool success = true;
+ if (aTransactionData) {
+ bool scheduleComposite = false;
+ success =
+ ProcessEmptyTransactionUpdates(*aTransactionData, &scheduleComposite);
+ scheduleAnyComposite = scheduleAnyComposite || scheduleComposite;
+ renderReasons |= wr::RenderReasons::RESOURCE_UPDATE;
+ }
+
+ // If we are going to kick off a new composite as a result of this
+ // transaction, or if there are already composite-triggering pending
+ // transactions inflight, then set sendDidComposite to false because we will
+ // send the DidComposite message after the composite occurs.
+ // If there are no pending transactions and we're not going to do a
+ // composite, then we leave sendDidComposite as true so we just send
+ // the DidComposite notification now.
+ bool sendDidComposite =
+ !scheduleAnyComposite && mPendingTransactionIds.empty();
+
+ // Only register a value for CONTENT_FRAME_TIME telemetry if we actually drew
+ // something. It is for consistency with disabling WebRender.
+ HoldPendingTransactionId(mWrEpoch, aTransactionId, false, aVsyncId,
+ aVsyncStartTime, aRefreshStartTime, aTxnStartTime,
+ aTxnURL, aFwdTime,
+ /* aIsFirstPaint */ false, std::move(aPayloads),
+ /* aUseForTelemetry */ scheduleAnyComposite);
+
+ if (scheduleAnyComposite) {
+ ScheduleGenerateFrame(renderReasons);
+ } else if (sendDidComposite) {
+ // The only thing in the pending transaction id queue should be the entry
+ // we just added, and now we're going to pretend we rendered it
+ MOZ_ASSERT(mPendingTransactionIds.size() == 1);
+ if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
+ TimeStamp now = TimeStamp::Now();
+ cbp->NotifyPipelineRendered(mPipelineId, mWrEpoch, VsyncId(), now, now,
+ now);
+ }
+ }
+
+ if (aTransactionData) {
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this,
+ aTransactionData->mSmallShmems);
+ wr::IpcResourceUpdateQueue::ReleaseShmems(this,
+ aTransactionData->mLargeShmems);
+ }
+
+ if (mRemoteTextureTxnScheduler) {
+ mRemoteTextureTxnScheduler->NotifyTxn(aFwdTransactionId);
+ }
+
+ if (!success) {
+ return IPC_FAIL(this, "Failed to process empty transaction update.");
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetFocusTarget(
+ const FocusTarget& aFocusTarget) {
+ UpdateAPZFocusState(aFocusTarget);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvParentCommands(
+ const wr::IdNamespace& aIdNamespace,
+ nsTArray<WebRenderParentCommand>&& aCommands) {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+
+ const bool isValidMessage = aIdNamespace == mIdNamespace;
+ if (!isValidMessage) {
+ return IPC_OK();
+ }
+
+ LOG("WebRenderBridgeParent::RecvParentCommands() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ wr::TransactionBuilder txn(mApi);
+ txn.SetLowPriority(!IsRootWebRenderBridgeParent());
+ bool success = ProcessWebRenderParentCommands(aCommands, txn);
+ mApi->SendTransaction(txn);
+
+ if (!success) {
+ return IPC_FAIL(this, "Invalid parent command found");
+ }
+
+ return IPC_OK();
+}
+
+bool WebRenderBridgeParent::ProcessWebRenderParentCommands(
+ const nsTArray<WebRenderParentCommand>& aCommands,
+ wr::TransactionBuilder& aTxn) {
+ // Transaction for async image pipeline that uses ImageBridge always need to
+ // be non low priority.
+ wr::TransactionBuilder txnForImageBridge(mApi->GetRootAPI());
+ wr::AutoTransactionSender sender(mApi->GetRootAPI(), &txnForImageBridge);
+
+ bool success = true;
+ for (nsTArray<WebRenderParentCommand>::index_type i = 0;
+ i < aCommands.Length(); ++i) {
+ const WebRenderParentCommand& cmd = aCommands[i];
+ switch (cmd.type()) {
+ case WebRenderParentCommand::TOpAddPipelineIdForCompositable: {
+ const OpAddPipelineIdForCompositable& op =
+ cmd.get_OpAddPipelineIdForCompositable();
+ AddPipelineIdForCompositable(op.pipelineId(), op.handle(), op.owner(),
+ aTxn, txnForImageBridge);
+ break;
+ }
+ case WebRenderParentCommand::TOpRemovePipelineIdForCompositable: {
+ const OpRemovePipelineIdForCompositable& op =
+ cmd.get_OpRemovePipelineIdForCompositable();
+ RemovePipelineIdForCompositable(op.pipelineId(), aTxn);
+ break;
+ }
+ case WebRenderParentCommand::TOpReleaseTextureOfImage: {
+ const OpReleaseTextureOfImage& op = cmd.get_OpReleaseTextureOfImage();
+ ReleaseTextureOfImage(op.key());
+ break;
+ }
+ case WebRenderParentCommand::TOpUpdateAsyncImagePipeline: {
+ const OpUpdateAsyncImagePipeline& op =
+ cmd.get_OpUpdateAsyncImagePipeline();
+
+ auto* pendingOps = mApi->GetPendingAsyncImagePipelineOps(aTxn);
+ auto* pendingRemotetextures = mApi->GetPendingRemoteTextureInfoList();
+
+ mAsyncImageManager->UpdateAsyncImagePipeline(
+ op.pipelineId(), op.scBounds(), op.rotation(), op.filter(),
+ op.mixBlendMode());
+ MOZ_ASSERT_IF(IsRootWebRenderBridgeParent(), !pendingRemotetextures);
+ mAsyncImageManager->ApplyAsyncImageForPipeline(
+ op.pipelineId(), aTxn, txnForImageBridge, pendingOps,
+ pendingRemotetextures);
+ break;
+ }
+ case WebRenderParentCommand::TOpUpdatedAsyncImagePipeline: {
+ const OpUpdatedAsyncImagePipeline& op =
+ cmd.get_OpUpdatedAsyncImagePipeline();
+ aTxn.InvalidateRenderedFrame(wr::RenderReasons::ASYNC_IMAGE);
+
+ auto* pendingOps = mApi->GetPendingAsyncImagePipelineOps(aTxn);
+ auto* pendingRemotetextures = mApi->GetPendingRemoteTextureInfoList();
+
+ MOZ_ASSERT_IF(IsRootWebRenderBridgeParent(), !pendingRemotetextures);
+ mAsyncImageManager->ApplyAsyncImageForPipeline(
+ op.pipelineId(), aTxn, txnForImageBridge, pendingOps,
+ pendingRemotetextures);
+ break;
+ }
+ case WebRenderParentCommand::TCompositableOperation: {
+ if (!ReceiveCompositableUpdate(cmd.get_CompositableOperation())) {
+ NS_ERROR("ReceiveCompositableUpdate failed");
+ }
+ break;
+ }
+ case WebRenderParentCommand::TOpAddCompositorAnimations: {
+ const OpAddCompositorAnimations& op =
+ cmd.get_OpAddCompositorAnimations();
+ CompositorAnimations data(std::move(op.data()));
+ // AnimationHelper::GetNextCompositorAnimationsId() encodes the child
+ // process PID in the upper 32 bits of the id, verify that this is as
+ // expected.
+ if ((data.id() >> 32) != (uint64_t)OtherPid()) {
+ gfxCriticalNote << "TOpAddCompositorAnimations bad id";
+ success = false;
+ continue;
+ }
+ if (data.animations().Length()) {
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ sampler->SetAnimations(data.id(), GetLayersId(), data.animations());
+ const auto activeAnim = mActiveAnimations.find(data.id());
+ if (activeAnim == mActiveAnimations.end()) {
+ mActiveAnimations.emplace(data.id(), mWrEpoch);
+ } else {
+ // Update wr::Epoch if the animation already exists.
+ activeAnim->second = mWrEpoch;
+ }
+ }
+ }
+ break;
+ }
+ default: {
+ // other commands are handle on the child
+ break;
+ }
+ }
+ }
+
+ MOZ_ASSERT(success);
+ return success;
+}
+
+void WebRenderBridgeParent::FlushSceneBuilds() {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+ // Since we are sending transactions through the scene builder thread, we need
+ // to block until all the inflight transactions have been processed. This
+ // flush message blocks until all previously sent scenes have been built
+ // and received by the render backend thread.
+ mApi->FlushSceneBuilder();
+ // The post-swap hook for async-scene-building calls the
+ // ScheduleRenderOnCompositorThread function from the scene builder thread,
+ // which then triggers a call to ScheduleGenerateFrame() on the compositor
+ // thread. But since *this* function is running on the compositor thread,
+ // that scheduling will not happen until this call stack unwinds (or we
+ // could spin a nested event loop, but that's more messy). Instead, we
+ // simulate it ourselves by calling ScheduleGenerateFrame() directly.
+ // Note also that the post-swap hook will run and do another
+ // ScheduleGenerateFrame() after we unwind here, so we will end up with an
+ // extra render/composite that is probably avoidable, but in practice we
+ // shouldn't be calling this function all that much in production so this
+ // is probably fine. If it becomes an issue we can add more state tracking
+ // machinery to optimize it away.
+ ScheduleGenerateFrame(wr::RenderReasons::FLUSH);
+}
+
+void WebRenderBridgeParent::FlushFrameGeneration(wr::RenderReasons aReasons) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(IsRootWebRenderBridgeParent()); // This function is only useful on
+ // the root WRBP
+
+ // This forces a new GenerateFrame transaction to be sent to the render
+ // backend thread, if one is pending. This doesn't block on any other threads.
+ if (mCompositorScheduler->NeedsComposite()) {
+ mCompositorScheduler->CancelCurrentCompositeTask();
+ // Update timestamp of scheduler for APZ and animation.
+ mCompositorScheduler->UpdateLastComposeTime();
+ MaybeGenerateFrame(VsyncId(), /* aForceGenerateFrame */ true,
+ aReasons | wr::RenderReasons::FLUSH);
+ }
+}
+
+void WebRenderBridgeParent::FlushFramePresentation() {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+ // This sends a message to the render backend thread to send a message
+ // to the renderer thread, and waits for that message to be processed. So
+ // 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();
+}
+
+void WebRenderBridgeParent::DisableNativeCompositor() {
+ // Make sure that SceneBuilder thread does not have a task.
+ mApi->FlushSceneBuilder();
+ // Disable WebRender's native compositor usage
+ mApi->EnableNativeCompositor(false);
+ // Ensure we generate and render a frame immediately.
+ ScheduleForcedGenerateFrame(wr::RenderReasons::CONFIG_CHANGE);
+
+ mDisablingNativeCompositor = true;
+}
+
+void WebRenderBridgeParent::UpdateQualitySettings() {
+ if (!IsRootWebRenderBridgeParent()) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+ return;
+ }
+ wr::TransactionBuilder txn(mApi);
+ txn.UpdateQualitySettings(gfxVars::ForceSubpixelAAWherePossible());
+ mApi->SendTransaction(txn);
+}
+
+void WebRenderBridgeParent::UpdateDebugFlags() {
+ if (!IsRootWebRenderBridgeParent()) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+ return;
+ }
+
+ mApi->UpdateDebugFlags(gfxVars::WebRenderDebugFlags());
+}
+
+void WebRenderBridgeParent::UpdateProfilerUI() {
+ if (!IsRootWebRenderBridgeParent()) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+ return;
+ }
+
+ nsCString uiString = gfxVars::GetWebRenderProfilerUIOrDefault();
+ mApi->SetProfilerUI(uiString);
+}
+
+void WebRenderBridgeParent::UpdateParameters() {
+ if (!IsRootWebRenderBridgeParent()) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+ return;
+ }
+
+ uint32_t count = gfxVars::WebRenderBatchingLookback();
+ mApi->SetBatchingLookback(count);
+ mApi->SetInt(wr::IntParameter::BatchedUploadThreshold,
+ gfxVars::WebRenderBatchedUploadThreshold());
+
+ mBlobTileSize = gfxVars::WebRenderBlobTileSize();
+}
+
+void WebRenderBridgeParent::UpdateBoolParameters() {
+ if (!IsRootWebRenderBridgeParent()) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+ return;
+ }
+
+ uint32_t bits = gfxVars::WebRenderBoolParameters();
+ uint32_t changedBits = mBoolParameterBits ^ bits;
+
+ for (auto paramName : MakeEnumeratedRange(wr::BoolParameter::Sentinel)) {
+ uint32_t i = (uint32_t)paramName;
+ if (changedBits & (1 << i)) {
+ bool value = (bits & (1 << i)) != 0;
+ mApi->SetBool(paramName, value);
+ }
+ }
+ mBoolParameterBits = bits;
+}
+
+#if defined(MOZ_WIDGET_ANDROID)
+void WebRenderBridgeParent::RequestScreenPixels(
+ UiCompositorControllerParent* aController) {
+ mScreenPixelsTarget = aController;
+}
+
+void WebRenderBridgeParent::MaybeCaptureScreenPixels() {
+ if (!mScreenPixelsTarget) {
+ return;
+ }
+
+ if (mDestroyed) {
+ return;
+ }
+
+ if (auto* cbp = GetRootCompositorBridgeParent()) {
+ cbp->FlushPendingWrTransactionEventsWithWait();
+ }
+
+ // This function should only get called in the root WRBP.
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+# ifdef DEBUG
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ MOZ_ASSERT(cbp && !cbp->IsPaused());
+# endif
+
+ SurfaceFormat format = SurfaceFormat::R8G8B8A8; // On android we use RGBA8
+ auto client_size = mWidget->GetClientSize();
+ size_t buffer_size =
+ client_size.width * client_size.height * BytesPerPixel(format);
+
+ ipc::Shmem mem;
+ if (!mScreenPixelsTarget->AllocPixelBuffer(buffer_size, &mem)) {
+ // Failed to alloc shmem, Just bail out.
+ return;
+ }
+
+ IntSize size(client_size.width, client_size.height);
+
+ bool needsYFlip = false;
+ mApi->Readback(TimeStamp::Now(), size, format,
+ Range<uint8_t>(mem.get<uint8_t>(), buffer_size), &needsYFlip);
+
+ Unused << mScreenPixelsTarget->SendScreenPixels(
+ std::move(mem), ScreenIntSize(client_size.width, client_size.height),
+ needsYFlip);
+
+ mScreenPixelsTarget = nullptr;
+}
+#endif
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetSnapshot(
+ NotNull<PTextureParent*> aTexture, bool* aNeedsYFlip) {
+ *aNeedsYFlip = false;
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ if (mDestroyed || !cbp || cbp->IsPaused()) {
+ return IPC_OK();
+ }
+
+ if (auto* cbp = GetRootCompositorBridgeParent()) {
+ cbp->FlushPendingWrTransactionEventsWithWait();
+ }
+
+ LOG("WebRenderBridgeParent::RecvGetSnapshot() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ // This function should only get called in the root WRBP. If this function
+ // gets called in a non-root WRBP, we will set mForceRendering in this WRBP
+ // but it will have no effect because CompositeToTarget (which reads the
+ // flag) only gets invoked in the root WRBP. So we assert that this is the
+ // root WRBP (i.e. has a non-null mWidget) to catch violations of this rule.
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+
+ RefPtr<TextureHost> texture = TextureHost::AsTextureHost(aTexture);
+ if (!texture) {
+ // We kill the content process rather than have it continue with an invalid
+ // snapshot, that may be too harsh and we could decide to return some sort
+ // of error to the child process and let it deal with it...
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ // XXX Add other TextureHost supports.
+ // Only BufferTextureHost is supported now.
+ BufferTextureHost* bufferTexture = texture->AsBufferTextureHost();
+ if (!bufferTexture) {
+ // We kill the content process rather than have it continue with an invalid
+ // snapshot, that may be too harsh and we could decide to return some sort
+ // of error to the child process and let it deal with it...
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ TimeStamp start = TimeStamp::Now();
+ MOZ_ASSERT(bufferTexture->GetBufferDescriptor().type() ==
+ BufferDescriptor::TRGBDescriptor);
+ DebugOnly<uint32_t> stride = ImageDataSerializer::GetRGBStride(
+ bufferTexture->GetBufferDescriptor().get_RGBDescriptor());
+ uint8_t* buffer = bufferTexture->GetBuffer();
+ IntSize size = bufferTexture->GetSize();
+
+ MOZ_ASSERT(buffer);
+ // For now the only formats we get here are RGBA and BGRA, and code below is
+ // assuming a bpp of 4. If we allow other formats, the code needs adjusting
+ // accordingly.
+ MOZ_ASSERT(BytesPerPixel(bufferTexture->GetFormat()) == 4);
+ uint32_t buffer_size = size.width * size.height * 4;
+
+ // Assert the stride of the buffer is what webrender expects
+ MOZ_ASSERT((uint32_t)(size.width * 4) == stride);
+
+ FlushSceneBuilds();
+ FlushFrameGeneration(wr::RenderReasons::SNAPSHOT);
+ mApi->Readback(start, size, bufferTexture->GetFormat(),
+ Range<uint8_t>(buffer, buffer_size), aNeedsYFlip);
+
+ return IPC_OK();
+}
+
+void WebRenderBridgeParent::AddPipelineIdForCompositable(
+ const wr::PipelineId& aPipelineId, const CompositableHandle& aHandle,
+ const CompositableHandleOwner& aOwner, wr::TransactionBuilder& aTxn,
+ wr::TransactionBuilder& aTxnForImageBridge) {
+ if (mDestroyed) {
+ return;
+ }
+
+ MOZ_ASSERT(mAsyncCompositables.find(wr::AsUint64(aPipelineId)) ==
+ mAsyncCompositables.end());
+
+ RefPtr<CompositableHost> host;
+ switch (aOwner) {
+ case CompositableHandleOwner::WebRenderBridge:
+ host = FindCompositable(aHandle);
+ break;
+ case CompositableHandleOwner::ImageBridge: {
+ RefPtr<ImageBridgeParent> imageBridge =
+ ImageBridgeParent::GetInstance(OtherPid());
+ if (!imageBridge) {
+ return;
+ }
+ host = imageBridge->FindCompositable(aHandle);
+ break;
+ }
+ }
+
+ if (!host) {
+ return;
+ }
+
+ WebRenderImageHost* wrHost = host->AsWebRenderImageHost();
+ MOZ_ASSERT(wrHost);
+ if (!wrHost) {
+ gfxCriticalNote
+ << "Incompatible CompositableHost at WebRenderBridgeParent.";
+ }
+
+ if (!wrHost) {
+ return;
+ }
+
+ wrHost->SetWrBridge(aPipelineId, this);
+ mAsyncCompositables.emplace(wr::AsUint64(aPipelineId), wrHost);
+ mAsyncImageManager->AddAsyncImagePipeline(aPipelineId, wrHost);
+
+ // If this is being called from WebRenderBridgeParent::RecvSetDisplayList,
+ // then aTxn might contain a display list that references pipelines that
+ // we just added to the async image manager.
+ // If we send the display list alone then WR will not yet have the content for
+ // the pipelines and so it will emit errors; the SetEmptyDisplayList call
+ // below ensure that we provide its content to WR as part of the same
+ // transaction.
+ mAsyncImageManager->SetEmptyDisplayList(aPipelineId, aTxn,
+ aTxnForImageBridge);
+}
+
+void WebRenderBridgeParent::RemovePipelineIdForCompositable(
+ const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn) {
+ if (mDestroyed) {
+ return;
+ }
+
+ auto it = mAsyncCompositables.find(wr::AsUint64(aPipelineId));
+ if (it == mAsyncCompositables.end()) {
+ return;
+ }
+ RefPtr<WebRenderImageHost>& wrHost = it->second;
+
+ wrHost->ClearWrBridge(aPipelineId, this);
+ mAsyncImageManager->RemoveAsyncImagePipeline(aPipelineId, aTxn);
+ aTxn.RemovePipeline(aPipelineId);
+ mAsyncCompositables.erase(wr::AsUint64(aPipelineId));
+}
+
+void WebRenderBridgeParent::DeleteImage(const ImageKey& aKey,
+ wr::TransactionBuilder& aUpdates) {
+ if (mDestroyed) {
+ return;
+ }
+
+ auto it = mSharedSurfaceIds.find(wr::AsUint64(aKey));
+ if (it != mSharedSurfaceIds.end()) {
+ mAsyncImageManager->HoldExternalImage(mPipelineId, mWrEpoch, it->second);
+ mSharedSurfaceIds.erase(it);
+ }
+
+ aUpdates.DeleteImage(aKey);
+}
+
+void WebRenderBridgeParent::ReleaseTextureOfImage(const wr::ImageKey& aKey) {
+ if (mDestroyed) {
+ return;
+ }
+
+ uint64_t id = wr::AsUint64(aKey);
+ CompositableTextureHostRef texture;
+ WebRenderTextureHost* wrTexture = nullptr;
+
+ auto it = mTextureHosts.find(id);
+ if (it != mTextureHosts.end()) {
+ wrTexture = (*it).second->AsWebRenderTextureHost();
+ }
+ if (wrTexture) {
+ mAsyncImageManager->HoldExternalImage(mPipelineId, mWrEpoch, wrTexture);
+ }
+ mTextureHosts.erase(id);
+}
+
+void WebRenderBridgeParent::MaybeNotifyOfLayers(
+ wr::TransactionBuilder& aBuilder, bool aWillHaveLayers) {
+ if (mLastNotifiedHasLayers == aWillHaveLayers) {
+ return;
+ }
+
+ aBuilder.Notify(wr::Checkpoint::SceneBuilt,
+ MakeUnique<ScheduleObserveLayersUpdate>(
+ mCompositorBridge, GetLayersId(), aWillHaveLayers));
+ mLastNotifiedHasLayers = aWillHaveLayers;
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvClearCachedResources() {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+
+ LOG("WebRenderBridgeParent::RecvClearCachedResources() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ // Clear resources
+ wr::TransactionBuilder txn(mApi);
+ txn.SetLowPriority(true);
+ txn.ClearDisplayList(GetNextWrEpoch(), mPipelineId);
+ MaybeNotifyOfLayers(txn, false);
+ mApi->SendTransaction(txn);
+
+ // Schedule generate frame to clean up Pipeline
+ ScheduleGenerateFrame(wr::RenderReasons::CLEAR_RESOURCES);
+
+ ClearAnimationResources();
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvClearAnimationResources() {
+ if (!mDestroyed) {
+ ClearAnimationResources();
+ }
+
+ return IPC_OK();
+}
+
+wr::Epoch WebRenderBridgeParent::UpdateWebRender(
+ CompositorVsyncScheduler* aScheduler, RefPtr<wr::WebRenderAPI>&& aApi,
+ AsyncImagePipelineManager* aImageMgr,
+ const TextureFactoryIdentifier& aTextureFactoryIdentifier) {
+ MOZ_ASSERT(!IsRootWebRenderBridgeParent());
+ MOZ_ASSERT(aScheduler);
+ MOZ_ASSERT(aApi);
+ MOZ_ASSERT(aImageMgr);
+
+ if (mDestroyed) {
+ return mWrEpoch;
+ }
+
+ // Update id name space to identify obsoleted keys.
+ // Since usage of invalid keys could cause crash in webrender.
+ mIdNamespace = aApi->GetNamespace();
+ // XXX Remove it when webrender supports sharing/moving Keys between different
+ // webrender instances.
+ // XXX It requests client to update/reallocate webrender related resources,
+ // but parent side does not wait end of the update.
+ // The code could become simpler if we could serialise old keys deallocation
+ // and new keys allocation. But we do not do it, it is because client side
+ // deallocate old layers/webrender keys after new layers/webrender keys
+ // allocation. Without client side's layout refactoring, we could not finish
+ // all old layers/webrender keys removals before new layer/webrender keys
+ // allocation. In future, we could address the problem.
+ Unused << SendWrUpdated(mIdNamespace, aTextureFactoryIdentifier);
+ CompositorBridgeParentBase* cBridge = mCompositorBridge;
+ // XXX Stop to clear resources if webreder supports resources sharing between
+ // different webrender instances.
+ ClearResources();
+ mCompositorBridge = cBridge;
+ mCompositorScheduler = aScheduler;
+ mApi = aApi;
+ mAsyncImageManager = aImageMgr;
+
+ // Register pipeline to updated AsyncImageManager.
+ mAsyncImageManager->AddPipeline(mPipelineId, this);
+
+ LOG("WebRenderBridgeParent::UpdateWebRender() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ return GetNextWrEpoch(); // Update webrender epoch
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvInvalidateRenderedFrame() {
+ // This function should only get called in the root WRBP
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ LOG("WebRenderBridgeParent::RecvInvalidateRenderedFrame() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ InvalidateRenderedFrame(wr::RenderReasons::WIDGET);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvScheduleComposite(
+ const wr::RenderReasons& aReasons) {
+ LOG("WebRenderBridgeParent::RecvScheduleComposite() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ // Caller of LayerManager::ScheduleComposite() expects that it trigger
+ // composite. Then we do not want to skip generate frame.
+ ScheduleForcedGenerateFrame(aReasons);
+ return IPC_OK();
+}
+
+void WebRenderBridgeParent::InvalidateRenderedFrame(
+ wr::RenderReasons aReasons) {
+ if (mDestroyed) {
+ return;
+ }
+
+ wr::TransactionBuilder fastTxn(mApi, /* aUseSceneBuilderThread */ false);
+ fastTxn.InvalidateRenderedFrame(aReasons);
+ mApi->SendTransaction(fastTxn);
+}
+
+void WebRenderBridgeParent::ScheduleForcedGenerateFrame(
+ wr::RenderReasons aReasons) {
+ if (mDestroyed) {
+ return;
+ }
+
+ InvalidateRenderedFrame(aReasons);
+ ScheduleGenerateFrame(aReasons);
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvCapture() {
+ if (!mDestroyed) {
+ mApi->Capture();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvStartCaptureSequence(
+ const nsACString& aPath, const uint32_t& aFlags) {
+ if (!mDestroyed) {
+ mApi->StartCaptureSequence(aPath, aFlags);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvStopCaptureSequence() {
+ if (!mDestroyed) {
+ mApi->StopCaptureSequence();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSyncWithCompositor() {
+ LOG("WebRenderBridgeParent::RecvSyncWithCompositor() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+
+ FlushSceneBuilds();
+ if (RefPtr<WebRenderBridgeParent> root = GetRootWebRenderBridgeParent()) {
+ root->FlushFrameGeneration(wr::RenderReasons::CONTENT_SYNC);
+ }
+ FlushFramePresentation();
+ // Finally, we force the AsyncImagePipelineManager to handle all the
+ // pipeline updates produced in the last step, so that it frees any
+ // unneeded textures. Then we can return from this sync IPC call knowing
+ // that we've done everything we can to flush stuff on the compositor.
+ mAsyncImageManager->ProcessPipelineUpdates();
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetConfirmedTargetAPZC(
+ const uint64_t& aBlockId, nsTArray<ScrollableLayerGuid>&& aTargets) {
+ for (size_t i = 0; i < aTargets.Length(); i++) {
+ // Guard against bad data from hijacked child processes
+ if (aTargets[i].mLayersId != GetLayersId()) {
+ NS_ERROR(
+ "Unexpected layers id in RecvSetConfirmedTargetAPZC; dropping "
+ "message...");
+ return IPC_FAIL(this, "Bad layers id");
+ }
+ }
+
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+ mCompositorBridge->SetConfirmedTargetAPZC(GetLayersId(), aBlockId,
+ std::move(aTargets));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetTestSampleTime(
+ const TimeStamp& aTime) {
+ if (mDestroyed) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ if (!mCompositorBridge->SetTestSampleTime(GetLayersId(), aTime)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ sampler->EnterTestMode();
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvLeaveTestMode() {
+ if (mDestroyed) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ mCompositorBridge->LeaveTestMode(GetLayersId());
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ sampler->LeaveTestMode();
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetAnimationValue(
+ const uint64_t& aCompositorAnimationsId, OMTAValue* aValue) {
+ if (mDestroyed) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ Maybe<TimeStamp> testingTimeStamp;
+ if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
+ testingTimeStamp = cbp->GetTestingTimeStamp();
+ }
+
+ sampler->SampleForTesting(testingTimeStamp);
+ *aValue = sampler->GetOMTAValue(aCompositorAnimationsId);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetAsyncScrollOffset(
+ const ScrollableLayerGuid::ViewID& aScrollId, const float& aX,
+ const float& aY) {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+ mCompositorBridge->SetTestAsyncScrollOffset(GetLayersId(), aScrollId,
+ CSSPoint(aX, aY));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetAsyncZoom(
+ const ScrollableLayerGuid::ViewID& aScrollId, const float& aZoom) {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+ mCompositorBridge->SetTestAsyncZoom(GetLayersId(), aScrollId,
+ LayerToParentLayerScale(aZoom));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvFlushApzRepaints() {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+ mCompositorBridge->FlushApzRepaints(GetLayersId());
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetAPZTestData(
+ APZTestData* aOutData) {
+ if (mDestroyed) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ mCompositorBridge->GetAPZTestData(GetLayersId(), aOutData);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetFrameUniformity(
+ FrameUniformityData* aOutData) {
+ if (mDestroyed) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ mCompositorBridge->GetFrameUniformity(GetLayersId(), aOutData);
+ return IPC_OK();
+}
+
+void WebRenderBridgeParent::ActorDestroy(ActorDestroyReason aWhy) { Destroy(); }
+
+void WebRenderBridgeParent::ResetPreviousSampleTime() {
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ sampler->ResetPreviousSampleTime();
+ }
+}
+
+RefPtr<OMTASampler> WebRenderBridgeParent::GetOMTASampler() const {
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ if (!cbp) {
+ return nullptr;
+ }
+ return cbp->GetOMTASampler();
+}
+
+void WebRenderBridgeParent::SetOMTASampleTime() {
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ sampler->SetSampleTime(mCompositorScheduler->GetLastComposeTime().Time());
+ }
+}
+
+void WebRenderBridgeParent::RetrySkippedComposite() {
+ if (!mSkippedComposite) {
+ return;
+ }
+
+ mSkippedComposite = false;
+ if (mCompositorScheduler) {
+ mCompositorScheduler->ScheduleComposition(mSkippedCompositeReasons |
+ RenderReasons::SKIPPED_COMPOSITE);
+ }
+ mSkippedCompositeReasons = wr::RenderReasons::NONE;
+}
+
+void WebRenderBridgeParent::CompositeToTarget(VsyncId aId,
+ wr::RenderReasons aReasons,
+ gfx::DrawTarget* aTarget,
+ const gfx::IntRect* aRect) {
+ // This function should only get called in the root WRBP
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+
+ // The two arguments are part of the CompositorVsyncSchedulerOwner API but in
+ // this implementation they should never be non-null.
+ MOZ_ASSERT(aTarget == nullptr);
+ MOZ_ASSERT(aRect == nullptr);
+
+ LOG("WebRenderBridgeParent::CompositeToTarget() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
+ uint64_t innerWindowId = cbp ? cbp->GetInnerWindowId() : 0;
+ AUTO_PROFILER_TRACING_MARKER_INNERWINDOWID("Paint", "CompositeToTarget",
+ GRAPHICS, innerWindowId);
+
+ bool paused = true;
+ if (cbp) {
+ paused = cbp->IsPaused();
+ }
+
+ if (paused || !mReceivedDisplayList) {
+ ResetPreviousSampleTime();
+ mCompositionOpportunityId = mCompositionOpportunityId.Next();
+ PROFILER_MARKER_TEXT("Discarded composite", GRAPHICS,
+ MarkerInnerWindowId(innerWindowId),
+ paused ? "Paused"_ns : "No display list"_ns);
+ return;
+ }
+
+ mSkippedComposite =
+ wr::RenderThread::Get()->TooManyPendingFrames(mApi->GetId());
+
+ if (mSkippedComposite) {
+ // Render thread is busy, try next time.
+ mSkippedComposite = true;
+ mSkippedCompositeReasons = mSkippedCompositeReasons | aReasons;
+ ResetPreviousSampleTime();
+
+ // Record that we skipped presenting a frame for
+ // all pending transactions that have finished scene building.
+ for (auto& id : mPendingTransactionIds) {
+ if (id.mSceneBuiltTime) {
+ id.mSkippedComposites++;
+ }
+ }
+
+ PROFILER_MARKER_TEXT("SkippedComposite", GRAPHICS,
+ MarkerInnerWindowId(innerWindowId),
+ "Too many pending frames");
+
+ Telemetry::ScalarAdd(Telemetry::ScalarID::GFX_SKIPPED_COMPOSITES, 1);
+
+ return;
+ }
+
+ mCompositionOpportunityId = mCompositionOpportunityId.Next();
+ MaybeGenerateFrame(aId, /* aForceGenerateFrame */ false, aReasons);
+}
+
+TimeDuration WebRenderBridgeParent::GetVsyncInterval() const {
+ // This function should only get called in the root WRBP
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
+ return cbp->GetVsyncInterval();
+ }
+ return TimeDuration();
+}
+
+void WebRenderBridgeParent::MaybeGenerateFrame(VsyncId aId,
+ bool aForceGenerateFrame,
+ wr::RenderReasons aReasons) {
+ // This function should only get called in the root WRBP
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ LOG("WebRenderBridgeParent::MaybeGenerateFrame() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
+ // Skip WR render during paused state.
+ if (cbp->IsPaused()) {
+ TimeStamp now = TimeStamp::Now();
+ PROFILER_MARKER_TEXT(
+ "SkippedComposite", GRAPHICS,
+ MarkerOptions(MarkerInnerWindowId(cbp->GetInnerWindowId()),
+ MarkerTiming::InstantAt(now)),
+ "CompositorBridgeParent is paused");
+ cbp->NotifyPipelineRendered(mPipelineId, mWrEpoch, VsyncId(), now, now,
+ now);
+ return;
+ }
+ }
+
+ TimeStamp start = TimeStamp::Now();
+
+ // Ensure GenerateFrame is handled on the render backend thread rather
+ // than going through the scene builder thread. That way we continue
+ // generating frames with the old scene even during slow scene builds.
+ wr::TransactionBuilder fastTxn(mApi, false /* useSceneBuilderThread */);
+ // Handle transaction that is related to DisplayList.
+ wr::TransactionBuilder sceneBuilderTxn(mApi);
+ wr::AutoTransactionSender sender(mApi, &sceneBuilderTxn);
+
+ mAsyncImageManager->SetCompositionInfo(start, mCompositionOpportunityId);
+ mAsyncImageManager->ApplyAsyncImagesOfImageBridge(sceneBuilderTxn, fastTxn);
+ mAsyncImageManager->SetCompositionInfo(TimeStamp(),
+ CompositionOpportunityId{});
+
+ if (!mAsyncImageManager->GetCompositeUntilTime().IsNull()) {
+ // Trigger another CompositeToTarget() call because there might be another
+ // frame that we want to generate after this one.
+ // It will check if we actually want to generate the frame or not.
+ mCompositorScheduler->ScheduleComposition(
+ wr::RenderReasons::ASYNC_IMAGE_COMPOSITE_UNTIL);
+ }
+
+ bool generateFrame = !fastTxn.IsEmpty() || aForceGenerateFrame;
+
+ if (mAsyncImageManager->GetAndResetWillGenerateFrame()) {
+ aReasons |= wr::RenderReasons::ASYNC_IMAGE;
+ generateFrame = true;
+ }
+
+ if (!generateFrame) {
+ // Could skip generating frame now.
+ PROFILER_MARKER_TEXT("SkippedComposite", GRAPHICS,
+ MarkerTiming::InstantAt(start),
+ "No reason to generate frame");
+ ResetPreviousSampleTime();
+ return;
+ }
+
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ if (sampler->HasAnimations()) {
+ ScheduleGenerateFrame(wr::RenderReasons::ANIMATED_PROPERTY);
+ }
+ }
+
+ SetOMTASampleTime();
+ SetAPZSampleTime();
+
+#if defined(ENABLE_FRAME_LATENCY_LOG)
+ auto startTime = TimeStamp::Now();
+ mApi->SetFrameStartTime(startTime);
+#endif
+
+ fastTxn.GenerateFrame(aId, aReasons);
+ wr::RenderThread::Get()->IncPendingFrameCount(mApi->GetId(), aId, start);
+
+ mApi->SendTransaction(fastTxn);
+
+#if defined(MOZ_WIDGET_ANDROID)
+ MaybeCaptureScreenPixels();
+#endif
+
+ mMostRecentComposite = TimeStamp::Now();
+
+ // During disabling native compositor, webrender needs to render twice.
+ // Otherwise, browser flashes black.
+ // XXX better fix?
+ if (mDisablingNativeCompositor) {
+ mDisablingNativeCompositor = false;
+
+ // Ensure we generate and render a frame immediately.
+ ScheduleForcedGenerateFrame(aReasons);
+ }
+}
+
+void WebRenderBridgeParent::HoldPendingTransactionId(
+ const wr::Epoch& aWrEpoch, TransactionId aTransactionId,
+ bool aContainsSVGGroup, const VsyncId& aVsyncId,
+ const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime,
+ const TimeStamp& aTxnStartTime, const nsACString& aTxnURL,
+ const TimeStamp& aFwdTime, const bool aIsFirstPaint,
+ nsTArray<CompositionPayload>&& aPayloads, const bool aUseForTelemetry) {
+ MOZ_ASSERT(aTransactionId > LastPendingTransactionId());
+ mPendingTransactionIds.push_back(PendingTransactionId(
+ aWrEpoch, aTransactionId, aContainsSVGGroup, aVsyncId, aVsyncStartTime,
+ aRefreshStartTime, aTxnStartTime, aTxnURL, aFwdTime, aIsFirstPaint,
+ aUseForTelemetry, std::move(aPayloads)));
+}
+
+TransactionId WebRenderBridgeParent::LastPendingTransactionId() {
+ TransactionId id{0};
+ if (!mPendingTransactionIds.empty()) {
+ id = mPendingTransactionIds.back().mId;
+ }
+ return id;
+}
+
+void WebRenderBridgeParent::NotifySceneBuiltForEpoch(
+ const wr::Epoch& aEpoch, const TimeStamp& aEndTime) {
+ for (auto& id : mPendingTransactionIds) {
+ if (id.mEpoch.mHandle == aEpoch.mHandle) {
+ id.mSceneBuiltTime = aEndTime;
+ break;
+ }
+ }
+}
+
+void WebRenderBridgeParent::NotifyDidSceneBuild(
+ RefPtr<const wr::WebRenderPipelineInfo> aInfo) {
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ if (!mCompositorScheduler) {
+ return;
+ }
+
+ mAsyncImageManager->SetWillGenerateFrame();
+
+ // If the scheduler has a composite more recent than our last composite (which
+ // we missed), and we're within the threshold ms of the last vsync, then
+ // kick of a late composite.
+ TimeStamp lastVsync = mCompositorScheduler->GetLastVsyncTime();
+ VsyncId lastVsyncId = mCompositorScheduler->GetLastVsyncId();
+ if (lastVsyncId == VsyncId() || !mMostRecentComposite ||
+ mMostRecentComposite >= lastVsync ||
+ ((TimeStamp::Now() - lastVsync).ToMilliseconds() >
+ StaticPrefs::gfx_webrender_late_scenebuild_threshold())) {
+ mCompositorScheduler->ScheduleComposition(wr::RenderReasons::SCENE);
+ return;
+ }
+
+ // Look through all the pipelines contained within the built scene
+ // and check which vsync they initiated from.
+ const auto& info = aInfo->Raw();
+ for (const auto& epoch : info.epochs) {
+ WebRenderBridgeParent* wrBridge = this;
+ if (!(epoch.pipeline_id == PipelineId())) {
+ wrBridge = mAsyncImageManager->GetWrBridge(epoch.pipeline_id);
+ }
+
+ if (wrBridge) {
+ VsyncId startId = wrBridge->GetVsyncIdForEpoch(epoch.epoch);
+ // If any of the pipelines started building on the current vsync (i.e
+ // we did all of display list building and scene building within the
+ // threshold), then don't do an early composite.
+ if (startId == lastVsyncId) {
+ mCompositorScheduler->ScheduleComposition(wr::RenderReasons::SCENE);
+ return;
+ }
+ }
+ }
+
+ CompositeToTarget(mCompositorScheduler->GetLastVsyncId(),
+ wr::RenderReasons::SCENE, nullptr, nullptr);
+}
+
+static Telemetry::HistogramID GetHistogramId(const bool aIsLargePaint,
+ const bool aIsFullDisplayList) {
+ const Telemetry::HistogramID histogramIds[] = {
+ Telemetry::CONTENT_SMALL_PAINT_PHASE_WEIGHT_PARTIAL,
+ Telemetry::CONTENT_LARGE_PAINT_PHASE_WEIGHT_PARTIAL,
+ Telemetry::CONTENT_SMALL_PAINT_PHASE_WEIGHT_FULL,
+ Telemetry::CONTENT_LARGE_PAINT_PHASE_WEIGHT_FULL,
+ };
+
+ return histogramIds[(aIsFullDisplayList * 2) + aIsLargePaint];
+}
+
+static void RecordPaintPhaseTelemetry(wr::RendererStats* aStats) {
+ if (!aStats || !aStats->full_paint) {
+ return;
+ }
+
+ const double geckoDL = aStats->gecko_display_list_time;
+ const double wrDL = aStats->wr_display_list_time;
+ const double sceneBuild = aStats->scene_build_time;
+ const double frameBuild = aStats->frame_build_time;
+ const double totalMs = geckoDL + wrDL + sceneBuild + frameBuild;
+
+ // If the total time was >= 16ms, then it's likely we missed a frame due to
+ // painting. We bucket these metrics separately.
+ const bool isLargePaint = totalMs >= 16.0;
+
+ // Split the results based on display list build type, partial or full.
+ const bool isFullDisplayList = aStats->full_display_list;
+
+ auto AsPercentage = [&](const double aTimeMs) -> double {
+ MOZ_ASSERT(aTimeMs <= totalMs);
+ return (aTimeMs / totalMs) * 100.0;
+ };
+
+ auto RecordKey = [&](const nsCString& aKey, const double aTimeMs) -> void {
+ const auto val = static_cast<uint32_t>(AsPercentage(aTimeMs));
+ const auto histogramId = GetHistogramId(isLargePaint, isFullDisplayList);
+ Telemetry::Accumulate(histogramId, aKey, val);
+ };
+
+ RecordKey("dl"_ns, geckoDL);
+ RecordKey("wrdl"_ns, wrDL);
+ RecordKey("sb"_ns, sceneBuild);
+ RecordKey("fb"_ns, frameBuild);
+}
+
+void WebRenderBridgeParent::FlushTransactionIdsForEpoch(
+ const wr::Epoch& aEpoch, const VsyncId& aCompositeStartId,
+ const TimeStamp& aCompositeStartTime, const TimeStamp& aRenderStartTime,
+ const TimeStamp& aEndTime, UiCompositorControllerParent* aUiController,
+ wr::RendererStats* aStats, nsTArray<FrameStats>& aOutputStats,
+ nsTArray<TransactionId>& aOutputTransactions) {
+ while (!mPendingTransactionIds.empty()) {
+ const auto& transactionId = mPendingTransactionIds.front();
+
+ if (aEpoch.mHandle < transactionId.mEpoch.mHandle) {
+ break;
+ }
+
+ if (!IsRootWebRenderBridgeParent() && !mVsyncRate.IsZero() &&
+ transactionId.mUseForTelemetry) {
+ auto fullPaintTime =
+ transactionId.mSceneBuiltTime
+ ? transactionId.mSceneBuiltTime - transactionId.mTxnStartTime
+ : TimeDuration::FromMilliseconds(0);
+
+ int32_t contentFrameTime = RecordContentFrameTime(
+ transactionId.mVsyncId, transactionId.mVsyncStartTime,
+ transactionId.mTxnStartTime, aCompositeStartId, aEndTime,
+ fullPaintTime, mVsyncRate, transactionId.mContainsSVGGroup, true,
+ aStats);
+
+ RecordPaintPhaseTelemetry(aStats);
+
+ if (StaticPrefs::gfx_logging_slow_frames_enabled_AtStartup() &&
+ contentFrameTime > 200) {
+ aOutputStats.AppendElement(FrameStats(
+ transactionId.mId, aCompositeStartTime, aRenderStartTime, aEndTime,
+ contentFrameTime,
+ aStats ? (double(aStats->resource_upload_time) / 1000000.0) : 0.0,
+ aStats ? (double(aStats->gpu_cache_upload_time) / 1000000.0) : 0.0,
+ transactionId.mTxnStartTime, transactionId.mRefreshStartTime,
+ transactionId.mFwdTime, transactionId.mSceneBuiltTime,
+ transactionId.mSkippedComposites, transactionId.mTxnURL));
+ }
+ }
+
+#if defined(ENABLE_FRAME_LATENCY_LOG)
+ if (transactionId.mRefreshStartTime) {
+ int32_t latencyMs =
+ lround((aEndTime - transactionId.mRefreshStartTime).ToMilliseconds());
+ printf_stderr(
+ "From transaction start to end of generate frame latencyMs %d this "
+ "%p\n",
+ latencyMs, this);
+ }
+ if (transactionId.mFwdTime) {
+ int32_t latencyMs =
+ lround((aEndTime - transactionId.mFwdTime).ToMilliseconds());
+ printf_stderr(
+ "From forwarding transaction to end of generate frame latencyMs %d "
+ "this %p\n",
+ latencyMs, this);
+ }
+#endif
+
+ if (aUiController && transactionId.mIsFirstPaint) {
+ aUiController->NotifyFirstPaint();
+ }
+
+ RecordCompositionPayloadsPresented(aEndTime, transactionId.mPayloads);
+
+ aOutputTransactions.AppendElement(transactionId.mId);
+ mPendingTransactionIds.pop_front();
+ }
+}
+
+LayersId WebRenderBridgeParent::GetLayersId() const {
+ return wr::AsLayersId(mPipelineId);
+}
+
+void WebRenderBridgeParent::ScheduleGenerateFrame(wr::RenderReasons aReasons) {
+ if (mCompositorScheduler) {
+ mAsyncImageManager->SetWillGenerateFrame();
+ mCompositorScheduler->ScheduleComposition(aReasons);
+ }
+}
+
+void WebRenderBridgeParent::FlushRendering(wr::RenderReasons aReasons,
+ bool aWaitForPresent) {
+ if (mDestroyed) {
+ return;
+ }
+
+ // This gets called during e.g. window resizes, so we need to flush the
+ // scene (which has the display list at the new window size).
+ FlushSceneBuilds();
+ FlushFrameGeneration(aReasons);
+ if (aWaitForPresent) {
+ FlushFramePresentation();
+ }
+}
+
+ipc::IPCResult WebRenderBridgeParent::RecvSetDefaultClearColor(
+ const uint32_t& aColor) {
+ SetClearColor(gfx::DeviceColor::FromABGR(aColor));
+ return IPC_OK();
+}
+
+void WebRenderBridgeParent::SetClearColor(const gfx::DeviceColor& aColor) {
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+
+ if (!IsRootWebRenderBridgeParent() || mDestroyed) {
+ return;
+ }
+
+ mApi->SetClearColor(aColor);
+}
+
+void WebRenderBridgeParent::Pause() {
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ LOG("WebRenderBridgeParent::Pause() PipelineId %" PRIx64 " Id %" PRIx64
+ " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ if (!IsRootWebRenderBridgeParent() || mDestroyed) {
+ return;
+ }
+
+ mApi->Pause();
+}
+
+bool WebRenderBridgeParent::Resume() {
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ LOG("WebRenderBridgeParent::Resume() PipelineId %" PRIx64 " Id %" PRIx64
+ " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ if (!IsRootWebRenderBridgeParent() || mDestroyed) {
+ return false;
+ }
+
+ if (!mApi->Resume()) {
+ return false;
+ }
+
+ // Ensure we generate and render a frame immediately.
+ ScheduleForcedGenerateFrame(wr::RenderReasons::WIDGET);
+ return true;
+}
+
+void WebRenderBridgeParent::ClearResources() {
+ if (!mApi) {
+ return;
+ }
+
+ if (!IsRootWebRenderBridgeParent()) {
+ mApi->FlushPendingWrTransactionEventsWithoutWait();
+ }
+
+ LOG("WebRenderBridgeParent::ClearResources() PipelineId %" PRIx64
+ " Id %" PRIx64 " root %d",
+ wr::AsUint64(mPipelineId), wr::AsUint64(mApi->GetId()),
+ IsRootWebRenderBridgeParent());
+
+ wr::Epoch wrEpoch = GetNextWrEpoch();
+ mReceivedDisplayList = false;
+ // Schedule generate frame to clean up Pipeline
+ ScheduleGenerateFrame(wr::RenderReasons::CLEAR_RESOURCES);
+
+ // WrFontKeys and WrImageKeys are deleted during WebRenderAPI destruction.
+ for (const auto& entry : mTextureHosts) {
+ WebRenderTextureHost* wrTexture = entry.second->AsWebRenderTextureHost();
+ MOZ_ASSERT(wrTexture);
+ if (wrTexture) {
+ mAsyncImageManager->HoldExternalImage(mPipelineId, wrEpoch, wrTexture);
+ }
+ }
+ mTextureHosts.clear();
+
+ for (const auto& entry : mSharedSurfaceIds) {
+ mAsyncImageManager->HoldExternalImage(mPipelineId, mWrEpoch, entry.second);
+ }
+ mSharedSurfaceIds.clear();
+
+ mAsyncImageManager->RemovePipeline(mPipelineId, wrEpoch);
+
+ wr::TransactionBuilder txn(mApi);
+ txn.SetLowPriority(true);
+ txn.ClearDisplayList(wrEpoch, mPipelineId);
+
+ for (const auto& entry : mAsyncCompositables) {
+ wr::PipelineId pipelineId = wr::AsPipelineId(entry.first);
+ RefPtr<WebRenderImageHost> host = entry.second;
+ host->ClearWrBridge(pipelineId, this);
+ mAsyncImageManager->RemoveAsyncImagePipeline(pipelineId, txn);
+ txn.RemovePipeline(pipelineId);
+ }
+ mAsyncCompositables.clear();
+ txn.RemovePipeline(mPipelineId);
+ mApi->SendTransaction(txn);
+
+ ClearAnimationResources();
+
+ if (IsRootWebRenderBridgeParent()) {
+ mCompositorScheduler->Destroy();
+ mApi->DestroyRenderer();
+ }
+
+ mCompositorScheduler = nullptr;
+ mAsyncImageManager = nullptr;
+ mApi = nullptr;
+ mCompositorBridge = nullptr;
+}
+
+void WebRenderBridgeParent::ClearAnimationResources() {
+ if (RefPtr<OMTASampler> sampler = GetOMTASampler()) {
+ sampler->ClearActiveAnimations(mActiveAnimations);
+ }
+ mActiveAnimations.clear();
+ std::queue<CompositorAnimationIdsForEpoch>().swap(
+ mCompositorAnimationsToDelete); // clear queue
+}
+
+void WebRenderBridgeParent::SendAsyncMessage(
+ const nsTArray<AsyncParentMessageData>& aMessage) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+}
+
+void WebRenderBridgeParent::SendPendingAsyncMessages() {
+ MOZ_ASSERT(mCompositorBridge);
+ mCompositorBridge->SendPendingAsyncMessages();
+}
+
+void WebRenderBridgeParent::SetAboutToSendAsyncMessages() {
+ MOZ_ASSERT(mCompositorBridge);
+ mCompositorBridge->SetAboutToSendAsyncMessages();
+}
+
+void WebRenderBridgeParent::NotifyNotUsed(PTextureParent* aTexture,
+ uint64_t aTransactionId) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+}
+
+base::ProcessId WebRenderBridgeParent::GetChildProcessId() {
+ return OtherPid();
+}
+
+dom::ContentParentId WebRenderBridgeParent::GetContentId() {
+ MOZ_ASSERT(mCompositorBridge);
+ return mCompositorBridge->GetContentId();
+}
+
+bool WebRenderBridgeParent::IsSameProcess() const {
+ return OtherPid() == base::GetCurrentProcId();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvNewCompositable(
+ const CompositableHandle& aHandle, const TextureInfo& aInfo) {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+ if (!AddCompositable(aHandle, aInfo)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebRenderBridgeParent::RecvReleaseCompositable(
+ const CompositableHandle& aHandle) {
+ if (mDestroyed) {
+ return IPC_OK();
+ }
+ ReleaseCompositable(aHandle);
+ return IPC_OK();
+}
+
+TextureFactoryIdentifier WebRenderBridgeParent::GetTextureFactoryIdentifier() {
+ MOZ_ASSERT(mApi);
+
+#ifdef XP_WIN
+ const bool supportsD3D11NV12 = gfx::DeviceManagerDx::Get()->CanUseNV12();
+#else
+ const bool supportsD3D11NV12 = false;
+#endif
+
+ TextureFactoryIdentifier ident(
+ mApi->GetBackendType(), mApi->GetCompositorType(), XRE_GetProcessType(),
+ mApi->GetMaxTextureSize(), mApi->GetUseANGLE(), mApi->GetUseDComp(),
+ mAsyncImageManager->UseCompositorWnd(), false, false, false,
+ supportsD3D11NV12, mApi->GetSyncHandle());
+ return ident;
+}
+
+wr::Epoch WebRenderBridgeParent::GetNextWrEpoch() {
+ MOZ_RELEASE_ASSERT(mWrEpoch.mHandle != UINT32_MAX);
+ mWrEpoch.mHandle++;
+ return mWrEpoch;
+}
+
+void WebRenderBridgeParent::RollbackWrEpoch() {
+ MOZ_RELEASE_ASSERT(mWrEpoch.mHandle != 0);
+ mWrEpoch.mHandle--;
+}
+
+void WebRenderBridgeParent::ExtractImageCompositeNotifications(
+ nsTArray<ImageCompositeNotificationInfo>* aNotifications) {
+ MOZ_ASSERT(IsRootWebRenderBridgeParent());
+ if (mDestroyed) {
+ return;
+ }
+ mAsyncImageManager->FlushImageNotifications(aNotifications);
+}
+
+void WebRenderBridgeParent::FlushPendingWrTransactionEventsWithWait() {
+ if (mDestroyed || IsRootWebRenderBridgeParent()) {
+ return;
+ }
+ mApi->FlushPendingWrTransactionEventsWithWait();
+}
+
+RefPtr<WebRenderBridgeParentRef>
+WebRenderBridgeParent::GetWebRenderBridgeParentRef() {
+ if (mDestroyed) {
+ return nullptr;
+ }
+
+ if (!mWebRenderBridgeRef) {
+ mWebRenderBridgeRef = new WebRenderBridgeParentRef(this);
+ }
+ return mWebRenderBridgeRef;
+}
+
+WebRenderBridgeParentRef::WebRenderBridgeParentRef(
+ WebRenderBridgeParent* aWebRenderBridge)
+ : mWebRenderBridge(aWebRenderBridge) {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(mWebRenderBridge);
+}
+
+RefPtr<WebRenderBridgeParent> WebRenderBridgeParentRef::WrBridge() {
+ return mWebRenderBridge;
+}
+
+void WebRenderBridgeParentRef::Clear() {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ mWebRenderBridge = nullptr;
+}
+
+WebRenderBridgeParentRef::~WebRenderBridgeParentRef() {
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(!mWebRenderBridge);
+}
+
+} // namespace mozilla::layers