diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/layers/wr/WebRenderBridgeParent.cpp | 2596 |
1 files changed, 2596 insertions, 0 deletions
diff --git a/gfx/layers/wr/WebRenderBridgeParent.cpp b/gfx/layers/wr/WebRenderBridgeParent.cpp new file mode 100644 index 0000000000..1484cc9976 --- /dev/null +++ b/gfx/layers/wr/WebRenderBridgeParent.cpp @@ -0,0 +1,2596 @@ +/* -*- 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 "Layers.h" +#include "nsExceptionHandler.h" +#include "mozilla/Range.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/gfxVars.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/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/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/widget/CompositorWidget.h" + +#ifdef XP_WIN +# include "mozilla/widget/WinCompositorWidget.h" +#endif + +#ifdef MOZ_GECKO_PROFILER +# include "mozilla/ProfilerMarkerTypes.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(); +} + +void gecko_profiler_start_marker(const char* name) { + PROFILER_MARKER(mozilla::ProfilerString8View::WrapNullTerminatedString(name), + GRAPHICS, mozilla::MarkerTiming::IntervalStart(), Tracing, + "WebRender"); +} + +void gecko_profiler_end_marker(const char* name) { + PROFILER_MARKER(mozilla::ProfilerString8View::WrapNullTerminatedString(name), + GRAPHICS, mozilla::MarkerTiming::IntervalEnd(), Tracing, + "WebRender"); +} + +void gecko_profiler_event_marker(const char* name) { + PROFILER_MARKER(mozilla::ProfilerString8View::WrapNullTerminatedString(name), + GRAPHICS, {}, Tracing, "WebRender"); +} + +void gecko_profiler_add_text_marker(const char* name, const char* text_bytes, + size_t text_len, uint64_t microseconds) { +#ifdef MOZ_GECKO_PROFILER + if (profiler_thread_is_being_profiled()) { + auto now = mozilla::TimeStamp::NowUnfuzzed(); + auto start = now - mozilla::TimeDuration::FromMicroseconds(microseconds); + PROFILER_MARKER_TEXT( + mozilla::ProfilerString8View::WrapNullTerminatedString(name), GRAPHICS, + mozilla::MarkerTiming::Interval(start, now), + mozilla::ProfilerString8View(text_bytes, text_len)); + } +#endif +} + +bool gecko_profiler_thread_is_being_profiled() { +#ifdef MOZ_GECKO_PROFILER + return profiler_thread_is_being_profiled(); +#else + return false; +#endif +} + +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); +} + +void gecko_profiler_register_thread(const char* name) { + PROFILER_REGISTER_THREAD(name); +} + +void gecko_profiler_unregister_thread() { PROFILER_UNREGISTER_THREAD(); } + +void record_telemetry_time(mozilla::wr::TelemetryProbe aProbe, + uint64_t aTimeNs) { + uint32_t time_ms = (uint32_t)(aTimeNs / 1000000); + switch (aProbe) { + case mozilla::wr::TelemetryProbe::SceneBuildTime: + mozilla::Telemetry::Accumulate(mozilla::Telemetry::WR_SCENEBUILD_TIME, + time_ms); + break; + case mozilla::wr::TelemetryProbe::SceneSwapTime: + mozilla::Telemetry::Accumulate(mozilla::Telemetry::WR_SCENESWAP_TIME, + time_ms); + break; + case mozilla::wr::TelemetryProbe::FrameBuildTime: + mozilla::Telemetry::Accumulate(mozilla::Telemetry::WR_FRAMEBUILD_TIME, + time_ms); + break; + default: + MOZ_ASSERT(false); + break; + } +} + +static CrashReporter::Annotation FromWrCrashAnnotation( + mozilla::wr::CrashAnnotation aAnnotation) { + switch (aAnnotation) { + case mozilla::wr::CrashAnnotation::CompileShader: + return CrashReporter::Annotation::GraphicsCompileShader; + 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 { + +namespace layers { + +using namespace mozilla::gfx; + +class ScheduleObserveLayersUpdate : public wr::NotificationHandler { + public: + ScheduleObserveLayersUpdate(RefPtr<CompositorBridgeParentBase> aBridge, + LayersId aLayersId, LayersObserverEpoch aEpoch, + bool aIsActive) + : mBridge(aBridge), + mLayersId(aLayersId), + mObserverEpoch(aEpoch), + mIsActive(aIsActive) {} + + void Notify(wr::Checkpoint) override { + CompositorThread()->Dispatch( + NewRunnableMethod<LayersId, LayersObserverEpoch, int>( + "ObserveLayersUpdate", mBridge, + &CompositorBridgeParentBase::ObserveLayersUpdate, mLayersId, + mObserverEpoch, mIsActive)); + } + + protected: + RefPtr<CompositorBridgeParentBase> mBridge; + LayersId mLayersId; + LayersObserverEpoch mObserverEpoch; + 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(); +#ifdef MOZ_GECKO_PROFILER + if (profiler_can_accept_markers()) { + profiler_add_marker("CONTENT_FULL_PAINT_TIME", + geckoprofiler::category::GRAPHICS, + MarkerTiming::Interval(startTime, endTime), + baseprofiler::markers::ContentBuildMarker{}); + } +#endif + Telemetry::Accumulate( + Telemetry::CONTENT_FULL_PAINT_TIME, + static_cast<uint32_t>((endTime - startTime).ToMilliseconds())); + 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) {} + + void Add(const wr::ImageKey& aKey, const wr::ExternalImageId& aId) { + mSurfaces.AppendElement(wr::ExternalImageKeyPair{aKey, aId}); + } + + void Notify(wr::Checkpoint) override { + CompositorThread()->Dispatch( + NewRunnableMethod<nsTArray<wr::ExternalImageKeyPair>>( + "ObserveSharedSurfaceRelease", mWrBridge, + &WebRenderBridgeParent::ObserveSharedSurfaceRelease, + std::move(mSurfaces))); + } + + private: + 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), + mChildLayersObserverEpoch{0}, + mParentLayersObserverEpoch{0}, + mWrEpoch{0}, + mIdNamespace(aApi->GetNamespace()), +#if defined(MOZ_WIDGET_ANDROID) + mScreenPixelsTarget(nullptr), +#endif + mPaused(false), + mDestroyed(false), + mReceivedDisplayList(false), + mIsFirstPaint(true), + mSkippedComposite(false), + mDisablingNativeCompositor(false), + mPendingScrollPayloads("WebRenderBridgeParent::mPendingScrollPayloads") { + MOZ_ASSERT(mAsyncImageManager); + mAsyncImageManager->AddPipeline(mPipelineId, this); + if (IsRootWebRenderBridgeParent()) { + MOZ_ASSERT(!mCompositorScheduler); + mCompositorScheduler = new CompositorVsyncScheduler(this, mWidget); + } + UpdateDebugFlags(); + UpdateQualitySettings(); + UpdateProfilerUI(); +} + +WebRenderBridgeParent::WebRenderBridgeParent(const wr::PipelineId& aPipelineId, + nsCString&& aError) + : mCompositorBridge(nullptr), + mPipelineId(aPipelineId), + mChildLayersObserverEpoch{0}, + mParentLayersObserverEpoch{0}, + mWrEpoch{0}, + mIdNamespace{0}, + mInitError(aError), + mPaused(false), + mDestroyed(true), + mReceivedDisplayList(false), + mIsFirstPaint(false), + mSkippedComposite(false), + mDisablingNativeCompositor(false), + mPendingScrollPayloads("WebRenderBridgeParent::mPendingScrollPayloads") {} + +WebRenderBridgeParent::~WebRenderBridgeParent() {} + +/* 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; + } + mDestroyed = true; + if (mWebRenderBridgeRef) { + // Break mutual reference + mWebRenderBridgeRef->Clear(); + mWebRenderBridgeRef = nullptr; + } + 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()) { + 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; + + 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)) { + return false; + } + aUpdates.AddImage(op.key(), op.descriptor(), bytes); + 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)) { + return false; + } + aUpdates.UpdateImageBuffer(op.key(), op.descriptor(), bytes); + 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)) { + return false; + } + aUpdates.AddBlobImage(op.key(), op.descriptor(), bytes, + wr::ToDeviceIntRect(op.visibleRect())); + 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)) { + return false; + } + aUpdates.UpdateBlobImage(op.key(), op.descriptor(), bytes, + wr::ToDeviceIntRect(op.visibleRect()), + wr::ToLayoutIntRect(op.dirtyRect())); + 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::TOpAddPrivateExternalImage: { + const auto& op = cmd.get_OpAddPrivateExternalImage(); + if (!AddPrivateExternalImage(op.externalImageId(), op.key(), + op.descriptor(), aUpdates)) { + return false; + } + break; + } + case OpUpdateResource::TOpAddSharedExternalImage: { + const auto& op = cmd.get_OpAddSharedExternalImage(); + if (!AddSharedExternalImage(op.externalImageId(), op.key(), aUpdates)) { + return false; + } + break; + } + case OpUpdateResource::TOpPushExternalImageForTexture: { + const auto& op = cmd.get_OpPushExternalImageForTexture(); + CompositableTextureHostRef texture; + texture = TextureHost::AsTextureHost(op.textureParent()); + if (!PushExternalImageForTexture(op.externalImageId(), op.key(), + texture, op.isUpdate(), aUpdates)) { + return false; + } + break; + } + case OpUpdateResource::TOpUpdatePrivateExternalImage: { + const auto& op = cmd.get_OpUpdatePrivateExternalImage(); + if (!UpdatePrivateExternalImage(op.externalImageId(), op.key(), + op.descriptor(), op.dirtyRect(), + aUpdates)) { + return false; + } + break; + } + case OpUpdateResource::TOpUpdateSharedExternalImage: { + const auto& op = cmd.get_OpUpdateSharedExternalImage(); + if (!UpdateSharedExternalImage(op.externalImageId(), op.key(), + op.dirtyRect(), aUpdates, + scheduleRelease)) { + return false; + } + break; + } + case OpUpdateResource::TOpAddRawFont: { + if (!ReadRawFont(cmd.get_OpAddRawFont(), reader, aUpdates)) { + return 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)) { + return false; + } + aUpdates.AddFontDescriptor(op.key(), bytes, op.fontIndex()); + 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)) { + return false; + } + aUpdates.AddFontInstance(op.instanceKey(), op.fontKey(), op.glyphSize(), + op.options().ptrOr(nullptr), + op.platformOptions().ptrOr(nullptr), + variations); + 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)); + } + return true; +} + +bool WebRenderBridgeParent::AddPrivateExternalImage( + wr::ExternalImageId aExtId, wr::ImageKey aKey, wr::ImageDescriptor aDesc, + wr::TransactionBuilder& aResources) { + if (!MatchesNamespace(aKey)) { + MOZ_ASSERT_UNREACHABLE("Stale private external image key (add)!"); + return true; + } + + aResources.AddExternalImage(aKey, aDesc, aExtId, + wr::ExternalImageType::Buffer(), 0); + + return true; +} + +bool WebRenderBridgeParent::UpdatePrivateExternalImage( + wr::ExternalImageId aExtId, wr::ImageKey aKey, + const wr::ImageDescriptor& aDesc, const ImageIntRect& aDirtyRect, + wr::TransactionBuilder& aResources) { + if (!MatchesNamespace(aKey)) { + MOZ_ASSERT_UNREACHABLE("Stale private external image key (update)!"); + return true; + } + + aResources.UpdateExternalImageWithDirtyRect( + aKey, aDesc, aExtId, wr::ExternalImageType::Buffer(), + wr::ToDeviceIntRect(aDirtyRect), 0); + + return true; +} + +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)); + + auto imageType = + mApi->GetBackendType() == WebRenderBackend::SOFTWARE + ? wr::ExternalImageType::TextureHandle(wr::ImageBufferKind::Texture2D) + : wr::ExternalImageType::Buffer(); + wr::ImageDescriptor descriptor(dSurf->GetSize(), 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; + } + + auto imageType = + mApi->GetBackendType() == WebRenderBackend::SOFTWARE + ? wr::ExternalImageType::TextureHandle(wr::ImageBufferKind::Texture2D) + : wr::ExternalImageType::Buffer(); + wr::ImageDescriptor descriptor(dSurf->GetSize(), dSurf->Stride(), + dSurf->GetFormat()); + aResources.UpdateExternalImageWithDirtyRect( + aKey, descriptor, aExtId, imageType, wr::ToDeviceIntRect(aDirtyRect), 0); + + return true; +} + +void WebRenderBridgeParent::ObserveSharedSurfaceRelease( + const nsTArray<wr::ExternalImageKeyPair>& aPairs) { + if (!mDestroyed) { + Unused << SendWrReleasedImages(aPairs); + } + 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) { + if (mDestroyed || aIdNamespace != mIdNamespace) { + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aSmallShmems); + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aLargeShmems); + return IPC_OK(); + } + + wr::TransactionBuilder txn; + 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(); + } 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 (!success) { + return IPC_FAIL(this, "Invalid WebRender resource data shmem or address."); + } + + mApi->SendTransaction(txn); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvDeleteCompositorAnimations( + nsTArray<uint64_t>&& aIds) { + if (mDestroyed) { + return IPC_OK(); + } + + // 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::WriteCollectedFramesPromise> +WebRenderBridgeParent::WriteCollectedFrames() { + return mApi->WriteCollectedFrames(); +} + +RefPtr<wr::WebRenderAPI::GetCollectedFramesPromise> +WebRenderBridgeParent::GetCollectedFrames() { + return mApi->GetCollectedFrames(); +} + +void WebRenderBridgeParent::AddPendingScrollPayload( + CompositionPayload& aPayload, const VsyncId& aCompositeStartId) { + auto pendingScrollPayloads = mPendingScrollPayloads.Lock(); + nsTArray<CompositionPayload>* payloads = + pendingScrollPayloads->LookupOrAdd(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&& aDL, + 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 aObserveLayersUpdate) { + if (NS_WARN_IF(!UpdateResources(aResourceUpdates, aSmallShmems, aLargeShmems, + aTxn))) { + return false; + } + + wr::Vec<uint8_t> dlData(std::move(aDL)); + + if (IsRootWebRenderBridgeParent()) { + LayoutDeviceIntSize widgetSize = mWidget->GetClientSize(); + LayoutDeviceIntRect rect = + LayoutDeviceIntRect(LayoutDeviceIntPoint(), widgetSize); + aTxn.SetDocumentView(rect); + } + gfx::DeviceColor clearColor(0.f, 0.f, 0.f, 0.f); + aTxn.SetDisplayList(clearColor, aWrEpoch, + wr::ToLayoutSize(RoundedToInt(aRect).Size()), mPipelineId, + aDLDesc, dlData); + + if (aObserveLayersUpdate) { + aTxn.Notify( + wr::Checkpoint::SceneBuilt, + MakeUnique<ScheduleObserveLayersUpdate>( + mCompositorBridge, GetLayersId(), mChildLayersObserverEpoch, 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 true; +} + +bool WebRenderBridgeParent::ProcessDisplayListData( + DisplayListData& aDisplayList, wr::Epoch aWrEpoch, + const TimeStamp& aTxnStartTime, bool aValidTransaction, + bool aObserveLayersUpdate) { + wr::TransactionBuilder txn; + Maybe<wr::AutoTransactionSender> sender; + + // 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()); + if (aValidTransaction) { + MOZ_ASSERT(aDisplayList.mIdNamespace == mIdNamespace); + sender.emplace(mApi, &txn); + } + + if (NS_WARN_IF( + !ProcessWebRenderParentCommands(aDisplayList.mCommands, txn))) { + return false; + } + + if (aDisplayList.mDL && aValidTransaction && + !SetDisplayList(aDisplayList.mRect, std::move(aDisplayList.mDL.ref()), + aDisplayList.mDLDesc, aDisplayList.mResourceUpdates, + aDisplayList.mSmallShmems, aDisplayList.mLargeShmems, + aTxnStartTime, txn, aWrEpoch, aObserveLayersUpdate)) { + return false; + } + return true; +} + +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 nsCString& aTxnURL, + const TimeStamp& aFwdTime, nsTArray<CompositionPayload>&& aPayloads) { + if (mDestroyed) { + for (const auto& op : aToDestroy) { + DestroyActor(op); + } + return IPC_OK(); + } + + if (!IsRootWebRenderBridgeParent()) { + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, aTxnURL); + } + + AUTO_PROFILER_TRACING_MARKER("Paint", "SetDisplayList", GRAPHICS); + 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 observeLayersUpdate = ShouldParentObserveEpoch(); + + if (!ProcessDisplayListData(aDisplayList, wrEpoch, aTxnStartTime, + validTransaction, observeLayersUpdate)) { + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mSmallShmems); + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mLargeShmems); + return IPC_FAIL(this, "Failed to process DisplayListData."); + } + + if (!validTransaction && observeLayersUpdate) { + mCompositorBridge->ObserveLayersUpdate(GetLayersId(), + mChildLayersObserverEpoch, true); + } + + 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); + + return IPC_OK(); +} + +bool WebRenderBridgeParent::ProcessEmptyTransactionUpdates( + TransactionData& aData, bool* aScheduleComposite) { + *aScheduleComposite = false; + wr::TransactionBuilder txn; + 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(); + + if (aData.mIdNamespace == mIdNamespace && + !UpdateResources(aData.mResourceUpdates, aData.mSmallShmems, + aData.mLargeShmems, txn)) { + return false; + } + + if (!aData.mCommands.IsEmpty()) { + if (!ProcessWebRenderParentCommands(aData.mCommands, txn)) { + return false; + } + } + + if (ShouldParentObserveEpoch()) { + txn.Notify( + wr::Checkpoint::SceneBuilt, + MakeUnique<ScheduleObserveLayersUpdate>( + mCompositorBridge, GetLayersId(), mChildLayersObserverEpoch, 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 true; +} + +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 nsCString& aTxnURL, + const TimeStamp& aFwdTime, nsTArray<CompositionPayload>&& aPayloads) { + if (mDestroyed) { + for (const auto& op : aToDestroy) { + DestroyActor(op); + } + return IPC_OK(); + } + + 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; + + if (aTransactionData) { + bool scheduleComposite = false; + if (!ProcessEmptyTransactionUpdates(*aTransactionData, + &scheduleComposite)) { + wr::IpcResourceUpdateQueue::ReleaseShmems(this, + aTransactionData->mSmallShmems); + wr::IpcResourceUpdateQueue::ReleaseShmems(this, + aTransactionData->mLargeShmems); + return IPC_FAIL(this, "Failed to process empty transaction update."); + } + scheduleAnyComposite = scheduleAnyComposite || scheduleComposite; + } + + // 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(); + } 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); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetFocusTarget( + const FocusTarget& aFocusTarget) { + UpdateAPZFocusState(aFocusTarget); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvParentCommands( + nsTArray<WebRenderParentCommand>&& aCommands) { + if (mDestroyed) { + return IPC_OK(); + } + + wr::TransactionBuilder txn; + txn.SetLowPriority(!IsRootWebRenderBridgeParent()); + if (!ProcessWebRenderParentCommands(aCommands, txn)) { + return IPC_FAIL(this, "Invalid parent command found"); + } + + mApi->SendTransaction(txn); + 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; + wr::AutoTransactionSender sender(mApi, &txnForImageBridge); + + 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.isAsync(), + 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(); + mAsyncImageManager->UpdateAsyncImagePipeline( + op.pipelineId(), op.scBounds(), op.rotation(), op.filter(), + op.mixBlendMode()); + mAsyncImageManager->ApplyAsyncImageForPipeline(op.pipelineId(), aTxn, + txnForImageBridge); + break; + } + case WebRenderParentCommand::TOpUpdatedAsyncImagePipeline: { + const OpUpdatedAsyncImagePipeline& op = + cmd.get_OpUpdatedAsyncImagePipeline(); + aTxn.InvalidateRenderedFrame(); + mAsyncImageManager->ApplyAsyncImageForPipeline(op.pipelineId(), aTxn, + txnForImageBridge); + 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()) { + return false; + } + 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; + } + } + } + return true; +} + +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(); +} + +void WebRenderBridgeParent::FlushFrameGeneration() { + 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); + } +} + +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(); + + mDisablingNativeCompositor = true; +} + +void WebRenderBridgeParent::UpdateQualitySettings() { + wr::TransactionBuilder txn; + txn.UpdateQualitySettings(gfxVars::ForceSubpixelAAWherePossible()); + mApi->SendTransaction(txn); +} + +void WebRenderBridgeParent::UpdateDebugFlags() { + mApi->UpdateDebugFlags(gfxVars::WebRenderDebugFlags()); +} + +void WebRenderBridgeParent::UpdateProfilerUI() { + nsCString uiString = gfxVars::GetWebRenderProfilerUIOrDefault(); + mApi->SetProfilerUI(uiString); +} + +void WebRenderBridgeParent::UpdateMultithreading() { + mApi->EnableMultithreading(gfxVars::UseWebRenderMultithreading()); +} + +void WebRenderBridgeParent::UpdateBatchingParameters() { + uint32_t count = gfxVars::WebRenderBatchingLookback(); + mApi->SetBatchingLookback(count); +} + +#if defined(MOZ_WIDGET_ANDROID) +void WebRenderBridgeParent::RequestScreenPixels( + UiCompositorControllerParent* aController) { + mScreenPixelsTarget = aController; +} + +void WebRenderBridgeParent::MaybeCaptureScreenPixels() { + if (!mScreenPixelsTarget) { + return; + } + + if (mDestroyed) { + return; + } + MOZ_ASSERT(!mPaused); + + // This function should only get called in the root WRBP. + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + 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( + PTextureParent* aTexture, bool* aNeedsYFlip) { + *aNeedsYFlip = false; + if (mDestroyed || mPaused) { + return IPC_OK(); + } + + // 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(); + 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 bool& aAsync, wr::TransactionBuilder& aTxn, + wr::TransactionBuilder& aTxnForImageBridge) { + if (mDestroyed) { + return; + } + + MOZ_ASSERT(mAsyncCompositables.find(wr::AsUint64(aPipelineId)) == + mAsyncCompositables.end()); + + RefPtr<CompositableHost> host; + if (aAsync) { + RefPtr<ImageBridgeParent> imageBridge = + ImageBridgeParent::GetInstance(OtherPid()); + if (!imageBridge) { + return; + } + host = imageBridge->FindCompositable(aHandle); + } else { + host = FindCompositable(aHandle); + } + 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); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetLayersObserverEpoch( + const LayersObserverEpoch& aChildEpoch) { + if (mDestroyed) { + return IPC_OK(); + } + mChildLayersObserverEpoch = aChildEpoch; + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvClearCachedResources() { + if (mDestroyed) { + return IPC_OK(); + } + + // Clear resources + wr::TransactionBuilder txn; + txn.SetLowPriority(true); + txn.ClearDisplayList(GetNextWrEpoch(), mPipelineId); + txn.Notify( + wr::Checkpoint::SceneBuilt, + MakeUnique<ScheduleObserveLayersUpdate>( + mCompositorBridge, GetLayersId(), mChildLayersObserverEpoch, false)); + mApi->SendTransaction(txn); + + // Schedule generate frame to clean up Pipeline + ScheduleGenerateFrame(); + + 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); + + return GetNextWrEpoch(); // Update webrender epoch +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvInvalidateRenderedFrame() { + // This function should only get called in the root WRBP + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + InvalidateRenderedFrame(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvScheduleComposite() { + // Caller of LayerManager::ScheduleComposite() expects that it trigger + // composite. Then we do not want to skip generate frame. + ScheduleForcedGenerateFrame(); + return IPC_OK(); +} + +void WebRenderBridgeParent::InvalidateRenderedFrame() { + if (mDestroyed) { + return; + } + + wr::TransactionBuilder fastTxn(/* aUseSceneBuilderThread */ false); + fastTxn.InvalidateRenderedFrame(); + mApi->SendTransaction(fastTxn); +} + +void WebRenderBridgeParent::ScheduleForcedGenerateFrame() { + if (mDestroyed) { + return; + } + + InvalidateRenderedFrame(); + ScheduleGenerateFrame(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvCapture() { + if (!mDestroyed) { + mApi->Capture(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvToggleCaptureSequence() { + if (!mDestroyed) { + mApi->ToggleCaptureSequence(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSyncWithCompositor() { + FlushSceneBuilds(); + if (RefPtr<WebRenderBridgeParent> root = GetRootWebRenderBridgeParent()) { + root->FlushFrameGeneration(); + } + 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 (!mCompositorBridge->SetTestSampleTime(GetLayersId(), aTime)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvLeaveTestMode() { + if (mDestroyed) { + return IPC_FAIL_NO_REASON(this); + } + + mCompositorBridge->LeaveTestMode(GetLayersId()); + 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) { + mCompositorBridge->GetAPZTestData(GetLayersId(), aOutData); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetFrameUniformity( + FrameUniformityData* aOutData) { + 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::CompositeIfNeeded() { + if (mSkippedComposite) { + mSkippedComposite = false; + if (mCompositorScheduler) { + mCompositorScheduler->ScheduleComposition(); + } + } +} + +void WebRenderBridgeParent::CompositeToTarget(VsyncId aId, + 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); + + AUTO_PROFILER_TRACING_MARKER("Paint", "CompositeToTarget", GRAPHICS); + if (mPaused || !mReceivedDisplayList) { + ResetPreviousSampleTime(); + mCompositionOpportunityId = mCompositionOpportunityId.Next(); + PROFILER_MARKER_TEXT("SkippedComposite", GRAPHICS, {}, + mPaused ? "Paused"_ns : "No display list"_ns); + return; + } + + if (mSkippedComposite || + wr::RenderThread::Get()->TooManyPendingFrames(mApi->GetId())) { + // Render thread is busy, try next time. + mSkippedComposite = true; + 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, {}, + "Too many pending frames"); + return; + } + + mCompositionOpportunityId = mCompositionOpportunityId.Next(); + MaybeGenerateFrame(aId, /* aForceGenerateFrame */ false); +} + +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) { + // This function should only get called in the root WRBP + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) { + // Skip WR render during paused state. + if (cbp->IsPaused()) { + TimeStamp now = TimeStamp::NowUnfuzzed(); + PROFILER_MARKER_TEXT("SkippedComposite", GRAPHICS, + 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(false /* useSceneBuilderThread */); + // Handle transaction that is related to DisplayList. + wr::TransactionBuilder sceneBuilderTxn; + 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(); + } + + bool generateFrame = mAsyncImageManager->GetAndResetWillGenerateFrame() || + !fastTxn.IsEmpty() || aForceGenerateFrame; + + 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(); + } + } + + SetOMTASampleTime(); + SetAPZSampleTime(); + + wr::RenderThread::Get()->IncPendingFrameCount(mApi->GetId(), aId, start); + +#if defined(ENABLE_FRAME_LATENCY_LOG) + auto startTime = TimeStamp::Now(); + mApi->SetFrameStartTime(startTime); +#endif + + MOZ_ASSERT(generateFrame); + fastTxn.GenerateFrame(aId); + 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(); + } +} + +void WebRenderBridgeParent::HoldPendingTransactionId( + const wr::Epoch& aWrEpoch, TransactionId aTransactionId, + bool aContainsSVGGroup, const VsyncId& aVsyncId, + const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime, + const TimeStamp& aTxnStartTime, const nsCString& 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(); + 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(); + return; + } + } + } + + CompositeToTarget(mCompositorScheduler->GetLastVsyncId(), nullptr, nullptr); +} + +TransactionId 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) { + TransactionId id{0}; + 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); + if (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); + + id = transactionId.mId; + mPendingTransactionIds.pop_front(); + } + return id; +} + +LayersId WebRenderBridgeParent::GetLayersId() const { + return wr::AsLayersId(mPipelineId); +} + +void WebRenderBridgeParent::ScheduleGenerateFrame() { + if (mCompositorScheduler) { + mAsyncImageManager->SetWillGenerateFrame(); + mCompositorScheduler->ScheduleComposition(); + } +} + +void WebRenderBridgeParent::FlushRendering(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(); + if (aWaitForPresent) { + FlushFramePresentation(); + } +} + +void WebRenderBridgeParent::SetClearColor(const gfx::DeviceColor& aColor) { + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + if (!IsRootWebRenderBridgeParent() || mDestroyed) { + return; + } + + mApi->SetClearColor(aColor); +} + +void WebRenderBridgeParent::Pause() { + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + if (!IsRootWebRenderBridgeParent() || mDestroyed) { + return; + } + + mApi->Pause(); + mPaused = true; +} + +bool WebRenderBridgeParent::Resume() { + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + if (!IsRootWebRenderBridgeParent() || mDestroyed) { + return false; + } + + if (!mApi->Resume()) { + return false; + } + + // Ensure we generate and render a frame immediately. + ScheduleForcedGenerateFrame(); + + mPaused = false; + return true; +} + +void WebRenderBridgeParent::ClearResources() { + if (!mApi) { + return; + } + + wr::Epoch wrEpoch = GetNextWrEpoch(); + mReceivedDisplayList = false; + // Schedule generate frame to clean up Pipeline + ScheduleGenerateFrame(); + + // 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; + 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(); + } + + 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 +} + +bool WebRenderBridgeParent::ShouldParentObserveEpoch() { + if (mParentLayersObserverEpoch == mChildLayersObserverEpoch) { + return false; + } + + mParentLayersObserverEpoch = mChildLayersObserverEpoch; + return true; +} + +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(); +} + +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, /* aUseWebRender */ true)) { + 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); + + TextureFactoryIdentifier ident( + mApi->GetBackendType(), mApi->GetCompositorType(), XRE_GetProcessType(), + mApi->GetMaxTextureSize(), false, mApi->GetUseANGLE(), + mApi->GetUseDComp(), mAsyncImageManager->UseCompositorWnd(), false, false, + false, 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); +} + +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 layers +} // namespace mozilla |