diff options
Diffstat (limited to 'gfx/layers/client')
-rw-r--r-- | gfx/layers/client/CanvasClient.cpp | 96 | ||||
-rw-r--r-- | gfx/layers/client/CanvasClient.h | 71 | ||||
-rw-r--r-- | gfx/layers/client/CompositableClient.cpp | 182 | ||||
-rw-r--r-- | gfx/layers/client/CompositableClient.h | 208 | ||||
-rw-r--r-- | gfx/layers/client/GPUVideoTextureClient.cpp | 57 | ||||
-rw-r--r-- | gfx/layers/client/GPUVideoTextureClient.h | 55 | ||||
-rw-r--r-- | gfx/layers/client/ImageClient.cpp | 277 | ||||
-rw-r--r-- | gfx/layers/client/ImageClient.h | 118 | ||||
-rw-r--r-- | gfx/layers/client/TextureClient.cpp | 1799 | ||||
-rw-r--r-- | gfx/layers/client/TextureClient.h | 812 | ||||
-rw-r--r-- | gfx/layers/client/TextureClientPool.cpp | 307 | ||||
-rw-r--r-- | gfx/layers/client/TextureClientPool.h | 175 | ||||
-rw-r--r-- | gfx/layers/client/TextureClientRecycleAllocator.cpp | 261 | ||||
-rw-r--r-- | gfx/layers/client/TextureClientRecycleAllocator.h | 140 | ||||
-rw-r--r-- | gfx/layers/client/TextureClientSharedSurface.cpp | 138 | ||||
-rw-r--r-- | gfx/layers/client/TextureClientSharedSurface.h | 68 | ||||
-rw-r--r-- | gfx/layers/client/TextureRecorded.cpp | 122 | ||||
-rw-r--r-- | gfx/layers/client/TextureRecorded.h | 60 |
18 files changed, 4946 insertions, 0 deletions
diff --git a/gfx/layers/client/CanvasClient.cpp b/gfx/layers/client/CanvasClient.cpp new file mode 100644 index 0000000000..22792e6caf --- /dev/null +++ b/gfx/layers/client/CanvasClient.cpp @@ -0,0 +1,96 @@ +/* -*- 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 "CanvasClient.h" + +#include "gfx2DGlue.h" // for ImageFormatToSurfaceFormat +#include "gfxPlatform.h" // for gfxPlatform +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/BufferTexture.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/CompositorBridgeChild.h" // for CompositorBridgeChild +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/OOPCanvasRenderer.h" +#include "mozilla/layers/TextureClient.h" // for TextureClient, etc +#include "mozilla/layers/TextureClientOGL.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#include "nsDebug.h" // for printf_stderr, NS_ASSERTION +#include "nsXULAppAPI.h" // for XRE_GetProcessType, etc + +using namespace mozilla::gfx; +using namespace mozilla::gl; + +namespace mozilla { +namespace layers { + +void CanvasClient::UseTexture(TextureClient* const aTexture) { + MOZ_ASSERT(aTexture); + + const auto isClientNonPremult = + bool(mTextureFlags & TextureFlags::NON_PREMULTIPLIED); + const auto isTextureNonPremult = + bool(aTexture->GetFlags() & TextureFlags::NON_PREMULTIPLIED); + MOZ_ALWAYS_TRUE(isTextureNonPremult == isClientNonPremult); + + bool changed = false; + + if (aTexture != mFrontBuffer) { + if (!aTexture->IsSharedWithCompositor()) { + if (!AddTextureClient(aTexture)) { + return; + } + } + changed = true; + mFrontBuffer = aTexture; + } + + AutoTArray<CompositableForwarder::TimedTextureClient, 1> textures; + CompositableForwarder::TimedTextureClient* t = textures.AppendElement(); + t->mTextureClient = aTexture; + t->mPictureRect = nsIntRect(nsIntPoint(0, 0), aTexture->GetSize()); + t->mFrameID = mFrameID; + + GetForwarder()->UseTextures(this, textures); + if (changed) { + aTexture->SyncWithObject(GetForwarder()->GetSyncObject()); + } +} + +static constexpr bool kIsWindows = +#ifdef XP_WIN + true; +#else + false; +#endif + +RefPtr<TextureClient> CanvasClient::CreateTextureClientForCanvas( + const gfx::SurfaceFormat aFormat, const gfx::IntSize aSize, + const TextureFlags aFlags) { + if (kIsWindows) { + // With WebRender, host side uses data of TextureClient longer. + // Then back buffer reuse in CanvasClient2D::Update() does not work. It + // causes a lot of TextureClient allocations. For reducing the allocations, + // TextureClientRecycler is used. + if (GetForwarder() && GetForwarder()->GetCompositorBackendType() == + LayersBackend::LAYERS_WR) { + return GetTextureClientRecycler()->CreateOrRecycle( + aFormat, aSize, BackendSelector::Canvas, mTextureFlags | aFlags); + } + return CreateTextureClientForDrawing( + aFormat, aSize, BackendSelector::Canvas, mTextureFlags | aFlags); + } + + // XXX - We should use CreateTextureClientForDrawing, but we first need + // to use double buffering. + gfx::BackendType backend = + gfxPlatform::GetPlatform()->GetPreferredCanvasBackend(); + return TextureClient::CreateForRawBufferAccess( + GetForwarder(), aFormat, aSize, backend, mTextureFlags | aFlags); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/CanvasClient.h b/gfx/layers/client/CanvasClient.h new file mode 100644 index 0000000000..2136e6f652 --- /dev/null +++ b/gfx/layers/client/CanvasClient.h @@ -0,0 +1,71 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_CANVASCLIENT_H +#define MOZILLA_GFX_CANVASCLIENT_H + +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed +#include "mozilla/layers/CompositableClient.h" // for CompositableClient +#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/TextureClient.h" // for TextureClient, etc +#include "mozilla/layers/PersistentBufferProvider.h" + +#include "mozilla/MaybeOneOf.h" + +#include "mozilla/mozalloc.h" // for operator delete + +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat + +namespace mozilla { +namespace layers { + +class CompositableForwarder; + +/** + * Compositable client for 2d and webgl canvas. + */ +class CanvasClient final : public CompositableClient { + int32_t mFrameID = 0; + RefPtr<TextureClient> mFrontBuffer; + + public: + /** + * Creates, configures, and returns a new canvas client. If necessary, a + * message will be sent to the compositor to create a corresponding image + * host. + */ + CanvasClient(CompositableForwarder* aFwd, const TextureFlags flags) + : CompositableClient(aFwd, flags) {} + + virtual ~CanvasClient() = default; + + void Clear() { mFrontBuffer = nullptr; } + + bool AddTextureClient(TextureClient* aTexture) override { + ++mFrameID; + return CompositableClient::AddTextureClient(aTexture); + } + + TextureInfo GetTextureInfo() const override { + return TextureInfo(CompositableType::IMAGE, mTextureFlags); + } + + void OnDetach() override { Clear(); } + + RefPtr<TextureClient> CreateTextureClientForCanvas(gfx::SurfaceFormat, + gfx::IntSize, + TextureFlags); + void UseTexture(TextureClient*); +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/CompositableClient.cpp b/gfx/layers/client/CompositableClient.cpp new file mode 100644 index 0000000000..9178f8f5c1 --- /dev/null +++ b/gfx/layers/client/CompositableClient.cpp @@ -0,0 +1,182 @@ +/* -*- 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/CompositableClient.h" + +#include <stdint.h> // for uint64_t, uint32_t + +#include "gfxPlatform.h" // for gfxPlatform +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/TextureClient.h" // for TextureClient, etc +#include "mozilla/layers/TextureClientOGL.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#include "mozilla/mozalloc.h" // for operator delete, etc +#ifdef XP_WIN +# include "gfxWindowsPlatform.h" // for gfxWindowsPlatform +# include "mozilla/layers/TextureD3D11.h" +#endif +#include "IPDLActor.h" +#include "gfxUtils.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +void CompositableClient::InitIPDL(const CompositableHandle& aHandle) { + MOZ_ASSERT(aHandle); + + mForwarder->AssertInForwarderThread(); + + mHandle = aHandle; + mIsAsync |= !NS_IsMainThread(); + // By simply taking the lock on mTextureClientRecycler we force memory barrier + // on mHandle and mIsAsync which makes them behave as Atomic as they are only + // ever set in this method. + auto l = mTextureClientRecycler.Lock(); +} + +CompositableClient::CompositableClient(CompositableForwarder* aForwarder, + TextureFlags aTextureFlags) + : mForwarder(aForwarder), + mTextureFlags(aTextureFlags), + mTextureClientRecycler("CompositableClient::mTextureClientRecycler"), + mIsAsync(false) {} + +CompositableClient::~CompositableClient() { Destroy(); } + +LayersBackend CompositableClient::GetCompositorBackendType() const { + return mForwarder->GetCompositorBackendType(); +} + +bool CompositableClient::Connect(ImageContainer* aImageContainer) { + MOZ_ASSERT(!mHandle); + if (!GetForwarder() || mHandle) { + return false; + } + + GetForwarder()->AssertInForwarderThread(); + GetForwarder()->Connect(this, aImageContainer); + return true; +} + +bool CompositableClient::IsConnected() const { + // CanSend() is only reliable in the same thread as the IPDL channel. + mForwarder->AssertInForwarderThread(); + return !!mHandle; +} + +void CompositableClient::Destroy() { + auto l = mTextureClientRecycler.Lock(); + if (*l) { + (*l)->Destroy(); + } + + if (mHandle) { + mForwarder->ReleaseCompositable(mHandle); + mHandle = CompositableHandle(); + } +} + +CompositableHandle CompositableClient::GetAsyncHandle() const { + if (mIsAsync) { + return mHandle; + } + return CompositableHandle(); +} + +already_AddRefed<TextureClient> CompositableClient::CreateBufferTextureClient( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + gfx::BackendType aMoz2DBackend, TextureFlags aTextureFlags) { + return TextureClient::CreateForRawBufferAccess(GetForwarder(), aFormat, aSize, + aMoz2DBackend, + aTextureFlags | mTextureFlags); +} + +already_AddRefed<TextureClient> +CompositableClient::CreateTextureClientForDrawing( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags aAllocFlags) { + return TextureClient::CreateForDrawing( + GetForwarder(), aFormat, aSize, aSelector, aTextureFlags | mTextureFlags, + aAllocFlags); +} + +already_AddRefed<TextureClient> +CompositableClient::CreateTextureClientFromSurface( + gfx::SourceSurface* aSurface, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags aAllocFlags) { + return TextureClient::CreateFromSurface(GetForwarder(), aSurface, aSelector, + aTextureFlags | mTextureFlags, + aAllocFlags); +} + +bool CompositableClient::AddTextureClient(TextureClient* aClient) { + if (!aClient) { + return false; + } + aClient->SetAddedToCompositableClient(); + return aClient->InitIPDLActor(mForwarder); +} + +void CompositableClient::ClearCachedResources() { + auto l = mTextureClientRecycler.Lock(); + if (*l) { + (*l)->ShrinkToMinimumSize(); + } +} + +void CompositableClient::HandleMemoryPressure() { + auto l = mTextureClientRecycler.Lock(); + if (*l) { + (*l)->ShrinkToMinimumSize(); + } +} + +void CompositableClient::RemoveTexture(TextureClient* aTexture) { + mForwarder->RemoveTextureFromCompositable(this, aTexture); +} + +TextureClientRecycleAllocator* CompositableClient::GetTextureClientRecycler() { + auto l = mTextureClientRecycler.Lock(); + if (*l) { + return *l; + } + + if (!mForwarder || !mForwarder->GetTextureForwarder()) { + return nullptr; + } + + *l = new layers::TextureClientRecycleAllocator(mForwarder); + return *l; +} + +void CompositableClient::DumpTextureClient(std::stringstream& aStream, + TextureClient* aTexture, + TextureDumpMode aCompress) { + if (!aTexture) { + return; + } + RefPtr<gfx::DataSourceSurface> dSurf = aTexture->GetAsSurface(); + if (!dSurf) { + return; + } + if (aCompress == TextureDumpMode::Compress) { + aStream << gfxUtils::GetAsLZ4Base64Str(dSurf).get(); + } else { + aStream << gfxUtils::GetAsDataURI(dSurf).get(); + } +} + +AutoRemoveTexture::~AutoRemoveTexture() { + if (mCompositable && mTexture && mCompositable->IsConnected()) { + mCompositable->RemoveTexture(mTexture); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/CompositableClient.h b/gfx/layers/client/CompositableClient.h new file mode 100644 index 0000000000..5ca8ae067d --- /dev/null +++ b/gfx/layers/client/CompositableClient.h @@ -0,0 +1,208 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_BUFFERCLIENT_H +#define MOZILLA_GFX_BUFFERCLIENT_H + +#include <stdint.h> // for uint64_t + +#include <map> // for map +#include <vector> // for vector + +#include "mozilla/Assertions.h" // for MOZ_CRASH +#include "mozilla/DataMutex.h" +#include "mozilla/RefPtr.h" // for already_AddRefed, RefCounted +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/LayersTypes.h" // for LayersBackend, TextureDumpMode +#include "mozilla/layers/TextureClient.h" // for TextureClient +#include "mozilla/webrender/WebRenderTypes.h" // for RenderRoot +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc + +namespace mozilla { +namespace layers { + +class CompositableClient; +class ImageBridgeChild; +class ImageContainer; +class CompositableForwarder; +class CompositableChild; +class TextureClientRecycleAllocator; +class ContentClientRemoteBuffer; + +/** + * CompositableClient manages the texture-specific logic for composite layers, + * independently of the layer. It is the content side of a CompositableClient/ + * CompositableHost pair. + * + * CompositableClient's purpose is to send texture data to the compositor side + * along with any extra information about how the texture is to be composited. + * Things like opacity or transformation belong to layer and not compositable. + * + * Since Compositables are independent of layers it is possible to create one, + * connect it to the compositor side, and start sending images to it. This alone + * is arguably not very useful, but it means that as long as a shadow layer can + * do the proper magic to find a reference to the right CompositableHost on the + * Compositor side, a Compositable client can be used outside of the main + * shadow layer forwarder machinery that is used on the main thread. + * + * The first step is to create a Compositable client and call Connect(). + * Connect() creates the underlying IPDL actor (see CompositableChild) and the + * corresponding CompositableHost on the other side. + * + * To do async texture transfer (like async-video), the CompositableClient + * should be created with a different CompositableForwarder (like + * ImageBridgeChild) and attachment is done with + * CompositableForwarder::AttachAsyncCompositable that takes an identifier + * instead of a CompositableChild, since the CompositableClient is not managed + * by this layer forwarder (the matching uses a global map on the compositor + * side, see CompositableMap in ImageBridgeParent.cpp) + * + * Subclasses: Painted layers use ContentClients, ImageLayers use ImageClients, + * Canvas layers use CanvasClients (but ImageHosts). We have a different + * subclass where we have a different way of interfacing with the textures - in + * terms of drawing into the compositable and/or passing its contents to the + * compostior. + */ +class CompositableClient { + protected: + virtual ~CompositableClient(); + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositableClient) + + explicit CompositableClient(CompositableForwarder* aForwarder, + TextureFlags aFlags = TextureFlags::NO_FLAGS); + + virtual void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false, + TextureDumpMode aCompress = TextureDumpMode::Compress){}; + + virtual TextureInfo GetTextureInfo() const = 0; + + LayersBackend GetCompositorBackendType() const; + + already_AddRefed<TextureClient> CreateBufferTextureClient( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + gfx::BackendType aMoz2dBackend = gfx::BackendType::NONE, + TextureFlags aFlags = TextureFlags::DEFAULT); + + already_AddRefed<TextureClient> CreateTextureClientForDrawing( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT); + + already_AddRefed<TextureClient> CreateTextureClientFromSurface( + gfx::SourceSurface* aSurface, BackendSelector aSelector, + TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT); + + /** + * Establishes the connection with compositor side through IPDL + */ + virtual bool Connect(ImageContainer* aImageContainer = nullptr); + + void Destroy(); + + bool IsConnected() const; + + CompositableForwarder* GetForwarder() const { return mForwarder; } + + /** + * This identifier is what lets us attach async compositables with a shadow + * layer. It is not used if the compositable is used with the regular shadow + * layer forwarder. + * + * If this returns empty, it means the compositable is not async (it is used + * on the main thread). + */ + CompositableHandle GetAsyncHandle() const; + + /** + * Handle for IPDL communication. + */ + CompositableHandle GetIPCHandle() const { return mHandle; } + + /** + * Tells the Compositor to create a TextureHost for this TextureClient. + */ + virtual bool AddTextureClient(TextureClient* aClient); + + /** + * A hook for the when the Compositable is detached from it's layer. + */ + virtual void OnDetach() {} + + /** + * Clear any resources that are not immediately necessary. This may be called + * in low-memory conditions. + */ + virtual void ClearCachedResources(); + + /** + * Shrink memory usage. + * Called when "memory-pressure" is observed. + */ + virtual void HandleMemoryPressure(); + + /** + * Should be called when deataching a TextureClient from a Compositable, + * because some platforms need to do some extra book keeping when this + * happens. + * + * See AutoRemoveTexture to automatically invoke this at the end of a scope. + */ + virtual void RemoveTexture(TextureClient* aTexture); + + void InitIPDL(const CompositableHandle& aHandle); + + TextureFlags GetTextureFlags() const { return mTextureFlags; } + + TextureClientRecycleAllocator* GetTextureClientRecycler(); + + bool HasTextureClientRecycler() { + auto lock = mTextureClientRecycler.Lock(); + return !!(*lock); + } + + static void DumpTextureClient(std::stringstream& aStream, + TextureClient* aTexture, + TextureDumpMode aCompress); + + protected: + RefPtr<CompositableForwarder> mForwarder; + // Some layers may want to enforce some flags to all their textures + // (like disallowing tiling) + Atomic<TextureFlags> mTextureFlags; + DataMutex<RefPtr<TextureClientRecycleAllocator>> mTextureClientRecycler; + + // Only ever accessed via mTextureClientRecycler's Lock + CompositableHandle mHandle; + bool mIsAsync; + + friend class CompositableChild; +}; + +/** + * Helper to call RemoveTexture at the end of a scope. + */ +struct AutoRemoveTexture { + explicit AutoRemoveTexture(CompositableClient* aCompositable, + TextureClient* aTexture = nullptr) + : mTexture(aTexture), mCompositable(aCompositable) {} + + ~AutoRemoveTexture(); + + RefPtr<TextureClient> mTexture; + + private: + CompositableClient* mCompositable; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/GPUVideoTextureClient.cpp b/gfx/layers/client/GPUVideoTextureClient.cpp new file mode 100644 index 0000000000..f946197bfe --- /dev/null +++ b/gfx/layers/client/GPUVideoTextureClient.cpp @@ -0,0 +1,57 @@ +/* -*- 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 "GPUVideoTextureClient.h" +#include "GPUVideoImage.h" +#include "mozilla/gfx/2D.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +GPUVideoTextureData::GPUVideoTextureData(IGPUVideoSurfaceManager* aManager, + const SurfaceDescriptorGPUVideo& aSD, + const gfx::IntSize& aSize) + : mManager(aManager), mSD(aSD), mSize(aSize) {} + +GPUVideoTextureData::~GPUVideoTextureData() = default; + +bool GPUVideoTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + aOutDescriptor = mSD; + return true; +} + +void GPUVideoTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = mSize; + // TODO: We should probably try do better for this. + // layers::Image doesn't expose a format, so it's hard + // to figure out in VideoDecoderParent. + aInfo.format = SurfaceFormat::B8G8R8X8; + aInfo.hasSynchronization = false; + aInfo.supportsMoz2D = false; + aInfo.canExposeMappedData = false; +} + +already_AddRefed<SourceSurface> GPUVideoTextureData::GetAsSourceSurface() { + return mManager->Readback(mSD); +} + +void GPUVideoTextureData::Deallocate(LayersIPCChannel* aAllocator) { + mManager->DeallocateSurfaceDescriptor(mSD); + mSD = SurfaceDescriptorGPUVideo(); +} + +void GPUVideoTextureData::Forget(LayersIPCChannel* aAllocator) { + // We always need to manually deallocate on the client side. + // Ideally we'd set up our TextureClient with the DEALLOCATE_CLIENT + // flag, but that forces texture destruction to be synchronous. + // Instead let's just deallocate from here as well. + Deallocate(aAllocator); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/GPUVideoTextureClient.h b/gfx/layers/client/GPUVideoTextureClient.h new file mode 100644 index 0000000000..93b2e761ca --- /dev/null +++ b/gfx/layers/client/GPUVideoTextureClient.h @@ -0,0 +1,55 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_GPUVIDEOTEXTURECLIENT_H +#define MOZILLA_GFX_GPUVIDEOTEXTURECLIENT_H + +#include "mozilla/layers/TextureClient.h" + +namespace mozilla { +namespace gfx { +class SourceSurface; +} + +namespace layers { +class IGPUVideoSurfaceManager; + +class GPUVideoTextureData : public TextureData { + public: + GPUVideoTextureData(IGPUVideoSurfaceManager* aManager, + const SurfaceDescriptorGPUVideo& aSD, + const gfx::IntSize& aSize); + virtual ~GPUVideoTextureData(); + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Lock(OpenMode) override { return true; }; + + void Unlock() override{}; + + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + void Deallocate(LayersIPCChannel* aAllocator) override; + + void Forget(LayersIPCChannel* aAllocator) override; + + already_AddRefed<gfx::SourceSurface> GetAsSourceSurface(); + + GPUVideoTextureData* AsGPUVideoTextureData() override { return this; } + + protected: + RefPtr<IGPUVideoSurfaceManager> mManager; + SurfaceDescriptorGPUVideo mSD; + gfx::IntSize mSize; + + public: + const decltype(mSD)& SD() const { return mSD; } +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_GPUVIDEOTEXTURECLIENT_H diff --git a/gfx/layers/client/ImageClient.cpp b/gfx/layers/client/ImageClient.cpp new file mode 100644 index 0000000000..fb89d69f6b --- /dev/null +++ b/gfx/layers/client/ImageClient.cpp @@ -0,0 +1,277 @@ +/* -*- 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 "ImageClient.h" + +#include <stdint.h> // for uint32_t + +#include "ImageContainer.h" // for Image, PlanarYCbCrImage, etc +#include "ImageTypes.h" // for ImageFormat::PLANAR_YCBCR, etc +#include "GLImages.h" // for SurfaceTextureImage::Data, etc +#include "gfx2DGlue.h" // for ImageFormatToSurfaceFormat +#include "gfxPlatform.h" // for gfxPlatform +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat, etc +#include "mozilla/layers/CompositableClient.h" // for CompositableClient +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/CompositorTypes.h" // for CompositableType, etc +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor, etc +#include "mozilla/layers/TextureForwarder.h" +#include "mozilla/layers/TextureClient.h" // for TextureClient, etc +#include "mozilla/layers/TextureClientOGL.h" // for SurfaceTextureClient +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_WARNING, NS_ASSERTION +#include "nsISupportsImpl.h" // for Image::Release, etc +#include "nsRect.h" // for mozilla::gfx::IntRect + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +/* static */ +already_AddRefed<ImageClient> ImageClient::CreateImageClient( + CompositableType aCompositableHostType, CompositableForwarder* aForwarder, + TextureFlags aFlags) { + RefPtr<ImageClient> result = nullptr; + switch (aCompositableHostType) { + case CompositableType::IMAGE: + result = + new ImageClientSingle(aForwarder, aFlags, CompositableType::IMAGE); + break; + case CompositableType::UNKNOWN: + result = nullptr; + break; + default: + MOZ_CRASH("GFX: unhandled program type image"); + } + + NS_ASSERTION(result, "Failed to create ImageClient"); + + return result.forget(); +} + +void ImageClient::RemoveTexture(TextureClient* aTexture) { + GetForwarder()->RemoveTextureFromCompositable(this, aTexture); +} + +ImageClientSingle::ImageClientSingle(CompositableForwarder* aFwd, + TextureFlags aFlags, + CompositableType aType) + : ImageClient(aFwd, aFlags, aType) {} + +TextureInfo ImageClientSingle::GetTextureInfo() const { + return TextureInfo(CompositableType::IMAGE); +} + +void ImageClientSingle::FlushAllImages() { + for (auto& b : mBuffers) { + // It should be safe to just assume a default render root here, even if + // the texture actually presents in a content render root, as the only + // risk would be if the content render root has not / is not going to + // generate a frame before the texture gets cleared. + RemoveTexture(b.mTextureClient); + } + mBuffers.Clear(); +} + +/* static */ +already_AddRefed<TextureClient> ImageClient::CreateTextureClientForImage( + Image* aImage, KnowsCompositor* aKnowsCompositor) { + RefPtr<TextureClient> texture; + if (aImage->GetFormat() == ImageFormat::PLANAR_YCBCR) { + PlanarYCbCrImage* ycbcr = static_cast<PlanarYCbCrImage*>(aImage); + const PlanarYCbCrData* data = ycbcr->GetData(); + if (!data) { + return nullptr; + } + texture = TextureClient::CreateForYCbCr( + aKnowsCompositor, data->mPictureRect, data->YDataSize(), data->mYStride, + data->CbCrDataSize(), data->mCbCrStride, data->mStereoMode, + data->mColorDepth, data->mYUVColorSpace, data->mColorRange, + data->mChromaSubsampling, TextureFlags::DEFAULT); + if (!texture) { + return nullptr; + } + + TextureClientAutoLock autoLock(texture, OpenMode::OPEN_WRITE_ONLY); + if (!autoLock.Succeeded()) { + return nullptr; + } + + bool status = UpdateYCbCrTextureClient(texture, *data); + MOZ_ASSERT(status); + if (!status) { + return nullptr; + } +#ifdef MOZ_WIDGET_ANDROID + } else if (aImage->GetFormat() == ImageFormat::SURFACE_TEXTURE) { + gfx::IntSize size = aImage->GetSize(); + SurfaceTextureImage* typedImage = aImage->AsSurfaceTextureImage(); + texture = AndroidSurfaceTextureData::CreateTextureClient( + typedImage->GetHandle(), size, typedImage->GetContinuous(), + typedImage->GetOriginPos(), typedImage->GetHasAlpha(), + typedImage->GetTransformOverride(), + aKnowsCompositor->GetTextureForwarder(), TextureFlags::DEFAULT); +#endif + } else { + RefPtr<gfx::SourceSurface> surface = aImage->GetAsSourceSurface(); + MOZ_ASSERT(surface); + texture = TextureClient::CreateForDrawing( + aKnowsCompositor, surface->GetFormat(), aImage->GetSize(), + BackendSelector::Content, TextureFlags::DEFAULT); + if (!texture) { + return nullptr; + } + + MOZ_ASSERT(texture->CanExposeDrawTarget()); + + if (!texture->Lock(OpenMode::OPEN_WRITE_ONLY)) { + return nullptr; + } + + { + // We must not keep a reference to the DrawTarget after it has been + // unlocked. + DrawTarget* dt = texture->BorrowDrawTarget(); + if (!dt) { + gfxWarning() + << "ImageClientSingle::UpdateImage failed in BorrowDrawTarget"; + return nullptr; + } + MOZ_ASSERT(surface.get()); + dt->CopySurface(surface, IntRect(IntPoint(), surface->GetSize()), + IntPoint()); + } + + texture->Unlock(); + } + return texture.forget(); +} + +bool ImageClientSingle::UpdateImage(ImageContainer* aContainer) { + AutoTArray<ImageContainer::OwningImage, 4> images; + uint32_t generationCounter; + aContainer->GetCurrentImages(&images, &generationCounter); + + if (mLastUpdateGenerationCounter == generationCounter) { + return true; + } + mLastUpdateGenerationCounter = generationCounter; + + // Don't try to update to invalid images. + images.RemoveElementsBy( + [](const auto& image) { return !image.mImage->IsValid(); }); + if (images.IsEmpty()) { + // This can happen if a ClearAllImages raced with SetCurrentImages from + // another thread and ClearImagesFromImageBridge ran after the + // SetCurrentImages call but before UpdateImageClientNow. + // This can also happen if all images in the list are invalid. + // We return true because the caller would attempt to recreate the + // ImageClient otherwise, and that isn't going to help. + for (auto& b : mBuffers) { + RemoveTexture(b.mTextureClient); + } + mBuffers.Clear(); + return true; + } + + nsTArray<Buffer> newBuffers; + AutoTArray<CompositableForwarder::TimedTextureClient, 4> textures; + + for (auto& img : images) { + Image* image = img.mImage; + + RefPtr<TextureClient> texture = image->GetTextureClient(GetForwarder()); + const bool hasTextureClient = !!texture; + + for (int32_t i = mBuffers.Length() - 1; i >= 0; --i) { + if (mBuffers[i].mImageSerial == image->GetSerial()) { + if (hasTextureClient) { + MOZ_ASSERT(image->GetTextureClient(GetForwarder()) == + mBuffers[i].mTextureClient); + } else { + texture = mBuffers[i].mTextureClient; + } + // Remove this element from mBuffers so mBuffers only contains + // images that aren't present in 'images' + mBuffers.RemoveElementAt(i); + } + } + + if (!texture) { + // Slow path, we should not be hitting it very often and if we do it means + // we are using an Image class that is not backed by textureClient and we + // should fix it. + texture = CreateTextureClientForImage(image, GetForwarder()); + } + + if (!texture) { + return false; + } + + // We check if the texture's allocator is still open, since in between media + // decoding a frame and adding it to the compositable, we could have + // restarted the GPU process. + if (!texture->GetAllocator()->IPCOpen()) { + continue; + } + if (!AddTextureClient(texture)) { + return false; + } + + CompositableForwarder::TimedTextureClient* t = textures.AppendElement(); + t->mTextureClient = texture; + t->mTimeStamp = img.mTimeStamp; + t->mPictureRect = image->GetPictureRect(); + t->mFrameID = img.mFrameID; + t->mProducerID = img.mProducerID; + + Buffer* newBuf = newBuffers.AppendElement(); + newBuf->mImageSerial = image->GetSerial(); + newBuf->mTextureClient = texture; + + texture->SyncWithObject(GetForwarder()->GetSyncObject()); + } + + GetForwarder()->UseTextures(this, textures); + + for (auto& b : mBuffers) { + RemoveTexture(b.mTextureClient); + } + mBuffers = std::move(newBuffers); + + return true; +} + +RefPtr<TextureClient> ImageClientSingle::GetForwardedTexture() { + if (mBuffers.Length() == 0) { + return nullptr; + } + return mBuffers[0].mTextureClient; +} + +bool ImageClientSingle::AddTextureClient(TextureClient* aTexture) { + MOZ_ASSERT((mTextureFlags & aTexture->GetFlags()) == mTextureFlags); + return CompositableClient::AddTextureClient(aTexture); +} + +void ImageClientSingle::OnDetach() { mBuffers.Clear(); } + +ImageClient::ImageClient(CompositableForwarder* aFwd, TextureFlags aFlags, + CompositableType aType) + : CompositableClient(aFwd, aFlags), + mType(aType), + mLastUpdateGenerationCounter(0) {} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ImageClient.h b/gfx/layers/client/ImageClient.h new file mode 100644 index 0000000000..899e40adff --- /dev/null +++ b/gfx/layers/client/ImageClient.h @@ -0,0 +1,118 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_IMAGECLIENT_H +#define MOZILLA_GFX_IMAGECLIENT_H + +#include <stdint.h> // for uint32_t, uint64_t +#include <sys/types.h> // for int32_t +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "mozilla/layers/CompositableClient.h" // for CompositableClient +#include "mozilla/layers/CompositorTypes.h" // for CompositableType, etc +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/TextureClient.h" // for TextureClient, etc +#include "mozilla/mozalloc.h" // for operator delete +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsRect.h" // for mozilla::gfx::IntRect + +namespace mozilla { +namespace layers { + +class CompositableForwarder; +class Image; +class ImageContainer; +class ImageClientSingle; + +/** + * Image clients are used by basic image layers on the content thread, they + * always match with an ImageHost on the compositor thread. See + * CompositableClient.h for information on connecting clients to hosts. + */ +class ImageClient : public CompositableClient { + public: + /** + * Creates, configures, and returns a new image client. If necessary, a + * message will be sent to the compositor to create a corresponding image + * host. + */ + static already_AddRefed<ImageClient> CreateImageClient( + CompositableType aImageHostType, CompositableForwarder* aFwd, + TextureFlags aFlags); + + virtual ~ImageClient() = default; + + /** + * Update this ImageClient from aContainer in aLayer + * returns false if this is the wrong kind of ImageClient for aContainer. + * Note that returning true does not necessarily imply success + */ + virtual bool UpdateImage(ImageContainer* aContainer) = 0; + + /** + * asynchronously remove all the textures used by the image client. + * + */ + virtual void FlushAllImages() {} + + virtual void RemoveTexture(TextureClient* aTexture) override; + + virtual ImageClientSingle* AsImageClientSingle() { return nullptr; } + + static already_AddRefed<TextureClient> CreateTextureClientForImage( + Image* aImage, KnowsCompositor* aForwarder); + + uint32_t GetLastUpdateGenerationCounter() { + return mLastUpdateGenerationCounter; + } + + virtual RefPtr<TextureClient> GetForwardedTexture() { return nullptr; } + + protected: + ImageClient(CompositableForwarder* aFwd, TextureFlags aFlags, + CompositableType aType); + + CompositableType mType; + uint32_t mLastUpdateGenerationCounter; +}; + +/** + * An image client which uses a single texture client. + */ +class ImageClientSingle : public ImageClient { + public: + ImageClientSingle(CompositableForwarder* aFwd, TextureFlags aFlags, + CompositableType aType); + + bool UpdateImage(ImageContainer* aContainer) override; + + void OnDetach() override; + + bool AddTextureClient(TextureClient* aTexture) override; + + TextureInfo GetTextureInfo() const override; + + void FlushAllImages() override; + + ImageClientSingle* AsImageClientSingle() override { return this; } + + RefPtr<TextureClient> GetForwardedTexture() override; + + bool IsEmpty() { return mBuffers.IsEmpty(); } + + protected: + struct Buffer { + RefPtr<TextureClient> mTextureClient; + int32_t mImageSerial; + }; + nsTArray<Buffer> mBuffers; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/TextureClient.cpp b/gfx/layers/client/TextureClient.cpp new file mode 100644 index 0000000000..6bf6bcd301 --- /dev/null +++ b/gfx/layers/client/TextureClient.cpp @@ -0,0 +1,1799 @@ +/* -*- 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/TextureClient.h" + +#include <stdint.h> // for uint8_t, uint32_t, etc + +#include "BufferTexture.h" +#include "IPDLActor.h" +#include "ImageContainer.h" // for PlanarYCbCrData, etc +#include "MainThreadUtils.h" +#include "gfx2DGlue.h" +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxUtils.h" // for gfxUtils::GetAsLZ4Base64Str +#include "mozilla/Atomics.h" +#include "mozilla/Mutex.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" // for CreateDataSourceSurfaceByCloning +#include "mozilla/gfx/Logging.h" // for gfxDebug +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/ipc/CrossProcessSemaphore.h" +#include "mozilla/ipc/SharedMemory.h" // for SharedMemory, etc +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/PTextureChild.h" +#include "mozilla/layers/TextureClientOGL.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#include "mozilla/layers/TextureRecorded.h" +#include "nsDebug.h" // for NS_ASSERTION, NS_WARNING, etc +#include "nsISerialEventTarget.h" +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsPrintfCString.h" // for nsPrintfCString + +#ifdef XP_WIN +# include "gfx2DGlue.h" +# include "gfxWindowsPlatform.h" +# include "mozilla/gfx/DeviceManagerDx.h" +# include "mozilla/layers/TextureD3D11.h" +#endif +#ifdef MOZ_WAYLAND +# include <gtk/gtkx.h> + +# include "gfxPlatformGtk.h" +# include "mozilla/layers/DMABUFTextureClientOGL.h" +# include "mozilla/widget/nsWaylandDisplay.h" +#endif + +#ifdef XP_MACOSX +# include "mozilla/layers/MacIOSurfaceTextureClientOGL.h" +#endif + +#if 0 +# define RECYCLE_LOG(...) printf_stderr(__VA_ARGS__) +#else +# define RECYCLE_LOG(...) \ + do { \ + } while (0) +#endif + +namespace mozilla::layers { + +using namespace mozilla::ipc; +using namespace mozilla::gl; +using namespace mozilla::gfx; + +struct TextureDeallocParams { + TextureData* data; + RefPtr<TextureChild> actor; + RefPtr<LayersIPCChannel> allocator; + bool clientDeallocation; + bool syncDeallocation; +}; + +void DeallocateTextureClient(TextureDeallocParams params); + +/** + * TextureChild is the content-side incarnation of the PTexture IPDL actor. + * + * TextureChild is used to synchronize a texture client and its corresponding + * TextureHost if needed (a TextureClient that is not shared with the compositor + * does not have a TextureChild) + * + * During the deallocation phase, a TextureChild may hold its recently destroyed + * TextureClient's data until the compositor side confirmed that it is safe to + * deallocte or recycle the it. + */ +class TextureChild final : PTextureChild { + ~TextureChild() { + // We should have deallocated mTextureData in ActorDestroy + MOZ_ASSERT(!mTextureData); + MOZ_ASSERT_IF(!mOwnerCalledDestroy, !mTextureClient); + } + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TextureChild) + + TextureChild() + : mCompositableForwarder(nullptr), + mTextureForwarder(nullptr), + mTextureClient(nullptr), + mTextureData(nullptr), + mDestroyed(false), + mIPCOpen(false), + mOwnsTextureData(false), + mOwnerCalledDestroy(false), + mUsesImageBridge(false) {} + + mozilla::ipc::IPCResult Recv__delete__() override { return IPC_OK(); } + + LayersIPCChannel* GetAllocator() { return mTextureForwarder; } + + void ActorDestroy(ActorDestroyReason why) override; + + bool IPCOpen() const { return mIPCOpen; } + + void Lock() const { + if (mUsesImageBridge) { + mLock.Enter(); + } + } + + void Unlock() const { + if (mUsesImageBridge) { + mLock.Leave(); + } + } + + private: + // AddIPDLReference and ReleaseIPDLReference are only to be called by + // CreateIPDLActor and DestroyIPDLActor, respectively. We intentionally make + // them private to prevent misuse. The purpose of these methods is to be aware + // of when the IPC system around this actor goes down: mIPCOpen is then set to + // false. + void AddIPDLReference() { + MOZ_ASSERT(mIPCOpen == false); + mIPCOpen = true; + AddRef(); + } + void ReleaseIPDLReference() { + MOZ_ASSERT(mIPCOpen == false); + Release(); + } + + /// The normal way to destroy the actor. + /// + /// This will asynchronously send a Destroy message to the parent actor, whom + /// will send the delete message. + void Destroy(const TextureDeallocParams& aParams); + + // This lock is used order to prevent several threads to access the + // TextureClient's data concurrently. In particular, it prevents shutdown + // code to destroy a texture while another thread is reading or writing into + // it. + // In most places, the lock is held in short and bounded scopes in which we + // don't block on any other resource. There are few exceptions to this, which + // are discussed below. + // + // The locking pattern of TextureClient may in some case upset deadlock + // detection tools such as TSan. Typically our tile rendering code will lock + // all of its tiles, render into them and unlock them all right after that, + // which looks something like: + // + // Lock tile A + // Lock tile B + // Lock tile C + // Apply drawing commands to tiles A, B and C + // Unlock tile A + // Unlock tile B + // Unlock tile C + // + // And later, we may end up rendering a tile buffer that has the same tiles, + // in a different order, for example: + // + // Lock tile B + // Lock tile A + // Lock tile D + // Apply drawing commands to tiles A, B and D + // Unlock tile B + // Unlock tile A + // Unlock tile D + // + // This is because textures being expensive to create, we recycle them as much + // as possible and they may reappear in the tile buffer in a different order. + // + // Unfortunately this is not very friendly to TSan's analysis, which will see + // that B was once locked while A was locked, and then A locked while B was + // locked. TSan identifies this as a potential dead-lock which would be the + // case if this kind of inconsistent and dependent locking order was happening + // concurrently. + // In the case of TextureClient, dependent locking only ever happens on the + // thread that draws into the texture (let's call it the producer thread). + // Other threads may call into a method that can lock the texture in a short + // and bounded scope inside of which it is not allowed to do anything that + // could cause the thread to block. A given texture can only have one producer + // thread. + // + // Another example of TSan-unfriendly locking pattern is when copying a + // texture into another, which also never happens outside of the producer + // thread. Copying A into B looks like this: + // + // Lock texture B + // Lock texture A + // Copy A into B + // Unlock A + // Unlock B + // + // In a given frame we may need to copy A into B and in another frame copy + // B into A. For example A and B can be the Front and Back buffers, + // alternating roles and the copy is needed to avoid the cost of re-drawing + // the valid region. + // + // The important rule is that all of the dependent locking must occur only + // in the texture's producer thread to avoid deadlocks. + mutable gfx::CriticalSection mLock; + + RefPtr<CompositableForwarder> mCompositableForwarder; + RefPtr<TextureForwarder> mTextureForwarder; + + TextureClient* mTextureClient; + TextureData* mTextureData; + Atomic<bool> mDestroyed; + bool mIPCOpen; + bool mOwnsTextureData; + bool mOwnerCalledDestroy; + bool mUsesImageBridge; + + friend class TextureClient; + friend void DeallocateTextureClient(TextureDeallocParams params); +}; + +static inline gfx::BackendType BackendTypeForBackendSelector( + LayersBackend aLayersBackend, BackendSelector aSelector) { + switch (aSelector) { + case BackendSelector::Canvas: + return gfxPlatform::GetPlatform()->GetPreferredCanvasBackend(); + case BackendSelector::Content: + return gfxPlatform::GetPlatform()->GetContentBackendFor(aLayersBackend); + default: + MOZ_ASSERT_UNREACHABLE("Unknown backend selector"); + return gfx::BackendType::NONE; + } +}; + +static TextureType GetTextureType(gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, + KnowsCompositor* aKnowsCompositor, + BackendSelector aSelector, + TextureAllocationFlags aAllocFlags) { + LayersBackend layersBackend = aKnowsCompositor->GetCompositorBackendType(); + gfx::BackendType moz2DBackend = + BackendTypeForBackendSelector(layersBackend, aSelector); + Unused << moz2DBackend; + +#ifdef XP_WIN + int32_t maxTextureSize = aKnowsCompositor->GetMaxTextureSize(); + if ((layersBackend == LayersBackend::LAYERS_WR && + !aKnowsCompositor->UsingSoftwareWebRender()) && + (moz2DBackend == gfx::BackendType::DIRECT2D || + moz2DBackend == gfx::BackendType::DIRECT2D1_1) && + aSize.width <= maxTextureSize && aSize.height <= maxTextureSize && + !(aAllocFlags & (ALLOC_UPDATE_FROM_SURFACE | ALLOC_DO_NOT_ACCELERATE))) { + return TextureType::D3D11; + } +#endif + +#ifdef MOZ_WAYLAND + if ((layersBackend == LayersBackend::LAYERS_WR && + !aKnowsCompositor->UsingSoftwareWebRender()) && + widget::GetDMABufDevice()->IsDMABufTexturesEnabled() && + aFormat != SurfaceFormat::A8) { + return TextureType::DMABUF; + } +#endif + +#ifdef XP_MACOSX + if (StaticPrefs::gfx_use_iosurface_textures_AtStartup()) { + return TextureType::MacIOSurface; + } +#endif + +#ifdef MOZ_WIDGET_ANDROID + if (gfxVars::UseAHardwareBufferContent() && + aSelector == BackendSelector::Content) { + return TextureType::AndroidHardwareBuffer; + } + if (StaticPrefs::gfx_use_surfacetexture_textures_AtStartup()) { + return TextureType::AndroidNativeWindow; + } +#endif + + return TextureType::Unknown; +} + +TextureType PreferredCanvasTextureType(KnowsCompositor* aKnowsCompositor) { + return GetTextureType(gfx::SurfaceFormat::R8G8B8A8, {1, 1}, aKnowsCompositor, + BackendSelector::Canvas, + TextureAllocationFlags::ALLOC_DEFAULT); +} + +static bool ShouldRemoteTextureType(TextureType aTextureType, + BackendSelector aSelector) { + if (aSelector != BackendSelector::Canvas || !gfxPlatform::UseRemoteCanvas()) { + return false; + } + + switch (aTextureType) { + case TextureType::D3D11: + return true; + default: + return false; + } +} + +/* static */ +TextureData* TextureData::Create(TextureForwarder* aAllocator, + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + KnowsCompositor* aKnowsCompositor, + BackendSelector aSelector, + TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags) { + TextureType textureType = + GetTextureType(aFormat, aSize, aKnowsCompositor, aSelector, aAllocFlags); + + if (ShouldRemoteTextureType(textureType, aSelector)) { + RefPtr<CanvasChild> canvasChild = aAllocator->GetCanvasChild(); + if (canvasChild) { + return new RecordedTextureData(canvasChild.forget(), aSize, aFormat, + textureType); + } + + // We don't have a CanvasChild, but are supposed to be remote. + // Fall back to software. + textureType = TextureType::Unknown; + } + +#if defined(XP_MACOSX) || defined(MOZ_WAYLAND) + gfx::BackendType moz2DBackend = BackendTypeForBackendSelector( + aKnowsCompositor->GetCompositorBackendType(), aSelector); +#endif + + switch (textureType) { +#ifdef XP_WIN + case TextureType::D3D11: + return D3D11TextureData::Create(aSize, aFormat, aAllocFlags); +#endif + +#ifdef MOZ_WAYLAND + case TextureType::DMABUF: + return DMABUFTextureData::Create(aSize, aFormat, moz2DBackend); +#endif + +#ifdef XP_MACOSX + case TextureType::MacIOSurface: + return MacIOSurfaceTextureData::Create(aSize, aFormat, moz2DBackend); +#endif +#ifdef MOZ_WIDGET_ANDROID + case TextureType::AndroidHardwareBuffer: + return AndroidHardwareBufferTextureData::Create(aSize, aFormat); + case TextureType::AndroidNativeWindow: + return AndroidNativeWindowTextureData::Create(aSize, aFormat); +#endif + default: + return nullptr; + } +} + +/* static */ +bool TextureData::IsRemote(KnowsCompositor* aKnowsCompositor, + BackendSelector aSelector) { + TextureType textureType = GetTextureType( + gfx::SurfaceFormat::UNKNOWN, gfx::IntSize(1, 1), aKnowsCompositor, + aSelector, TextureAllocationFlags::ALLOC_DEFAULT); + + return ShouldRemoteTextureType(textureType, aSelector); +} + +static void DestroyTextureData(TextureData* aTextureData, + LayersIPCChannel* aAllocator, bool aDeallocate) { + if (!aTextureData) { + return; + } + + if (aDeallocate) { + aTextureData->Deallocate(aAllocator); + } else { + aTextureData->Forget(aAllocator); + } + delete aTextureData; +} + +void TextureChild::ActorDestroy(ActorDestroyReason why) { + AUTO_PROFILER_LABEL("TextureChild::ActorDestroy", GRAPHICS); + MOZ_ASSERT(mIPCOpen); + mIPCOpen = false; + + if (mTextureData) { + DestroyTextureData(mTextureData, GetAllocator(), mOwnsTextureData); + mTextureData = nullptr; + } +} + +void TextureChild::Destroy(const TextureDeallocParams& aParams) { + MOZ_ASSERT(!mOwnerCalledDestroy); + if (mOwnerCalledDestroy) { + return; + } + + mOwnerCalledDestroy = true; + + if (!IPCOpen()) { + DestroyTextureData(aParams.data, aParams.allocator, + aParams.clientDeallocation); + return; + } + + // DestroyTextureData will be called by TextureChild::ActorDestroy + mTextureData = aParams.data; + mOwnsTextureData = aParams.clientDeallocation; + + if (!mCompositableForwarder || + !mCompositableForwarder->DestroyInTransaction(this)) { + this->SendDestroy(); + } +} + +/* static */ +Atomic<uint64_t> TextureClient::sSerialCounter(0); + +static void DeallocateTextureClientSyncProxy(TextureDeallocParams params, + ReentrantMonitor* aBarrier, + bool* aDone) { + DeallocateTextureClient(params); + ReentrantMonitorAutoEnter autoMon(*aBarrier); + *aDone = true; + aBarrier->NotifyAll(); +} + +/// The logic for synchronizing a TextureClient's deallocation goes here. +/// +/// This funciton takes care of dispatching work to the right thread using +/// a synchronous proxy if needed, and handles client/host deallocation. +void DeallocateTextureClient(TextureDeallocParams params) { + if (!params.actor && !params.data) { + // Nothing to do + return; + } + + TextureChild* actor = params.actor; + nsCOMPtr<nsISerialEventTarget> ipdlThread; + + if (params.allocator) { + ipdlThread = params.allocator->GetThread(); + if (!ipdlThread) { + // An allocator with no thread means we are too late in the shutdown + // sequence. + gfxCriticalError() << "Texture deallocated too late during shutdown"; + return; + } + } + + // First make sure that the work is happening on the IPDL thread. + if (ipdlThread && !ipdlThread->IsOnCurrentThread()) { + if (params.syncDeallocation) { + bool done = false; + ReentrantMonitor barrier MOZ_UNANNOTATED("DeallocateTextureClient"); + ReentrantMonitorAutoEnter autoMon(barrier); + ipdlThread->Dispatch(NewRunnableFunction( + "DeallocateTextureClientSyncProxyRunnable", + DeallocateTextureClientSyncProxy, params, &barrier, &done)); + while (!done) { + barrier.Wait(); + } + } else { + ipdlThread->Dispatch(NewRunnableFunction( + "DeallocateTextureClientRunnable", DeallocateTextureClient, params)); + } + // The work has been forwarded to the IPDL thread, we are done. + return; + } + + // Below this line, we are either in the IPDL thread or ther is no IPDL + // thread anymore. + + if (!ipdlThread) { + // If we don't have a thread we can't know for sure that we are in + // the IPDL thread and use the LayersIPCChannel. + // This should ideally not happen outside of gtest, but some shutdown + // raciness could put us in this situation. + params.allocator = nullptr; + } + + if (!actor) { + // We don't have an IPDL actor, probably because we destroyed the + // TextureClient before sharing it with the compositor. It means the data + // cannot be owned by the TextureHost since we never created the + // TextureHost... + DestroyTextureData(params.data, params.allocator, /* aDeallocate */ true); + return; + } + + actor->Destroy(params); +} + +void TextureClient::Destroy() { + // Async paints should have been flushed by now. + MOZ_RELEASE_ASSERT(mPaintThreadRefs == 0); + + if (mActor && !mIsLocked) { + mActor->Lock(); + } + + mBorrowedDrawTarget = nullptr; + mReadLock = nullptr; + + RefPtr<TextureChild> actor = mActor; + mActor = nullptr; + + if (actor && !actor->mDestroyed.compareExchange(false, true)) { + actor->Unlock(); + actor = nullptr; + } + + TextureData* data = mData; + mData = nullptr; + + if (data || actor) { + TextureDeallocParams params; + params.actor = actor; + params.allocator = mAllocator; + params.clientDeallocation = !!(mFlags & TextureFlags::DEALLOCATE_CLIENT); + params.data = data; + // At the moment we always deallocate synchronously when deallocating on the + // client side, but having asynchronous deallocate in some of the cases will + // be a worthwhile optimization. + params.syncDeallocation = !!(mFlags & TextureFlags::DEALLOCATE_CLIENT); + + // Release the lock before calling DeallocateTextureClient because the + // latter may wait for the main thread which could create a dead-lock. + + if (actor) { + actor->Unlock(); + } + + DeallocateTextureClient(params); + } +} + +void TextureClient::LockActor() const { + if (mActor) { + mActor->Lock(); + } +} + +void TextureClient::UnlockActor() const { + if (mActor) { + mActor->Unlock(); + } +} + +bool TextureClient::IsReadLocked() const { + if (!mReadLock) { + return false; + } + MOZ_ASSERT(mReadLock->AsNonBlockingLock(), + "Can only check locked for non-blocking locks!"); + return mReadLock->AsNonBlockingLock()->GetReadCount() > 1; +} + +bool TextureClient::TryReadLock() { + if (!mReadLock || mIsReadLocked) { + return true; + } + + if (mReadLock->AsNonBlockingLock()) { + if (IsReadLocked()) { + return false; + } + } + + if (!mReadLock->TryReadLock(TimeDuration::FromMilliseconds(500))) { + return false; + } + + mIsReadLocked = true; + return true; +} + +void TextureClient::ReadUnlock() { + if (!mIsReadLocked) { + return; + } + MOZ_ASSERT(mReadLock); + mReadLock->ReadUnlock(); + mIsReadLocked = false; +} + +bool TextureClient::Lock(OpenMode aMode) { + MOZ_ASSERT(IsValid()); + MOZ_ASSERT(!mIsLocked); + if (!IsValid()) { + return false; + } + if (mIsLocked) { + return mOpenMode == aMode; + } + + if ((aMode & OpenMode::OPEN_WRITE || !mInfo.canConcurrentlyReadLock) && + !TryReadLock()) { + // Only warn if attempting to write. Attempting to read is acceptable usage. + if (aMode & OpenMode::OPEN_WRITE) { + NS_WARNING( + "Attempt to Lock a texture that is being read by the compositor!"); + } + return false; + } + + LockActor(); + + mIsLocked = mData->Lock(aMode); + mOpenMode = aMode; + + auto format = GetFormat(); + if (mIsLocked && CanExposeDrawTarget() && + (aMode & OpenMode::OPEN_READ_WRITE) == OpenMode::OPEN_READ_WRITE && + NS_IsMainThread() && + // the formats that we apparently expect, in the cairo backend. Any other + // format will trigger an assertion in GfxFormatToCairoFormat. + (format == SurfaceFormat::A8R8G8B8_UINT32 || + format == SurfaceFormat::X8R8G8B8_UINT32 || + format == SurfaceFormat::A8 || format == SurfaceFormat::R5G6B5_UINT16)) { + if (!BorrowDrawTarget()) { + // Failed to get a DrawTarget, means we won't be able to write into the + // texture, might as well fail now. + Unlock(); + return false; + } + } + + if (!mIsLocked) { + UnlockActor(); + ReadUnlock(); + } + + return mIsLocked; +} + +void TextureClient::Unlock() { + MOZ_ASSERT(IsValid()); + MOZ_ASSERT(mIsLocked); + if (!IsValid() || !mIsLocked) { + return; + } + + if (mBorrowedDrawTarget) { + if (!(mOpenMode & OpenMode::OPEN_ASYNC)) { + if (mOpenMode & OpenMode::OPEN_WRITE) { + mBorrowedDrawTarget->Flush(); + } + + mBorrowedDrawTarget->DetachAllSnapshots(); + // If this assertion is hit, it means something is holding a strong + // reference to our DrawTarget externally, which is not allowed. + MOZ_ASSERT(mBorrowedDrawTarget->refCount() <= mExpectedDtRefs); + } + + mBorrowedDrawTarget = nullptr; + } + + if (mOpenMode & OpenMode::OPEN_WRITE) { + mUpdated = true; + } + + if (mData) { + mData->Unlock(); + } + mIsLocked = false; + mOpenMode = OpenMode::OPEN_NONE; + + UnlockActor(); + ReadUnlock(); +} + +void TextureClient::EnableReadLock() { + if (!mReadLock) { + if (mAllocator->GetTileLockAllocator()) { + mReadLock = NonBlockingTextureReadLock::Create(mAllocator); + } else { + // IPC is down + gfxCriticalError() << "TextureClient::EnableReadLock IPC is down"; + } + } +} + +bool TextureClient::OnForwardedToHost() { + if (mData) { + mData->OnForwardedToHost(); + } + + if (mReadLock && mUpdated) { + // Take a read lock on behalf of the TextureHost. The latter will unlock + // after the shared data is available again for drawing. + mReadLock->ReadLock(); + mUpdated = false; + return true; + } + + return false; +} + +TextureClient::~TextureClient() { + // TextureClients should be kept alive while there are references on the + // paint thread. + MOZ_ASSERT(mPaintThreadRefs == 0); + mReadLock = nullptr; + Destroy(); +} + +void TextureClient::UpdateFromSurface(gfx::SourceSurface* aSurface) { + MOZ_ASSERT(IsValid()); + MOZ_ASSERT(mIsLocked); + MOZ_ASSERT(aSurface); + // If you run into this assertion, make sure the texture was locked write-only + // rather than read-write. + MOZ_ASSERT(!mBorrowedDrawTarget); + + // XXX - It would be better to first try the DrawTarget approach and fallback + // to the backend-specific implementation because the latter will usually do + // an expensive read-back + cpu-side copy if the texture is on the gpu. + // There is a bug with the DrawTarget approach, though specific to reading + // back from WebGL (where R and B channel end up inverted) to figure out + // first. + if (mData->UpdateFromSurface(aSurface)) { + return; + } + if (CanExposeDrawTarget() && NS_IsMainThread()) { + RefPtr<DrawTarget> dt = BorrowDrawTarget(); + + MOZ_ASSERT(dt); + if (dt) { + dt->CopySurface(aSurface, + gfx::IntRect(gfx::IntPoint(0, 0), aSurface->GetSize()), + gfx::IntPoint(0, 0)); + return; + } + } + NS_WARNING("TextureClient::UpdateFromSurface failed"); +} + +already_AddRefed<TextureClient> TextureClient::CreateSimilar( + LayersBackend aLayersBackend, TextureFlags aFlags, + TextureAllocationFlags aAllocFlags) const { + MOZ_ASSERT(IsValid()); + + MOZ_ASSERT(!mIsLocked); + if (mIsLocked) { + return nullptr; + } + + LockActor(); + TextureData* data = + mData->CreateSimilar(mAllocator, aLayersBackend, aFlags, aAllocFlags); + UnlockActor(); + + if (!data) { + return nullptr; + } + + return MakeAndAddRef<TextureClient>(data, aFlags, mAllocator); +} + +gfx::DrawTarget* TextureClient::BorrowDrawTarget() { + MOZ_ASSERT(IsValid()); + MOZ_ASSERT(mIsLocked); + // TODO- We can't really assert that at the moment because there is code that + // Borrows the DrawTarget, just to get a snapshot, which is legit in term of + // OpenMode but we should have a way to get a SourceSurface directly instead. + // MOZ_ASSERT(mOpenMode & OpenMode::OPEN_WRITE); + + if (!IsValid() || !mIsLocked) { + return nullptr; + } + + if (!mBorrowedDrawTarget) { + mBorrowedDrawTarget = mData->BorrowDrawTarget(); +#ifdef DEBUG + mExpectedDtRefs = mBorrowedDrawTarget ? mBorrowedDrawTarget->refCount() : 0; +#endif + } + + return mBorrowedDrawTarget; +} + +void TextureClient::EndDraw() { + MOZ_ASSERT(mOpenMode & OpenMode::OPEN_READ_WRITE); + + // Because EndDraw is used when we are not unlocking this TextureClient at the + // end of a transaction, we need to Flush and DetachAllSnapshots to ensure any + // dependents are updated. + mBorrowedDrawTarget->Flush(); + mBorrowedDrawTarget->DetachAllSnapshots(); + MOZ_ASSERT(mBorrowedDrawTarget->refCount() <= mExpectedDtRefs); + + mBorrowedDrawTarget = nullptr; + mData->EndDraw(); +} + +already_AddRefed<gfx::SourceSurface> TextureClient::BorrowSnapshot() { + MOZ_ASSERT(mIsLocked); + + RefPtr<gfx::SourceSurface> surface = mData->BorrowSnapshot(); + if (!surface) { + surface = BorrowDrawTarget()->Snapshot(); + } + + return surface.forget(); +} + +bool TextureClient::BorrowMappedData(MappedTextureData& aMap) { + MOZ_ASSERT(IsValid()); + + // TODO - SharedRGBImage just accesses the buffer without properly locking + // the texture. It's bad. + // MOZ_ASSERT(mIsLocked); + // if (!mIsLocked) { + // return nullptr; + //} + + return mData ? mData->BorrowMappedData(aMap) : false; +} + +bool TextureClient::BorrowMappedYCbCrData(MappedYCbCrTextureData& aMap) { + MOZ_ASSERT(IsValid()); + + return mData ? mData->BorrowMappedYCbCrData(aMap) : false; +} + +bool TextureClient::ToSurfaceDescriptor(SurfaceDescriptor& aOutDescriptor) { + MOZ_ASSERT(IsValid()); + + return mData ? mData->Serialize(aOutDescriptor) : false; +} + +// static +PTextureChild* TextureClient::CreateIPDLActor() { + TextureChild* c = new TextureChild(); + c->AddIPDLReference(); + return c; +} + +// static +bool TextureClient::DestroyIPDLActor(PTextureChild* actor) { + static_cast<TextureChild*>(actor)->ReleaseIPDLReference(); + return true; +} + +// static +already_AddRefed<TextureClient> TextureClient::AsTextureClient( + PTextureChild* actor) { + if (!actor) { + return nullptr; + } + + TextureChild* tc = static_cast<TextureChild*>(actor); + + tc->Lock(); + + // Since TextureClient may be destroyed asynchronously with respect to its + // IPDL actor, we must acquire a reference within a lock. The mDestroyed bit + // tells us whether or not the main thread has disconnected the TextureClient + // from its actor. + if (tc->mDestroyed) { + tc->Unlock(); + return nullptr; + } + + RefPtr<TextureClient> texture = tc->mTextureClient; + tc->Unlock(); + + return texture.forget(); +} + +bool TextureClient::IsSharedWithCompositor() const { + return mActor && mActor->IPCOpen(); +} + +void TextureClient::AddFlags(TextureFlags aFlags) { + MOZ_ASSERT( + !IsSharedWithCompositor() || + ((GetFlags() & TextureFlags::RECYCLE) && !IsAddedToCompositableClient())); + mFlags |= aFlags; +} + +void TextureClient::RemoveFlags(TextureFlags aFlags) { + MOZ_ASSERT( + !IsSharedWithCompositor() || + ((GetFlags() & TextureFlags::RECYCLE) && !IsAddedToCompositableClient())); + mFlags &= ~aFlags; +} + +void TextureClient::RecycleTexture(TextureFlags aFlags) { + MOZ_ASSERT(GetFlags() & TextureFlags::RECYCLE); + MOZ_ASSERT(!mIsLocked); + + mAddedToCompositableClient = false; + if (mFlags != aFlags) { + mFlags = aFlags; + } +} + +void TextureClient::SetAddedToCompositableClient() { + if (!mAddedToCompositableClient) { + mAddedToCompositableClient = true; + if (!(GetFlags() & TextureFlags::RECYCLE)) { + return; + } + MOZ_ASSERT(!mIsLocked); + LockActor(); + if (IsValid() && mActor && !mActor->mDestroyed && mActor->IPCOpen()) { + mActor->SendRecycleTexture(mFlags); + } + UnlockActor(); + } +} + +static void CancelTextureClientNotifyNotUsed(uint64_t aTextureId, + LayersIPCChannel* aAllocator) { + if (!aAllocator) { + return; + } + nsCOMPtr<nsISerialEventTarget> thread = aAllocator->GetThread(); + if (!thread) { + return; + } + if (thread->IsOnCurrentThread()) { + aAllocator->CancelWaitForNotifyNotUsed(aTextureId); + } else { + thread->Dispatch(NewRunnableFunction( + "CancelTextureClientNotifyNotUsedRunnable", + CancelTextureClientNotifyNotUsed, aTextureId, aAllocator)); + } +} + +void TextureClient::CancelWaitForNotifyNotUsed() { + if (GetFlags() & TextureFlags::RECYCLE) { + CancelTextureClientNotifyNotUsed(mSerial, GetAllocator()); + return; + } +} + +/* static */ +void TextureClient::TextureClientRecycleCallback(TextureClient* aClient, + void* aClosure) { + MOZ_ASSERT(aClient->GetRecycleAllocator()); + aClient->GetRecycleAllocator()->RecycleTextureClient(aClient); +} + +void TextureClient::SetRecycleAllocator( + ITextureClientRecycleAllocator* aAllocator) { + mRecycleAllocator = aAllocator; + if (aAllocator) { + SetRecycleCallback(TextureClientRecycleCallback, nullptr); + } else { + ClearRecycleCallback(); + } +} + +bool TextureClient::InitIPDLActor(CompositableForwarder* aForwarder) { + MOZ_ASSERT(aForwarder && aForwarder->GetTextureForwarder()->GetThread() == + mAllocator->GetThread()); + + if (mActor && !mActor->IPCOpen()) { + return false; + } + + if (mActor && !mActor->mDestroyed) { + CompositableForwarder* currentFwd = mActor->mCompositableForwarder; + TextureForwarder* currentTexFwd = mActor->mTextureForwarder; + if (currentFwd != aForwarder) { + // It's a bit iffy but right now ShadowLayerForwarder inherits + // TextureForwarder even though it should not. + // ShadowLayerForwarder::GetTextureForwarder actually returns a pointer to + // the CompositorBridgeChild. It's Ok for a texture to move from a + // ShadowLayerForwarder to another, but not form a CompositorBridgeChild + // to another (they use different channels). + if (currentTexFwd && currentTexFwd != aForwarder->GetTextureForwarder()) { + gfxCriticalError() + << "Attempt to move a texture to a different channel CF."; + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return false; + } + if (currentFwd && currentFwd->GetCompositorBackendType() != + aForwarder->GetCompositorBackendType()) { + gfxCriticalError() + << "Attempt to move a texture to different compositor backend."; + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return false; + } + mActor->mCompositableForwarder = aForwarder; + mActor->mUsesImageBridge = + aForwarder->GetTextureForwarder()->UsesImageBridge(); + } + return true; + } + MOZ_ASSERT(!mActor || mActor->mDestroyed, + "Cannot use a texture on several IPC channels."); + + SurfaceDescriptor desc; + if (!ToSurfaceDescriptor(desc)) { + return false; + } + + // Try external image id allocation. + mExternalImageId = + aForwarder->GetTextureForwarder()->GetNextExternalImageId(); + + ReadLockDescriptor readLockDescriptor = null_t(); + if (mReadLock) { + mReadLock->Serialize(readLockDescriptor, GetAllocator()->GetParentPid()); + } + + PTextureChild* actor = aForwarder->GetTextureForwarder()->CreateTexture( + desc, std::move(readLockDescriptor), + aForwarder->GetCompositorBackendType(), GetFlags(), mSerial, + mExternalImageId); + + if (!actor) { + gfxCriticalNote << static_cast<int32_t>(desc.type()) << ", " + << static_cast<int32_t>( + aForwarder->GetCompositorBackendType()) + << ", " << static_cast<uint32_t>(GetFlags()) << ", " + << mSerial; + return false; + } + + mActor = static_cast<TextureChild*>(actor); + mActor->mCompositableForwarder = aForwarder; + mActor->mTextureForwarder = aForwarder->GetTextureForwarder(); + mActor->mTextureClient = this; + + // If the TextureClient is already locked, we have to lock TextureChild's + // mutex since it will be unlocked in TextureClient::Unlock. + if (mIsLocked) { + LockActor(); + } + + return mActor->IPCOpen(); +} + +bool TextureClient::InitIPDLActor(KnowsCompositor* aKnowsCompositor) { + MOZ_ASSERT(aKnowsCompositor && + aKnowsCompositor->GetTextureForwarder()->GetThread() == + mAllocator->GetThread()); + TextureForwarder* fwd = aKnowsCompositor->GetTextureForwarder(); + if (mActor && !mActor->mDestroyed) { + CompositableForwarder* currentFwd = mActor->mCompositableForwarder; + TextureForwarder* currentTexFwd = mActor->mTextureForwarder; + + if (currentFwd) { + gfxCriticalError() + << "Attempt to remove a texture from a CompositableForwarder."; + return false; + } + + if (currentTexFwd && currentTexFwd != fwd) { + gfxCriticalError() + << "Attempt to move a texture to a different channel TF."; + return false; + } + mActor->mTextureForwarder = fwd; + return true; + } + MOZ_ASSERT(!mActor || mActor->mDestroyed, + "Cannot use a texture on several IPC channels."); + + SurfaceDescriptor desc; + if (!ToSurfaceDescriptor(desc)) { + return false; + } + + // Try external image id allocation. + mExternalImageId = + aKnowsCompositor->GetTextureForwarder()->GetNextExternalImageId(); + + ReadLockDescriptor readLockDescriptor = null_t(); + if (mReadLock) { + mReadLock->Serialize(readLockDescriptor, GetAllocator()->GetParentPid()); + } + + PTextureChild* actor = + fwd->CreateTexture(desc, std::move(readLockDescriptor), + aKnowsCompositor->GetCompositorBackendType(), + GetFlags(), mSerial, mExternalImageId); + if (!actor) { + gfxCriticalNote << static_cast<int32_t>(desc.type()) << ", " + << static_cast<int32_t>( + aKnowsCompositor->GetCompositorBackendType()) + << ", " << static_cast<uint32_t>(GetFlags()) << ", " + << mSerial; + return false; + } + + mActor = static_cast<TextureChild*>(actor); + mActor->mTextureForwarder = fwd; + mActor->mTextureClient = this; + + // If the TextureClient is already locked, we have to lock TextureChild's + // mutex since it will be unlocked in TextureClient::Unlock. + if (mIsLocked) { + LockActor(); + } + + return mActor->IPCOpen(); +} + +PTextureChild* TextureClient::GetIPDLActor() { return mActor; } + +// static +already_AddRefed<TextureClient> TextureClient::CreateForDrawing( + KnowsCompositor* aAllocator, gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + BackendSelector aSelector, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags) { + return TextureClient::CreateForDrawing(aAllocator->GetTextureForwarder(), + aFormat, aSize, aAllocator, aSelector, + aTextureFlags, aAllocFlags); +} + +// static +already_AddRefed<TextureClient> TextureClient::CreateForDrawing( + TextureForwarder* aAllocator, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, KnowsCompositor* aKnowsCompositor, + BackendSelector aSelector, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags) { + LayersBackend layersBackend = aKnowsCompositor->GetCompositorBackendType(); + gfx::BackendType moz2DBackend = + BackendTypeForBackendSelector(layersBackend, aSelector); + + // also test the validity of aAllocator + if (!aAllocator || !aAllocator->IPCOpen()) { + return nullptr; + } + + if (!gfx::Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + TextureData* data = + TextureData::Create(aAllocator, aFormat, aSize, aKnowsCompositor, + aSelector, aTextureFlags, aAllocFlags); + + if (data) { + return MakeAndAddRef<TextureClient>(data, aTextureFlags, aAllocator); + } + + // Can't do any better than a buffer texture client. + return TextureClient::CreateForRawBufferAccess(aAllocator, aFormat, aSize, + moz2DBackend, layersBackend, + aTextureFlags, aAllocFlags); +} + +// static +already_AddRefed<TextureClient> TextureClient::CreateFromSurface( + KnowsCompositor* aAllocator, gfx::SourceSurface* aSurface, + BackendSelector aSelector, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags) { + // also test the validity of aAllocator + if (!aAllocator || !aAllocator->GetTextureForwarder()->IPCOpen()) { + return nullptr; + } + + gfx::IntSize size = aSurface->GetSize(); + + if (!gfx::Factory::AllowedSurfaceSize(size)) { + return nullptr; + } + + TextureData* data = nullptr; +#if defined(XP_WIN) + LayersBackend layersBackend = aAllocator->GetCompositorBackendType(); + gfx::BackendType moz2DBackend = + BackendTypeForBackendSelector(layersBackend, aSelector); + + int32_t maxTextureSize = aAllocator->GetMaxTextureSize(); + + if (layersBackend == LayersBackend::LAYERS_WR && + (moz2DBackend == gfx::BackendType::DIRECT2D || + moz2DBackend == gfx::BackendType::DIRECT2D1_1) && + size.width <= maxTextureSize && size.height <= maxTextureSize) { + data = D3D11TextureData::Create(aSurface, aAllocFlags); + } +#endif + + if (data) { + return MakeAndAddRef<TextureClient>(data, aTextureFlags, + aAllocator->GetTextureForwarder()); + } + + // Fall back to using UpdateFromSurface + + TextureAllocationFlags allocFlags = + TextureAllocationFlags(aAllocFlags | ALLOC_UPDATE_FROM_SURFACE); + RefPtr<TextureClient> client = + CreateForDrawing(aAllocator, aSurface->GetFormat(), size, aSelector, + aTextureFlags, allocFlags); + if (!client) { + return nullptr; + } + + TextureClientAutoLock autoLock(client, OpenMode::OPEN_WRITE_ONLY); + if (!autoLock.Succeeded()) { + return nullptr; + } + + client->UpdateFromSurface(aSurface); + return client.forget(); +} + +// static +already_AddRefed<TextureClient> TextureClient::CreateForRawBufferAccess( + KnowsCompositor* aAllocator, gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + gfx::BackendType aMoz2DBackend, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags) { + return CreateForRawBufferAccess( + aAllocator->GetTextureForwarder(), aFormat, aSize, aMoz2DBackend, + aAllocator->GetCompositorBackendType(), aTextureFlags, aAllocFlags); +} + +// static +already_AddRefed<TextureClient> TextureClient::CreateForRawBufferAccess( + LayersIPCChannel* aAllocator, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, gfx::BackendType aMoz2DBackend, + LayersBackend aLayersBackend, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags) { + // also test the validity of aAllocator + if (!aAllocator || !aAllocator->IPCOpen()) { + return nullptr; + } + + if (!gfx::Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + if (aFormat == SurfaceFormat::B8G8R8X8) { + // Skia doesn't support RGBX, so ensure we clear the buffer for the proper + // alpha values. + aAllocFlags = TextureAllocationFlags(aAllocFlags | ALLOC_CLEAR_BUFFER); + } + + // Note that we ignore the backend type if we get here. It should only be D2D + // or Skia, and D2D does not support data surfaces. Therefore it is safe to + // force the buffer to be Skia. + NS_WARNING_ASSERTION(aMoz2DBackend == gfx::BackendType::SKIA || + aMoz2DBackend == gfx::BackendType::DIRECT2D || + aMoz2DBackend == gfx::BackendType::DIRECT2D1_1, + "Unsupported TextureClient backend type"); + + TextureData* texData = BufferTextureData::Create( + aSize, aFormat, gfx::BackendType::SKIA, aLayersBackend, aTextureFlags, + aAllocFlags, aAllocator); + if (!texData) { + return nullptr; + } + + return MakeAndAddRef<TextureClient>(texData, aTextureFlags, aAllocator); +} + +// static +already_AddRefed<TextureClient> TextureClient::CreateForYCbCr( + KnowsCompositor* aAllocator, const gfx::IntRect& aDisplay, + const gfx::IntSize& aYSize, uint32_t aYStride, + const gfx::IntSize& aCbCrSize, uint32_t aCbCrStride, StereoMode aStereoMode, + gfx::ColorDepth aColorDepth, gfx::YUVColorSpace aYUVColorSpace, + gfx::ColorRange aColorRange, gfx::ChromaSubsampling aSubsampling, + TextureFlags aTextureFlags) { + if (!aAllocator || !aAllocator->GetLayersIPCActor()->IPCOpen()) { + return nullptr; + } + + if (!gfx::Factory::AllowedSurfaceSize(aYSize)) { + return nullptr; + } + + TextureData* data = BufferTextureData::CreateForYCbCr( + aAllocator, aDisplay, aYSize, aYStride, aCbCrSize, aCbCrStride, + aStereoMode, aColorDepth, aYUVColorSpace, aColorRange, aSubsampling, + aTextureFlags); + if (!data) { + return nullptr; + } + + return MakeAndAddRef<TextureClient>(data, aTextureFlags, + aAllocator->GetTextureForwarder()); +} + +TextureClient::TextureClient(TextureData* aData, TextureFlags aFlags, + LayersIPCChannel* aAllocator) + : AtomicRefCountedWithFinalize("TextureClient"), + mAllocator(aAllocator), + mActor(nullptr), + mData(aData), + mFlags(aFlags), + mOpenMode(OpenMode::OPEN_NONE) +#ifdef DEBUG + , + mExpectedDtRefs(0) +#endif + , + mIsLocked(false), + mIsReadLocked(false), + mUpdated(false), + mAddedToCompositableClient(false), + mFwdTransactionId(0), + mSerial(++sSerialCounter) +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL + , + mPoolTracker(nullptr) +#endif +{ + mData->FillInfo(mInfo); + mFlags |= mData->GetTextureFlags(); + + if (mFlags & TextureFlags::NON_BLOCKING_READ_LOCK) { + MOZ_ASSERT(!(mFlags & TextureFlags::BLOCKING_READ_LOCK)); + EnableReadLock(); + } else if (mFlags & TextureFlags::BLOCKING_READ_LOCK) { + MOZ_ASSERT(!(mFlags & TextureFlags::NON_BLOCKING_READ_LOCK)); + EnableBlockingReadLock(); + } +} + +bool TextureClient::CopyToTextureClient(TextureClient* aTarget, + const gfx::IntRect* aRect, + const gfx::IntPoint* aPoint) { + MOZ_ASSERT(IsLocked()); + MOZ_ASSERT(aTarget->IsLocked()); + + if (!aTarget->CanExposeDrawTarget() || !CanExposeDrawTarget()) { + return false; + } + + RefPtr<DrawTarget> destinationTarget = aTarget->BorrowDrawTarget(); + if (!destinationTarget) { + gfxWarning() << "TextureClient::CopyToTextureClient (dest) failed in " + "BorrowDrawTarget"; + return false; + } + + RefPtr<DrawTarget> sourceTarget = BorrowDrawTarget(); + if (!sourceTarget) { + gfxWarning() << "TextureClient::CopyToTextureClient (src) failed in " + "BorrowDrawTarget"; + return false; + } + + RefPtr<gfx::SourceSurface> source = sourceTarget->Snapshot(); + destinationTarget->CopySurface( + source, aRect ? *aRect : gfx::IntRect(gfx::IntPoint(0, 0), GetSize()), + aPoint ? *aPoint : gfx::IntPoint(0, 0)); + return true; +} + +already_AddRefed<gfx::DataSourceSurface> TextureClient::GetAsSurface() { + if (!Lock(OpenMode::OPEN_READ)) { + return nullptr; + } + RefPtr<gfx::DataSourceSurface> data; + { // scope so that the DrawTarget is destroyed before Unlock() + RefPtr<gfx::DrawTarget> dt = BorrowDrawTarget(); + if (dt) { + RefPtr<gfx::SourceSurface> surf = dt->Snapshot(); + if (surf) { + data = surf->GetDataSurface(); + } + } + } + Unlock(); + return data.forget(); +} + +void TextureClient::GetSurfaceDescriptorRemoteDecoder( + SurfaceDescriptorRemoteDecoder* const aOutDesc) { + const auto handle = GetSerial(); + + RemoteDecoderVideoSubDescriptor subDesc = null_t(); + MOZ_RELEASE_ASSERT(mData); + mData->GetSubDescriptor(&subDesc); + + *aOutDesc = + SurfaceDescriptorRemoteDecoder(handle, std::move(subDesc), Nothing()); +} + +class MemoryTextureReadLock : public NonBlockingTextureReadLock { + public: + MemoryTextureReadLock(); + + virtual ~MemoryTextureReadLock(); + + bool ReadLock() override; + + int32_t ReadUnlock() override; + + int32_t GetReadCount() override; + + LockType GetType() override { return TYPE_NONBLOCKING_MEMORY; } + + bool IsValid() const override { return true; }; + + bool Serialize(ReadLockDescriptor& aOutput, base::ProcessId aOther) override; + + Atomic<int32_t> mReadCount; +}; + +// The cross-prcess implementation of TextureReadLock. +// +// Since we don't use cross-process reference counting for the ReadLock objects, +// we use the lock's internal counter as a way to know when to deallocate the +// underlying shmem section: when the counter is equal to 1, it means that the +// lock is not "held" (the texture is writable), when the counter is equal to 0 +// it means that we can safely deallocate the shmem section without causing a +// race condition with the other process. +class ShmemTextureReadLock : public NonBlockingTextureReadLock { + public: + struct ShmReadLockInfo { + int32_t readCount; + }; + + explicit ShmemTextureReadLock(LayersIPCChannel* aAllocator); + + virtual ~ShmemTextureReadLock(); + + bool ReadLock() override; + + int32_t ReadUnlock() override; + + int32_t GetReadCount() override; + + bool IsValid() const override { return mAllocSuccess; }; + + LockType GetType() override { return TYPE_NONBLOCKING_SHMEM; } + + bool Serialize(ReadLockDescriptor& aOutput, base::ProcessId aOther) override; + + mozilla::layers::ShmemSection& GetShmemSection() { return mShmemSection; } + + explicit ShmemTextureReadLock( + const mozilla::layers::ShmemSection& aShmemSection) + : mShmemSection(aShmemSection), mAllocSuccess(true) { + MOZ_COUNT_CTOR(ShmemTextureReadLock); + } + + ShmReadLockInfo* GetShmReadLockInfoPtr() { + return reinterpret_cast<ShmReadLockInfo*>( + mShmemSection.shmem().get<char>() + mShmemSection.offset()); + } + + RefPtr<LayersIPCChannel> mClientAllocator; + mozilla::layers::ShmemSection mShmemSection; + bool mAllocSuccess; +}; + +class CrossProcessSemaphoreReadLock : public TextureReadLock { + public: + CrossProcessSemaphoreReadLock() + : mSemaphore(CrossProcessSemaphore::Create("TextureReadLock", 1)), + mShared(false) {} + explicit CrossProcessSemaphoreReadLock(CrossProcessSemaphoreHandle aHandle) + : mSemaphore(CrossProcessSemaphore::Create(std::move(aHandle))), + mShared(false) {} + + bool ReadLock() override { + if (!IsValid()) { + return false; + } + return mSemaphore->Wait(); + } + bool TryReadLock(TimeDuration aTimeout) override { + if (!IsValid()) { + return false; + } + return mSemaphore->Wait(Some(aTimeout)); + } + int32_t ReadUnlock() override { + if (!IsValid()) { + return 1; + } + mSemaphore->Signal(); + return 1; + } + bool IsValid() const override { return !!mSemaphore; } + + bool Serialize(ReadLockDescriptor& aOutput, base::ProcessId aOther) override; + + LockType GetType() override { return TYPE_CROSS_PROCESS_SEMAPHORE; } + + UniquePtr<CrossProcessSemaphore> mSemaphore; + bool mShared; +}; + +// static +already_AddRefed<TextureReadLock> TextureReadLock::Deserialize( + ReadLockDescriptor&& aDescriptor, ISurfaceAllocator* aAllocator) { + switch (aDescriptor.type()) { + case ReadLockDescriptor::TShmemSection: { + const ShmemSection& section = aDescriptor.get_ShmemSection(); + MOZ_RELEASE_ASSERT(section.shmem().IsReadable()); + return MakeAndAddRef<ShmemTextureReadLock>(section); + } + case ReadLockDescriptor::Tuintptr_t: { + if (!aAllocator->IsSameProcess()) { + // Trying to use a memory based lock instead of a shmem based one in + // the cross-process case is a bad security violation. + NS_ERROR( + "A client process may be trying to peek at the host's address " + "space!"); + return nullptr; + } + RefPtr<TextureReadLock> lock = + reinterpret_cast<MemoryTextureReadLock*>(aDescriptor.get_uintptr_t()); + + MOZ_ASSERT(lock); + if (lock) { + // The corresponding AddRef is in MemoryTextureReadLock::Serialize + lock.get()->Release(); + } + + return lock.forget(); + } + case ReadLockDescriptor::TCrossProcessSemaphoreDescriptor: { + return MakeAndAddRef<CrossProcessSemaphoreReadLock>( + std::move(aDescriptor.get_CrossProcessSemaphoreDescriptor().sem())); + } + case ReadLockDescriptor::Tnull_t: { + return nullptr; + } + default: { + // Invalid descriptor. + MOZ_DIAGNOSTIC_ASSERT(false); + } + } + return nullptr; +} +// static +already_AddRefed<TextureReadLock> NonBlockingTextureReadLock::Create( + LayersIPCChannel* aAllocator) { + if (aAllocator->IsSameProcess()) { + // If our compositor is in the same process, we can save some cycles by not + // using shared memory. + return MakeAndAddRef<MemoryTextureReadLock>(); + } + + return MakeAndAddRef<ShmemTextureReadLock>(aAllocator); +} + +MemoryTextureReadLock::MemoryTextureReadLock() : mReadCount(1) { + MOZ_COUNT_CTOR(MemoryTextureReadLock); +} + +MemoryTextureReadLock::~MemoryTextureReadLock() { + // One read count that is added in constructor. + MOZ_ASSERT(mReadCount == 1); + MOZ_COUNT_DTOR(MemoryTextureReadLock); +} + +bool MemoryTextureReadLock::Serialize(ReadLockDescriptor& aOutput, + base::ProcessId aOther) { + // AddRef here and Release when receiving on the host side to make sure the + // reference count doesn't go to zero before the host receives the message. + // see TextureReadLock::Deserialize + this->AddRef(); + aOutput = ReadLockDescriptor(uintptr_t(this)); + return true; +} + +bool MemoryTextureReadLock::ReadLock() { + NS_ASSERT_OWNINGTHREAD(MemoryTextureReadLock); + + ++mReadCount; + return true; +} + +int32_t MemoryTextureReadLock::ReadUnlock() { + int32_t readCount = --mReadCount; + MOZ_ASSERT(readCount >= 0); + + return readCount; +} + +int32_t MemoryTextureReadLock::GetReadCount() { + NS_ASSERT_OWNINGTHREAD(MemoryTextureReadLock); + return mReadCount; +} + +ShmemTextureReadLock::ShmemTextureReadLock(LayersIPCChannel* aAllocator) + : mClientAllocator(aAllocator), mAllocSuccess(false) { + MOZ_COUNT_CTOR(ShmemTextureReadLock); + MOZ_ASSERT(mClientAllocator); + MOZ_ASSERT(mClientAllocator->GetTileLockAllocator()); +#define MOZ_ALIGN_WORD(x) (((x) + 3) & ~3) + if (mClientAllocator->GetTileLockAllocator()->AllocShmemSection( + MOZ_ALIGN_WORD(sizeof(ShmReadLockInfo)), &mShmemSection)) { + ShmReadLockInfo* info = GetShmReadLockInfoPtr(); + info->readCount = 1; + mAllocSuccess = true; + } +} + +ShmemTextureReadLock::~ShmemTextureReadLock() { + if (mClientAllocator) { + // Release one read count that is added in constructor. + // The count is kept for calling GetReadCount() by TextureClientPool. + ReadUnlock(); + } + MOZ_COUNT_DTOR(ShmemTextureReadLock); +} + +bool ShmemTextureReadLock::Serialize(ReadLockDescriptor& aOutput, + base::ProcessId aOther) { + aOutput = ReadLockDescriptor(GetShmemSection()); + return true; +} + +bool ShmemTextureReadLock::ReadLock() { + NS_ASSERT_OWNINGTHREAD(ShmemTextureReadLock); + if (!mAllocSuccess) { + return false; + } + ShmReadLockInfo* info = GetShmReadLockInfoPtr(); + PR_ATOMIC_INCREMENT(&info->readCount); + return true; +} + +int32_t ShmemTextureReadLock::ReadUnlock() { + if (!mAllocSuccess) { + return 0; + } + ShmReadLockInfo* info = GetShmReadLockInfoPtr(); + int32_t readCount = PR_ATOMIC_DECREMENT(&info->readCount); + MOZ_ASSERT(readCount >= 0); + if (readCount <= 0) { + if (mClientAllocator && mClientAllocator->GetTileLockAllocator()) { + mClientAllocator->GetTileLockAllocator()->DeallocShmemSection( + mShmemSection); + } else { + // we are on the compositor process, or IPC is down. + FixedSizeSmallShmemSectionAllocator::FreeShmemSection(mShmemSection); + } + } + return readCount; +} + +int32_t ShmemTextureReadLock::GetReadCount() { + NS_ASSERT_OWNINGTHREAD(ShmemTextureReadLock); + if (!mAllocSuccess) { + return 0; + } + ShmReadLockInfo* info = GetShmReadLockInfoPtr(); + return info->readCount; +} + +bool CrossProcessSemaphoreReadLock::Serialize(ReadLockDescriptor& aOutput, + base::ProcessId aOther) { + if (!mShared && IsValid()) { + aOutput = ReadLockDescriptor( + CrossProcessSemaphoreDescriptor(mSemaphore->CloneHandle())); + mSemaphore->CloseHandle(); + mShared = true; + return true; + } else { + return mShared; + } +} + +void TextureClient::EnableBlockingReadLock() { + if (!mReadLock) { + mReadLock = new CrossProcessSemaphoreReadLock(); + } +} + +bool UpdateYCbCrTextureClient(TextureClient* aTexture, + const PlanarYCbCrData& aData) { + MOZ_ASSERT(aTexture); + MOZ_ASSERT(aTexture->IsLocked()); + MOZ_ASSERT(aTexture->GetFormat() == gfx::SurfaceFormat::YUV, + "This textureClient can only use YCbCr data"); + MOZ_ASSERT(!aTexture->IsImmutable()); + MOZ_ASSERT(aTexture->IsValid()); + MOZ_ASSERT(aData.mCbSkip == aData.mCrSkip); + + MappedYCbCrTextureData mapped; + if (!aTexture->BorrowMappedYCbCrData(mapped)) { + NS_WARNING("Failed to extract YCbCr info!"); + return false; + } + + uint32_t bytesPerPixel = + BytesPerPixel(SurfaceFormatForColorDepth(aData.mColorDepth)); + MappedYCbCrTextureData srcData; + srcData.y.data = aData.mYChannel; + srcData.y.size = aData.YDataSize(); + srcData.y.stride = aData.mYStride; + srcData.y.skip = aData.mYSkip; + srcData.y.bytesPerPixel = bytesPerPixel; + srcData.cb.data = aData.mCbChannel; + srcData.cb.size = aData.CbCrDataSize(); + srcData.cb.stride = aData.mCbCrStride; + srcData.cb.skip = aData.mCbSkip; + srcData.cb.bytesPerPixel = bytesPerPixel; + srcData.cr.data = aData.mCrChannel; + srcData.cr.size = aData.CbCrDataSize(); + srcData.cr.stride = aData.mCbCrStride; + srcData.cr.skip = aData.mCrSkip; + srcData.cr.bytesPerPixel = bytesPerPixel; + srcData.metadata = nullptr; + + if (!srcData.CopyInto(mapped)) { + NS_WARNING("Failed to copy image data!"); + return false; + } + + if (TextureRequiresLocking(aTexture->GetFlags())) { + // We don't have support for proper locking yet, so we'll + // have to be immutable instead. + aTexture->MarkImmutable(); + } + return true; +} + +already_AddRefed<TextureClient> TextureClient::CreateWithData( + TextureData* aData, TextureFlags aFlags, LayersIPCChannel* aAllocator) { + if (!aData) { + return nullptr; + } + return MakeAndAddRef<TextureClient>(aData, aFlags, aAllocator); +} + +template <class PixelDataType> +static void copyData(PixelDataType* aDst, + const MappedYCbCrChannelData& aChannelDst, + PixelDataType* aSrc, + const MappedYCbCrChannelData& aChannelSrc) { + uint8_t* srcByte = reinterpret_cast<uint8_t*>(aSrc); + const int32_t srcSkip = aChannelSrc.skip + 1; + uint8_t* dstByte = reinterpret_cast<uint8_t*>(aDst); + const int32_t dstSkip = aChannelDst.skip + 1; + for (int32_t i = 0; i < aChannelSrc.size.height; ++i) { + for (int32_t j = 0; j < aChannelSrc.size.width; ++j) { + *aDst = *aSrc; + aSrc += srcSkip; + aDst += dstSkip; + } + srcByte += aChannelSrc.stride; + aSrc = reinterpret_cast<PixelDataType*>(srcByte); + dstByte += aChannelDst.stride; + aDst = reinterpret_cast<PixelDataType*>(dstByte); + } +} + +bool MappedYCbCrChannelData::CopyInto(MappedYCbCrChannelData& aDst) { + if (!data || !aDst.data || size != aDst.size) { + return false; + } + + if (stride == aDst.stride && skip == aDst.skip) { + // fast path! + // We assume that the padding in the destination is there for alignment + // purposes and doesn't contain useful data. + memcpy(aDst.data, data, stride * size.height); + return true; + } + + if (aDst.skip == 0 && skip == 0) { + // fast-ish path + for (int32_t i = 0; i < size.height; ++i) { + memcpy(aDst.data + i * aDst.stride, data + i * stride, + size.width * bytesPerPixel); + } + return true; + } + + MOZ_ASSERT(bytesPerPixel == 1 || bytesPerPixel == 2); + // slow path + if (bytesPerPixel == 1) { + copyData(aDst.data, aDst, data, *this); + } else if (bytesPerPixel == 2) { + copyData(reinterpret_cast<uint16_t*>(aDst.data), aDst, + reinterpret_cast<uint16_t*>(data), *this); + } + return true; +} + +} // namespace mozilla::layers diff --git a/gfx/layers/client/TextureClient.h b/gfx/layers/client/TextureClient.h new file mode 100644 index 0000000000..fa0df57a59 --- /dev/null +++ b/gfx/layers/client/TextureClient.h @@ -0,0 +1,812 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_TEXTURECLIENT_H +#define MOZILLA_GFX_TEXTURECLIENT_H + +#include <stddef.h> // for size_t +#include <stdint.h> // for uint32_t, uint8_t, uint64_t + +#include "GLTextureImage.h" // for TextureImage +#include "GfxTexturesReporter.h" +#include "ImageTypes.h" // for StereoMode +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" // for override +#include "mozilla/DebugOnly.h" +#include "mozilla/RefPtr.h" // for RefPtr, RefCounted +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/CriticalSection.h" +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/ipc/Shmem.h" // for Shmem +#include "mozilla/layers/AtomicRefCountedWithFinalize.h" +#include "mozilla/layers/CompositorTypes.h" // for TextureFlags, etc +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/SyncObject.h" +#include "mozilla/mozalloc.h" // for operator delete +#include "mozilla/webrender/WebRenderTypes.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsISupportsImpl.h" // for TextureImage::AddRef, etc +#include "nsThreadUtils.h" +#include "pratom.h" + +class gfxImageSurface; +struct ID3D11Device; + +namespace mozilla { + +// When defined, we track which pool the tile came from and test for +// any inconsistencies. This can be defined in release build as well. +#ifdef DEBUG +# define GFX_DEBUG_TRACK_CLIENTS_IN_POOL 1 +#endif + +namespace layers { + +class AndroidHardwareBufferTextureData; +class BufferTextureData; +class CompositableForwarder; +class KnowsCompositor; +class LayersIPCChannel; +class CompositableClient; +struct PlanarYCbCrData; +class Image; +class PTextureChild; +class TextureChild; +class TextureData; +class GPUVideoTextureData; +class TextureClient; +class ITextureClientRecycleAllocator; +class SharedSurfaceTextureData; +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL +class TextureClientPool; +#endif +class TextureForwarder; + +/** + * TextureClient is the abstraction that allows us to share data between the + * content and the compositor side. + */ + +enum TextureAllocationFlags { + ALLOC_DEFAULT = 0, + ALLOC_CLEAR_BUFFER = + 1 << 1, // Clear the buffer to whatever is best for the draw target + ALLOC_CLEAR_BUFFER_WHITE = 1 << 2, // explicit all white + ALLOC_CLEAR_BUFFER_BLACK = 1 << 3, // explicit all black + ALLOC_DISALLOW_BUFFERTEXTURECLIENT = 1 << 4, + + // Allocate the texture for out-of-band content updates. This is mostly for + // TextureClientD3D11, which may otherwise choose D3D10 or non-KeyedMutex + // surfaces when used on the main thread. + ALLOC_FOR_OUT_OF_BAND_CONTENT = 1 << 5, + + // Disable any cross-device synchronization. This is also for + // TextureClientD3D11, and creates a texture without KeyedMutex. + ALLOC_MANUAL_SYNCHRONIZATION = 1 << 6, + + // The texture is going to be updated using UpdateFromSurface and needs to + // support that call. + ALLOC_UPDATE_FROM_SURFACE = 1 << 7, + + // Do not use an accelerated texture type. + ALLOC_DO_NOT_ACCELERATE = 1 << 8, +}; + +enum class BackendSelector { Content, Canvas }; + +/// Temporary object providing direct access to a Texture's memory. +/// +/// see TextureClient::CanExposeMappedData() and +/// TextureClient::BorrowMappedData(). +struct MappedTextureData { + uint8_t* data; + gfx::IntSize size; + int32_t stride; + gfx::SurfaceFormat format; +}; + +struct MappedYCbCrChannelData { + uint8_t* data; + gfx::IntSize size; + int32_t stride; + int32_t skip; + uint32_t bytesPerPixel; + + bool CopyInto(MappedYCbCrChannelData& aDst); +}; + +struct MappedYCbCrTextureData { + MappedYCbCrChannelData y; + MappedYCbCrChannelData cb; + MappedYCbCrChannelData cr; + // Sad but because of how SharedPlanarYCbCrData is used we have to expose this + // for now. + uint8_t* metadata; + StereoMode stereoMode; + + bool CopyInto(MappedYCbCrTextureData& aDst) { + return y.CopyInto(aDst.y) && cb.CopyInto(aDst.cb) && cr.CopyInto(aDst.cr); + } +}; + +class ReadLockDescriptor; +class NonBlockingTextureReadLock; + +// A class to help implement copy-on-write semantics for shared textures. +// +// A TextureClient/Host pair can opt into using a ReadLock by calling +// TextureClient::EnableReadLock. This will equip the TextureClient with a +// ReadLock object that will be automatically ReadLock()'ed by the texture +// itself when it is written into (see TextureClient::Unlock). A +// TextureReadLock's counter starts at 1 and is expected to be equal to 1 when +// the lock is destroyed. See ShmemTextureReadLock for explanations about why we +// use 1 instead of 0 as the initial state. TextureReadLock is mostly internally +// managed by the TextureClient/Host pair, and the compositable only has to +// forward it during updates. If an update message contains a null_t lock, it +// means that the texture was not written into on the content side, and there is +// no synchronization required on the compositor side (or it means that the +// texture pair did not opt into using ReadLocks). On the compositor side, the +// TextureHost can receive a ReadLock during a transaction, and will both +// ReadUnlock() it and drop it as soon as the shared data is available again for +// writing (the texture upload is done, or the compositor not reading the +// texture anymore). The lock is dropped to make sure it is ReadUnlock()'ed only +// once. +class TextureReadLock { + protected: + virtual ~TextureReadLock() = default; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TextureReadLock) + + virtual bool ReadLock() = 0; + virtual bool TryReadLock(TimeDuration aTimeout) { return ReadLock(); } + virtual int32_t ReadUnlock() = 0; + virtual bool IsValid() const = 0; + + static already_AddRefed<TextureReadLock> Deserialize( + ReadLockDescriptor&& aDescriptor, ISurfaceAllocator* aAllocator); + + virtual bool Serialize(ReadLockDescriptor& aOutput, + base::ProcessId aOther) = 0; + + enum LockType { + TYPE_NONBLOCKING_MEMORY, + TYPE_NONBLOCKING_SHMEM, + TYPE_CROSS_PROCESS_SEMAPHORE + }; + virtual LockType GetType() = 0; + + virtual NonBlockingTextureReadLock* AsNonBlockingLock() { return nullptr; } + + protected: + NS_DECL_OWNINGTHREAD +}; + +class NonBlockingTextureReadLock : public TextureReadLock { + public: + virtual int32_t GetReadCount() = 0; + + static already_AddRefed<TextureReadLock> Create(LayersIPCChannel* aAllocator); + + NonBlockingTextureReadLock* AsNonBlockingLock() override { return this; } +}; + +#ifdef XP_WIN +class D3D11TextureData; +class DXGIYCbCrTextureData; +#endif + +class TextureData { + public: + struct Info { + gfx::IntSize size; + gfx::SurfaceFormat format; + bool hasSynchronization; + bool supportsMoz2D; + bool canExposeMappedData; + bool canConcurrentlyReadLock; + + Info() + : format(gfx::SurfaceFormat::UNKNOWN), + hasSynchronization(false), + supportsMoz2D(false), + canExposeMappedData(false), + canConcurrentlyReadLock(true) {} + }; + + static TextureData* Create(TextureForwarder* aAllocator, + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + KnowsCompositor* aKnowsCompositor, + BackendSelector aSelector, + TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags); + + static bool IsRemote(KnowsCompositor* aKnowsCompositor, + BackendSelector aSelector); + + MOZ_COUNTED_DTOR_VIRTUAL(TextureData) + + virtual void FillInfo(TextureData::Info& aInfo) const = 0; + + virtual bool Lock(OpenMode aMode) = 0; + + virtual void Unlock() = 0; + + virtual already_AddRefed<gfx::DrawTarget> BorrowDrawTarget() { + return nullptr; + } + + /** + * When the TextureData is not being Unlocked, this can be used to inform a + * TextureData that drawing has finished until the next BorrowDrawTarget. + */ + virtual void EndDraw() {} + + virtual already_AddRefed<gfx::SourceSurface> BorrowSnapshot() { + return nullptr; + } + + virtual bool BorrowMappedData(MappedTextureData&) { return false; } + + virtual bool BorrowMappedYCbCrData(MappedYCbCrTextureData&) { return false; } + + virtual void Deallocate(LayersIPCChannel* aAllocator) = 0; + + /// Depending on the texture's flags either Deallocate or Forget is called. + virtual void Forget(LayersIPCChannel* aAllocator) {} + + virtual bool Serialize(SurfaceDescriptor& aDescriptor) = 0; + virtual void GetSubDescriptor(RemoteDecoderVideoSubDescriptor* aOutDesc) {} + + virtual void OnForwardedToHost() {} + + virtual TextureData* CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags = TextureFlags::DEFAULT, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT) const { + return nullptr; + } + + virtual bool UpdateFromSurface(gfx::SourceSurface* aSurface) { + return false; + }; + + virtual void SyncWithObject(RefPtr<SyncObjectClient> aSyncObject){}; + + virtual TextureFlags GetTextureFlags() const { + return TextureFlags::NO_FLAGS; + } + +#ifdef XP_WIN + virtual D3D11TextureData* AsD3D11TextureData() { return nullptr; } + virtual DXGIYCbCrTextureData* AsDXGIYCbCrTextureData() { return nullptr; } +#endif + + virtual BufferTextureData* AsBufferTextureData() { return nullptr; } + + virtual GPUVideoTextureData* AsGPUVideoTextureData() { return nullptr; } + + virtual AndroidHardwareBufferTextureData* + AsAndroidHardwareBufferTextureData() { + return nullptr; + } + + // It is used by AndroidHardwareBufferTextureData and + // SharedSurfaceTextureData. Returns buffer id when it owns + // AndroidHardwareBuffer. It is used only on android. + virtual Maybe<uint64_t> GetBufferId() const { return Nothing(); } + + // The acquire fence is a fence that is used for waiting until rendering to + // its AHardwareBuffer is completed. + // It is used only on android. + virtual mozilla::ipc::FileDescriptor GetAcquireFence() { + return mozilla::ipc::FileDescriptor(); + } + + protected: + MOZ_COUNTED_DEFAULT_CTOR(TextureData) +}; + +/** + * TextureClient is a thin abstraction over texture data that need to be shared + * between the content process and the compositor process. It is the + * content-side half of a TextureClient/TextureHost pair. A corresponding + * TextureHost lives on the compositor-side. + * + * TextureClient's primary purpose is to present texture data in a way that is + * understood by the IPC system. There are two ways to use it: + * - Use it to serialize image data that is not IPC-friendly (most likely + * involving a copy into shared memory) + * - preallocate it and paint directly into it, which avoids copy but requires + * the painting code to be aware of TextureClient (or at least the underlying + * shared memory). + * + * There is always one and only one TextureClient per TextureHost, and the + * TextureClient/Host pair only owns one buffer of image data through its + * lifetime. This means that the lifetime of the underlying shared data + * matches the lifetime of the TextureClient/Host pair. It also means + * TextureClient/Host do not implement double buffering, which is the + * responsibility of the compositable (which would use pairs of Textures). + * In order to send several different buffers to the compositor side, use + * several TextureClients. + */ +class TextureClient : public AtomicRefCountedWithFinalize<TextureClient> { + public: + TextureClient(TextureData* aData, TextureFlags aFlags, + LayersIPCChannel* aAllocator); + + virtual ~TextureClient(); + + static already_AddRefed<TextureClient> CreateWithData( + TextureData* aData, TextureFlags aFlags, LayersIPCChannel* aAllocator); + + // Creates and allocates a TextureClient usable with Moz2D. + static already_AddRefed<TextureClient> CreateForDrawing( + KnowsCompositor* aAllocator, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, BackendSelector aSelector, TextureFlags aTextureFlags, + TextureAllocationFlags flags = ALLOC_DEFAULT); + + static already_AddRefed<TextureClient> CreateFromSurface( + KnowsCompositor* aAllocator, gfx::SourceSurface* aSurface, + BackendSelector aSelector, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags); + + // Creates and allocates a TextureClient supporting the YCbCr format. + static already_AddRefed<TextureClient> CreateForYCbCr( + KnowsCompositor* aAllocator, const gfx::IntRect& aDisplay, + const gfx::IntSize& aYSize, uint32_t aYStride, + const gfx::IntSize& aCbCrSize, uint32_t aCbCrStride, + StereoMode aStereoMode, gfx::ColorDepth aColorDepth, + gfx::YUVColorSpace aYUVColorSpace, gfx::ColorRange aColorRange, + gfx::ChromaSubsampling aSubsampling, TextureFlags aTextureFlags); + + // Creates and allocates a TextureClient (can be accessed through raw + // pointers). + static already_AddRefed<TextureClient> CreateForRawBufferAccess( + KnowsCompositor* aAllocator, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, gfx::BackendType aMoz2dBackend, + TextureFlags aTextureFlags, TextureAllocationFlags flags = ALLOC_DEFAULT); + + // Creates and allocates a TextureClient of the same type. + already_AddRefed<TextureClient> CreateSimilar( + LayersBackend aLayersBackend = LayersBackend::LAYERS_NONE, + TextureFlags aFlags = TextureFlags::DEFAULT, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT) const; + + /** + * Locks the shared data, allowing the caller to get access to it. + * + * Please always lock/unlock when accessing the shared data. + * If Lock() returns false, you should not attempt to access the shared data. + */ + bool Lock(OpenMode aMode); + + void Unlock(); + + bool IsLocked() const { return mIsLocked; } + + gfx::IntSize GetSize() const { return mInfo.size; } + + gfx::SurfaceFormat GetFormat() const { return mInfo.format; } + + /** + * Returns true if this texture has a synchronization mechanism (mutex, fence, + * etc.). Textures that do not implement synchronization should be immutable + * or should use immediate uploads (see TextureFlags in CompositorTypes.h) + * Even if a texture does not implement synchronization, Lock and Unlock need + * to be used appropriately since the latter are also there to map/numap data. + */ + bool HasSynchronization() const { return mInfo.hasSynchronization; } + + bool CanExposeDrawTarget() const { return mInfo.supportsMoz2D; } + + bool CanExposeMappedData() const { return mInfo.canExposeMappedData; } + + /** + * Returns a DrawTarget to draw into the TextureClient. + * This function should never be called when not on the main thread! + * + * This must never be called on a TextureClient that is not sucessfully + * locked. When called several times within one Lock/Unlock pair, this method + * should return the same DrawTarget. The DrawTarget is automatically flushed + * by the TextureClient when the latter is unlocked, and the DrawTarget that + * will be returned within the next lock/unlock pair may or may not be the + * same object. Do not keep references to the DrawTarget outside of the + * lock/unlock pair. + * + * This is typically used as follows: + * + * if (!texture->Lock(OpenMode::OPEN_READ_WRITE)) { + * return false; + * } + * { + * // Restrict this code's scope to ensure all references to dt are gone + * // when Unlock is called. + * DrawTarget* dt = texture->BorrowDrawTarget(); + * // use the draw target ... + * } + * texture->Unlock(); + * + */ + gfx::DrawTarget* BorrowDrawTarget(); + + /** + * When the TextureClient is not being Unlocked, this can be used to inform it + * that drawing has finished until the next BorrowDrawTarget. + */ + void EndDraw(); + + already_AddRefed<gfx::SourceSurface> BorrowSnapshot(); + + /** + * Similar to BorrowDrawTarget but provides direct access to the texture's + * bits instead of a DrawTarget. + */ + bool BorrowMappedData(MappedTextureData&); + bool BorrowMappedYCbCrData(MappedYCbCrTextureData&); + + /** + * This function can be used to update the contents of the TextureClient + * off the main thread. + */ + void UpdateFromSurface(gfx::SourceSurface* aSurface); + + /** + * This method is strictly for debugging. It causes locking and + * needless copies. + */ + already_AddRefed<gfx::DataSourceSurface> GetAsSurface(); + + /** + * Copies a rectangle from this texture client to a position in aTarget. + * It is assumed that the necessary locks are in place; so this should at + * least have a read lock and aTarget should at least have a write lock. + */ + bool CopyToTextureClient(TextureClient* aTarget, const gfx::IntRect* aRect, + const gfx::IntPoint* aPoint); + + /** + * Allocate and deallocate a TextureChild actor. + * + * TextureChild is an implementation detail of TextureClient that is not + * exposed to the rest of the code base. CreateIPDLActor and DestroyIPDLActor + * are for use with the managing IPDL protocols only (so that they can + * implement AllocPextureChild and DeallocPTextureChild). + */ + static PTextureChild* CreateIPDLActor(); + static bool DestroyIPDLActor(PTextureChild* actor); + + /** + * Get the TextureClient corresponding to the actor passed in parameter. + */ + static already_AddRefed<TextureClient> AsTextureClient(PTextureChild* actor); + + /** + * TextureFlags contain important information about various aspects + * of the texture, like how its liferime is managed, and how it + * should be displayed. + * See TextureFlags in CompositorTypes.h. + */ + TextureFlags GetFlags() const { return mFlags; } + + bool HasFlags(TextureFlags aFlags) const { + return (mFlags & aFlags) == aFlags; + } + + void AddFlags(TextureFlags aFlags); + + void RemoveFlags(TextureFlags aFlags); + + // Must not be called when TextureClient is in use by CompositableClient. + void RecycleTexture(TextureFlags aFlags); + + /** + * After being shared with the compositor side, an immutable texture is never + * modified, it can only be read. It is safe to not Lock/Unlock immutable + * textures. + */ + bool IsImmutable() const { return !!(mFlags & TextureFlags::IMMUTABLE); } + + void MarkImmutable() { AddFlags(TextureFlags::IMMUTABLE); } + + bool IsSharedWithCompositor() const; + + /** + * If this method returns false users of TextureClient are not allowed + * to access the shared data. + */ + bool IsValid() const { return !!mData; } + + /** + * Called when TextureClient is added to CompositableClient. + */ + void SetAddedToCompositableClient(); + + /** + * If this method retuns false, TextureClient is already added to + * CompositableClient, since its creation or recycling. + */ + bool IsAddedToCompositableClient() const { + return mAddedToCompositableClient; + } + + /** + * Create and init the TextureChild/Parent IPDL actor pair + * with a CompositableForwarder. + * + * Should be called only once per TextureClient. + * The TextureClient must not be locked when calling this method. + */ + bool InitIPDLActor(CompositableForwarder* aForwarder); + + /** + * Create and init the TextureChild/Parent IPDL actor pair + * with a TextureForwarder. + * + * Should be called only once per TextureClient. + * The TextureClient must not be locked when calling this method. + */ + bool InitIPDLActor(KnowsCompositor* aKnowsCompositor); + + /** + * Return a pointer to the IPDLActor. + * + * This is to be used with IPDL messages only. Do not store the returned + * pointer. + */ + PTextureChild* GetIPDLActor(); + + /** + * Triggers the destruction of the shared data and the corresponding + * TextureHost. + * + * If the texture flags contain TextureFlags::DEALLOCATE_CLIENT, the + * destruction will be synchronously coordinated with the compositor side, + * otherwise it will be done asynchronously. + */ + void Destroy(); + + /** + * Track how much of this texture is wasted. + * For example we might allocate a 256x256 tile but only use 10x10. + */ + void SetWaste(int aWasteArea) { + mWasteTracker.Update(aWasteArea, BytesPerPixel(GetFormat())); + } + + void SyncWithObject(RefPtr<SyncObjectClient> aSyncObject) { + mData->SyncWithObject(aSyncObject); + } + + LayersIPCChannel* GetAllocator() { return mAllocator; } + + ITextureClientRecycleAllocator* GetRecycleAllocator() { + return mRecycleAllocator; + } + void SetRecycleAllocator(ITextureClientRecycleAllocator* aAllocator); + + /// If you add new code that uses this method, you are probably doing + /// something wrong. + TextureData* GetInternalData() { return mData; } + const TextureData* GetInternalData() const { return mData; } + + uint64_t GetSerial() const { return mSerial; } + void GetSurfaceDescriptorRemoteDecoder( + SurfaceDescriptorRemoteDecoder* aOutDesc); + + void CancelWaitForNotifyNotUsed(); + + /** + * Set last transaction id of CompositableForwarder. + * + * Called when TextureClient has TextureFlags::RECYCLE flag. + * When CompositableForwarder forwards the TextureClient with + * TextureFlags::RECYCLE, it holds TextureClient's ref until host side + * releases it. The host side sends TextureClient release message. + * The id is used to check if the message is for the last TextureClient + * forwarding. + */ + void SetLastFwdTransactionId(uint64_t aTransactionId) { + MOZ_ASSERT(mFwdTransactionId <= aTransactionId); + mFwdTransactionId = aTransactionId; + } + + uint64_t GetLastFwdTransactionId() { return mFwdTransactionId; } + + TextureReadLock* GetReadLock() { return mReadLock; } + + bool IsReadLocked() const; + + bool TryReadLock(); + void ReadUnlock(); + + void SetUpdated() { mUpdated = true; } + + bool OnForwardedToHost(); + + // Mark that the TextureClient will be used by the paint thread, and should + // not free its underlying texture data. This must only be called from the + // main thread. + void AddPaintThreadRef(); + + // Mark that the TextureClient is no longer in use by the PaintThread. This + // must only be called from the PaintThread. + void DropPaintThreadRef(); + + wr::MaybeExternalImageId GetExternalImageKey() { return mExternalImageId; } + + private: + static void TextureClientRecycleCallback(TextureClient* aClient, + void* aClosure); + + // Internal helpers for creating texture clients using the actual forwarder + // instead of KnowsCompositor. TextureClientPool uses these to let it cache + // texture clients per-process instead of per ShadowLayerForwarder, but + // everyone else should use the public functions instead. + friend class TextureClientPool; + static already_AddRefed<TextureClient> CreateForDrawing( + TextureForwarder* aAllocator, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, KnowsCompositor* aKnowsCompositor, + BackendSelector aSelector, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT); + + static already_AddRefed<TextureClient> CreateForRawBufferAccess( + LayersIPCChannel* aAllocator, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, gfx::BackendType aMoz2dBackend, + LayersBackend aLayersBackend, TextureFlags aTextureFlags, + TextureAllocationFlags flags = ALLOC_DEFAULT); + + void EnableReadLock(); + void EnableBlockingReadLock(); + + /** + * Called once, during the destruction of the Texture, on the thread in which + * texture's reference count reaches 0 (could be any thread). + * + * Here goes the shut-down code that uses virtual methods. + * Must only be called by Release(). + */ + void Finalize() {} + + friend class AtomicRefCountedWithFinalize<TextureClient>; + + protected: + /** + * Should only be called *once* per texture, in TextureClient::InitIPDLActor. + * Some texture implementations rely on the fact that the descriptor will be + * deserialized. + * Calling ToSurfaceDescriptor again after it has already returned true, + * or never constructing a TextureHost with aDescriptor may result in a memory + * leak (see TextureClientD3D9 for example). + */ + bool ToSurfaceDescriptor(SurfaceDescriptor& aDescriptor); + + void LockActor() const; + void UnlockActor() const; + + TextureData::Info mInfo; + + RefPtr<LayersIPCChannel> mAllocator; + RefPtr<TextureChild> mActor; + RefPtr<ITextureClientRecycleAllocator> mRecycleAllocator; + RefPtr<TextureReadLock> mReadLock; + + TextureData* mData; + RefPtr<gfx::DrawTarget> mBorrowedDrawTarget; + + TextureFlags mFlags; + + gl::GfxTextureWasteTracker mWasteTracker; + + OpenMode mOpenMode; +#ifdef DEBUG + uint32_t mExpectedDtRefs; +#endif + bool mIsLocked; + bool mIsReadLocked; + // This member tracks that the texture was written into until the update + // is sent to the compositor. We need this remember to lock mReadLock on + // behalf of the compositor just before sending the notification. + bool mUpdated; + + // Used when TextureClient is recycled with TextureFlags::RECYCLE flag. + bool mAddedToCompositableClient; + + uint64_t mFwdTransactionId; + + // Serial id of TextureClient. It is unique in current process. + const uint64_t mSerial; + + // When non-zero, texture data must not be freed. + mozilla::Atomic<uintptr_t> mPaintThreadRefs; + + // External image id. It is unique if it is allocated. + // The id is allocated in TextureClient::InitIPDLActor(). + // Its allocation is supported by + // CompositorBridgeChild and ImageBridgeChild for now. + wr::MaybeExternalImageId mExternalImageId; + + // Used to assign serial ids of TextureClient. + static mozilla::Atomic<uint64_t> sSerialCounter; + + friend class TextureChild; + friend void TestTextureClientSurface(TextureClient*, gfxImageSurface*); + friend void TestTextureClientYCbCr(TextureClient*, PlanarYCbCrData&); + friend already_AddRefed<TextureHost> CreateTextureHostWithBackend( + TextureClient*, ISurfaceAllocator*, LayersBackend&); + +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL + public: + // Pointer to the pool this tile came from. + TextureClientPool* mPoolTracker; +#endif +}; + +/** + * Task that releases TextureClient pointer on a specified thread. + */ +class TextureClientReleaseTask : public Runnable { + public: + explicit TextureClientReleaseTask(TextureClient* aClient) + : Runnable("layers::TextureClientReleaseTask"), mTextureClient(aClient) {} + + NS_IMETHOD Run() override { + mTextureClient = nullptr; + return NS_OK; + } + + private: + RefPtr<TextureClient> mTextureClient; +}; + +// Automatically lock and unlock a texture. Since texture locking is fallible, +// Succeeded() must be checked on the guard object before proceeding. +class MOZ_RAII TextureClientAutoLock { + public: + TextureClientAutoLock(TextureClient* aTexture, OpenMode aMode) + : mTexture(aTexture), mSucceeded(false) { + mSucceeded = mTexture->Lock(aMode); +#ifdef DEBUG + mChecked = false; +#endif + } + ~TextureClientAutoLock() { + MOZ_ASSERT(mChecked); + if (mSucceeded) { + mTexture->Unlock(); + } + } + + bool Succeeded() { +#ifdef DEBUG + mChecked = true; +#endif + return mSucceeded; + } + + private: + TextureClient* mTexture; +#ifdef DEBUG + bool mChecked; +#endif + bool mSucceeded; +}; + +/// Convenience function to set the content of ycbcr texture. +bool UpdateYCbCrTextureClient(TextureClient* aTexture, + const PlanarYCbCrData& aData); + +TextureType PreferredCanvasTextureType(KnowsCompositor* aKnowsCompositor); + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/TextureClientPool.cpp b/gfx/layers/client/TextureClientPool.cpp new file mode 100644 index 0000000000..2ca0ce20e8 --- /dev/null +++ b/gfx/layers/client/TextureClientPool.cpp @@ -0,0 +1,307 @@ +/* -*- 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 "TextureClientPool.h" +#include "CompositableClient.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/TextureForwarder.h" +#include "mozilla/StaticPrefs_layers.h" + +#include "nsComponentManagerUtils.h" + +#define TCP_LOG(...) +//#define TCP_LOG(...) printf_stderr(__VA_ARGS__); + +namespace mozilla { +namespace layers { + +// We want to shrink to our maximum size of N unused tiles +// after a timeout to allow for short-term budget requirements +static void ShrinkCallback(nsITimer* aTimer, void* aClosure) { + static_cast<TextureClientPool*>(aClosure)->ShrinkToMaximumSize(); +} + +// After a certain amount of inactivity, let's clear the pool so that +// we don't hold onto tiles needlessly. In general, allocations are +// cheap enough that re-allocating isn't an issue unless we're allocating +// at an inopportune time (e.g. mid-animation). +static void ClearCallback(nsITimer* aTimer, void* aClosure) { + static_cast<TextureClientPool*>(aClosure)->Clear(); +} + +TextureClientPool::TextureClientPool( + KnowsCompositor* aKnowsCompositor, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, TextureFlags aFlags, uint32_t aShrinkTimeoutMsec, + uint32_t aClearTimeoutMsec, uint32_t aInitialPoolSize, + uint32_t aPoolUnusedSize, TextureForwarder* aAllocator) + : mKnowsCompositor(aKnowsCompositor), + mFormat(aFormat), + mSize(aSize), + mFlags(aFlags), + mShrinkTimeoutMsec(aShrinkTimeoutMsec), + mClearTimeoutMsec(aClearTimeoutMsec), + mInitialPoolSize(aInitialPoolSize), + mPoolUnusedSize(aPoolUnusedSize), + mOutstandingClients(0), + mSurfaceAllocator(aAllocator), + mDestroyed(false) { + TCP_LOG("TexturePool %p created with maximum unused texture clients %u\n", + this, mInitialPoolSize); + mShrinkTimer = NS_NewTimer(); + mClearTimer = NS_NewTimer(); + if (aFormat == gfx::SurfaceFormat::UNKNOWN) { + gfxWarning() << "Creating texture pool for SurfaceFormat::UNKNOWN format"; + } +} + +TextureClientPool::~TextureClientPool() { + mShrinkTimer->Cancel(); + mClearTimer->Cancel(); +} + +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL +static bool TestClientPool(const char* what, TextureClient* aClient, + TextureClientPool* aPool) { + if (!aClient || !aPool) { + return false; + } + + TextureClientPool* actual = aClient->mPoolTracker; + bool ok = (actual == aPool); + if (ok) { + ok = (aClient->GetFormat() == aPool->GetFormat()); + } + + if (!ok) { + if (actual) { + gfxCriticalError() << "Pool error(" << what << "): " << aPool << "-" + << aPool->GetFormat() << ", " << actual << "-" + << actual->GetFormat() << ", " << aClient->GetFormat(); + MOZ_CRASH("GFX: Crashing with actual"); + } else { + gfxCriticalError() << "Pool error(" << what << "): " << aPool << "-" + << aPool->GetFormat() << ", nullptr, " + << aClient->GetFormat(); + MOZ_CRASH("GFX: Crashing without actual"); + } + } + return ok; +} +#endif + +already_AddRefed<TextureClient> TextureClientPool::GetTextureClient() { + // Try to fetch a client from the pool + RefPtr<TextureClient> textureClient; + + // We initially allocate mInitialPoolSize for our pool. If we run + // out of TextureClients, we allocate additional TextureClients to try and + // keep around mPoolUnusedSize + if (mTextureClients.empty()) { + AllocateTextureClient(); + } + + if (mTextureClients.empty()) { + // All our allocations failed, return nullptr + return nullptr; + } + + mOutstandingClients++; + textureClient = mTextureClients.top(); + mTextureClients.pop(); +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL + if (textureClient) { + textureClient->mPoolTracker = this; + } + DebugOnly<bool> ok = TestClientPool("fetch", textureClient, this); + MOZ_ASSERT(ok); +#endif + TCP_LOG("TexturePool %p giving %p from pool; size %u outstanding %u\n", this, + textureClient.get(), mTextureClients.size(), mOutstandingClients); + + return textureClient.forget(); +} + +void TextureClientPool::AllocateTextureClient() { + TCP_LOG("TexturePool %p allocating TextureClient, outstanding %u\n", this, + mOutstandingClients); + + TextureAllocationFlags allocFlags = ALLOC_DEFAULT; + + RefPtr<TextureClient> newClient; + if (StaticPrefs::layers_force_shmem_tiles_AtStartup()) { + // gfx::BackendType::NONE means use the content backend + newClient = TextureClient::CreateForRawBufferAccess( + mSurfaceAllocator, mFormat, mSize, gfx::BackendType::NONE, GetBackend(), + mFlags, allocFlags); + } else { + newClient = TextureClient::CreateForDrawing( + mSurfaceAllocator, mFormat, mSize, mKnowsCompositor, + BackendSelector::Content, mFlags, allocFlags); + } + + if (newClient) { + mTextureClients.push(newClient); + } +} + +void TextureClientPool::ResetTimers() { + // Shrink down if we're beyond our maximum size + if (mShrinkTimeoutMsec && + mTextureClients.size() + mTextureClientsDeferred.size() > + mPoolUnusedSize) { + TCP_LOG("TexturePool %p scheduling a shrink-to-max-size\n", this); + mShrinkTimer->InitWithNamedFuncCallback( + ShrinkCallback, this, mShrinkTimeoutMsec, nsITimer::TYPE_ONE_SHOT, + "layers::TextureClientPool::ResetTimers"); + } + + // Clear pool after a period of inactivity to reduce memory consumption + if (mClearTimeoutMsec) { + TCP_LOG("TexturePool %p scheduling a clear\n", this); + mClearTimer->InitWithNamedFuncCallback( + ClearCallback, this, mClearTimeoutMsec, nsITimer::TYPE_ONE_SHOT, + "layers::TextureClientPool::ResetTimers"); + } +} + +void TextureClientPool::ReturnTextureClient(TextureClient* aClient) { + if (!aClient || mDestroyed) { + return; + } +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL + DebugOnly<bool> ok = TestClientPool("return", aClient, this); + MOZ_ASSERT(ok); +#endif + // Add the client to the pool: + MOZ_ASSERT(mOutstandingClients > mTextureClientsDeferred.size()); + mOutstandingClients--; + mTextureClients.push(aClient); + TCP_LOG("TexturePool %p had client %p returned; size %u outstanding %u\n", + this, aClient, mTextureClients.size(), mOutstandingClients); + + ResetTimers(); +} + +void TextureClientPool::ReturnTextureClientDeferred(TextureClient* aClient) { + if (!aClient || mDestroyed) { + return; + } + MOZ_ASSERT(aClient->GetReadLock()); +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL + DebugOnly<bool> ok = TestClientPool("defer", aClient, this); + MOZ_ASSERT(ok); +#endif + mTextureClientsDeferred.push_back(aClient); + TCP_LOG( + "TexturePool %p had client %p defer-returned, size %u outstanding %u\n", + this, aClient, mTextureClientsDeferred.size(), mOutstandingClients); + + ResetTimers(); +} + +void TextureClientPool::ShrinkToMaximumSize() { + // We're over our desired maximum size, immediately shrink down to the + // maximum. + // + // We cull from the deferred TextureClients first, as we can't reuse those + // until they get returned. + uint32_t totalUnusedTextureClients = + mTextureClients.size() + mTextureClientsDeferred.size(); + + // If we have > mInitialPoolSize outstanding, then we want to keep around + // mPoolUnusedSize at a maximum. If we have fewer than mInitialPoolSize + // outstanding, then keep around the entire initial pool size. + uint32_t targetUnusedClients; + if (mOutstandingClients > mInitialPoolSize) { + targetUnusedClients = mPoolUnusedSize; + } else { + targetUnusedClients = mInitialPoolSize; + } + + TCP_LOG( + "TexturePool %p shrinking to maximum unused size %u; current pool size " + "%u; total outstanding %u\n", + this, targetUnusedClients, totalUnusedTextureClients, + mOutstandingClients); + + while (totalUnusedTextureClients > targetUnusedClients) { + if (!mTextureClientsDeferred.empty()) { + mOutstandingClients--; + TCP_LOG("TexturePool %p dropped deferred client %p; %u remaining\n", this, + mTextureClientsDeferred.front().get(), + mTextureClientsDeferred.size() - 1); + mTextureClientsDeferred.pop_front(); + } else { + TCP_LOG("TexturePool %p dropped non-deferred client %p; %u remaining\n", + this, mTextureClients.top().get(), mTextureClients.size() - 1); + mTextureClients.pop(); + } + totalUnusedTextureClients--; + } +} + +void TextureClientPool::ReturnDeferredClients() { + if (mTextureClientsDeferred.empty()) { + return; + } + + TCP_LOG("TexturePool %p returning %u deferred clients to pool\n", this, + mTextureClientsDeferred.size()); + + ReturnUnlockedClients(); + ShrinkToMaximumSize(); +} + +void TextureClientPool::ReturnUnlockedClients() { + for (auto it = mTextureClientsDeferred.begin(); + it != mTextureClientsDeferred.end();) { + MOZ_ASSERT((*it)->GetReadLock()->AsNonBlockingLock()->GetReadCount() >= 1); + // Last count is held by the lock itself. + if (!(*it)->IsReadLocked()) { + mTextureClients.push(*it); + it = mTextureClientsDeferred.erase(it); + + MOZ_ASSERT(mOutstandingClients > 0); + mOutstandingClients--; + } else { + it++; + } + } +} + +void TextureClientPool::ReportClientLost() { + MOZ_ASSERT(mOutstandingClients > mTextureClientsDeferred.size()); + mOutstandingClients--; + TCP_LOG("TexturePool %p getting report client lost; down to %u outstanding\n", + this, mOutstandingClients); +} + +void TextureClientPool::Clear() { + TCP_LOG("TexturePool %p getting cleared\n", this); + while (!mTextureClients.empty()) { + TCP_LOG("TexturePool %p releasing client %p\n", this, + mTextureClients.top().get()); + mTextureClients.pop(); + } + while (!mTextureClientsDeferred.empty()) { + MOZ_ASSERT(mOutstandingClients > 0); + mOutstandingClients--; + TCP_LOG("TexturePool %p releasing deferred client %p\n", this, + mTextureClientsDeferred.front().get()); + mTextureClientsDeferred.pop_front(); + } +} + +void TextureClientPool::Destroy() { + Clear(); + mDestroyed = true; + mInitialPoolSize = 0; + mPoolUnusedSize = 0; + mKnowsCompositor = nullptr; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/TextureClientPool.h b/gfx/layers/client/TextureClientPool.h new file mode 100644 index 0000000000..d557ca9a03 --- /dev/null +++ b/gfx/layers/client/TextureClientPool.h @@ -0,0 +1,175 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_TEXTURECLIENTPOOL_H +#define MOZILLA_GFX_TEXTURECLIENTPOOL_H + +#include "mozilla/gfx/Types.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/RefPtr.h" +#include "mozilla/layers/KnowsCompositor.h" +#include "TextureClient.h" +#include "nsITimer.h" +#include <stack> +#include <list> + +namespace mozilla { +namespace layers { + +class ISurfaceAllocator; +class TextureForwarder; +class TextureReadLock; + +class TextureClientAllocator { + protected: + virtual ~TextureClientAllocator() = default; + + public: + NS_INLINE_DECL_REFCOUNTING(TextureClientAllocator) + + virtual already_AddRefed<TextureClient> GetTextureClient() = 0; + + /** + * Return a TextureClient that is not yet ready to be reused, but will be + * imminently. + */ + virtual void ReturnTextureClientDeferred(TextureClient* aClient) = 0; + + virtual void ReportClientLost() = 0; +}; + +class TextureClientPool final : public TextureClientAllocator { + virtual ~TextureClientPool(); + + public: + TextureClientPool(KnowsCompositor* aKnowsCompositor, + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + TextureFlags aFlags, uint32_t aShrinkTimeoutMsec, + uint32_t aClearTimeoutMsec, uint32_t aInitialPoolSize, + uint32_t aPoolUnusedSize, TextureForwarder* aAllocator); + + /** + * Gets an allocated TextureClient of size and format that are determined + * by the initialisation parameters given to the pool. This will either be + * a cached client that was returned to the pool, or a newly allocated + * client if one isn't available. + * + * All clients retrieved by this method should be returned using the return + * functions, or reported lost so that the pool can manage its size correctly. + */ + already_AddRefed<TextureClient> GetTextureClient() override; + + /** + * Return a TextureClient that is no longer being used and is ready for + * immediate re-use or destruction. + */ + void ReturnTextureClient(TextureClient* aClient); + + /** + * Return a TextureClient that is not yet ready to be reused, but will be + * imminently. + */ + void ReturnTextureClientDeferred(TextureClient* aClient) override; + + /** + * Return any clients to the pool that were previously returned in + * ReturnTextureClientDeferred. + */ + void ReturnDeferredClients(); + + /** + * Attempt to shrink the pool so that there are no more than + * mInitialPoolSize outstanding. + */ + void ShrinkToMaximumSize(); + + /** + * Report that a client retrieved via GetTextureClient() has become + * unusable, so that it will no longer be tracked. + */ + void ReportClientLost() override; + + /** + * Calling this will cause the pool to attempt to relinquish any unused + * clients. + */ + void Clear(); + + LayersBackend GetBackend() const { + return mKnowsCompositor->GetCompositorBackendType(); + } + int32_t GetMaxTextureSize() const { + return mKnowsCompositor->GetMaxTextureSize(); + } + gfx::SurfaceFormat GetFormat() { return mFormat; } + TextureFlags GetFlags() const { return mFlags; } + + /** + * Clear the pool and put it in a state where it won't recycle any new + * texture. + */ + void Destroy(); + + private: + void ReturnUnlockedClients(); + + /// Allocate a single TextureClient to be returned from the pool. + void AllocateTextureClient(); + + /// Reset and/or initialise timers for shrinking/clearing the pool. + void ResetTimers(); + + /// KnowsCompositor passed to the TextureClient for buffer creation. + RefPtr<KnowsCompositor> mKnowsCompositor; + + /// Format is passed to the TextureClient for buffer creation. + gfx::SurfaceFormat mFormat; + + /// The width and height of the tiles to be used. + gfx::IntSize mSize; + + /// Flags passed to the TextureClient for buffer creation. + const TextureFlags mFlags; + + /// How long to wait after a TextureClient is returned before trying + /// to shrink the pool to its maximum size of mPoolUnusedSize. + uint32_t mShrinkTimeoutMsec; + + /// How long to wait after a TextureClient is returned before trying + /// to clear the pool. + uint32_t mClearTimeoutMsec; + + // The initial number of unused texture clients to seed the pool with + // on construction + uint32_t mInitialPoolSize; + + // How many unused texture clients to try and keep around if we go over + // the initial allocation + uint32_t mPoolUnusedSize; + + /// This is a total number of clients in the wild and in the stack of + /// deferred clients (see below). So, the total number of clients in + /// existence is always mOutstandingClients + the size of mTextureClients. + uint32_t mOutstandingClients; + + std::stack<RefPtr<TextureClient>> mTextureClients; + + std::list<RefPtr<TextureClient>> mTextureClientsDeferred; + RefPtr<nsITimer> mShrinkTimer; + RefPtr<nsITimer> mClearTimer; + // This mSurfaceAllocator owns us, so no need to hold a ref to it + TextureForwarder* mSurfaceAllocator; + + // Keep track of whether this pool has been destroyed or not. If it has, + // we won't accept returns of TextureClients anymore, and the refcounting + // should take care of their destruction. + bool mDestroyed; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_TEXTURECLIENTPOOL_H */ diff --git a/gfx/layers/client/TextureClientRecycleAllocator.cpp b/gfx/layers/client/TextureClientRecycleAllocator.cpp new file mode 100644 index 0000000000..a88e272330 --- /dev/null +++ b/gfx/layers/client/TextureClientRecycleAllocator.cpp @@ -0,0 +1,261 @@ +/* -*- 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 "gfxPlatform.h" +#include "ImageContainer.h" +#include "mozilla/layers/BufferTexture.h" +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/TextureForwarder.h" +#include "TextureClientRecycleAllocator.h" + +namespace mozilla { +namespace layers { + +// Used to keep TextureClient's reference count stable as not to disrupt +// recycling. +class TextureClientHolder final { + ~TextureClientHolder() = default; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TextureClientHolder) + + explicit TextureClientHolder(TextureClient* aClient) + : mTextureClient(aClient), mWillRecycle(true) {} + + TextureClient* GetTextureClient() { return mTextureClient; } + + bool WillRecycle() { return mWillRecycle; } + + void ClearWillRecycle() { mWillRecycle = false; } + + void ClearTextureClient() { mTextureClient = nullptr; } + + protected: + RefPtr<TextureClient> mTextureClient; + bool mWillRecycle; +}; + +class MOZ_RAII DefaultTextureClientAllocationHelper + : public ITextureClientAllocationHelper { + public: + DefaultTextureClientAllocationHelper( + TextureClientRecycleAllocator* aAllocator, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, BackendSelector aSelector, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocationFlags) + : ITextureClientAllocationHelper(aFormat, aSize, aSelector, aTextureFlags, + aAllocationFlags), + mAllocator(aAllocator) {} + + bool IsCompatible(TextureClient* aTextureClient) override { + if (aTextureClient->GetFormat() != mFormat || + aTextureClient->GetSize() != mSize) { + return false; + } + return true; + } + + already_AddRefed<TextureClient> Allocate( + KnowsCompositor* aKnowsCompositor) override { + return mAllocator->Allocate(mFormat, mSize, mSelector, mTextureFlags, + mAllocationFlags); + } + + protected: + TextureClientRecycleAllocator* mAllocator; +}; + +YCbCrTextureClientAllocationHelper::YCbCrTextureClientAllocationHelper( + const PlanarYCbCrData& aData, const gfx::IntSize& aYSize, + const gfx::IntSize& aCbCrSize, TextureFlags aTextureFlags) + : ITextureClientAllocationHelper(gfx::SurfaceFormat::YUV, aYSize, + BackendSelector::Content, aTextureFlags, + ALLOC_DEFAULT), + mData(aData), + mYSize(aYSize), + mCbCrSize(aCbCrSize) {} + +YCbCrTextureClientAllocationHelper::YCbCrTextureClientAllocationHelper( + const PlanarYCbCrData& aData, TextureFlags aTextureFlags) + : YCbCrTextureClientAllocationHelper(aData, aData.YDataSize(), + aData.CbCrDataSize(), aTextureFlags) {} + +bool YCbCrTextureClientAllocationHelper::IsCompatible( + TextureClient* aTextureClient) { + MOZ_ASSERT(aTextureClient->GetFormat() == gfx::SurfaceFormat::YUV); + + BufferTextureData* bufferData = + aTextureClient->GetInternalData()->AsBufferTextureData(); + + if (!bufferData || + !bufferData->GetPictureRect().IsEqualEdges(mData.mPictureRect) || + bufferData->GetYSize().isNothing() || + bufferData->GetYSize().ref() != mYSize || + bufferData->GetCbCrSize().isNothing() || + bufferData->GetCbCrSize().ref() != mCbCrSize || + bufferData->GetYStride().isNothing() || + bufferData->GetYStride().ref() != mData.mYStride || + bufferData->GetCbCrStride().isNothing() || + bufferData->GetCbCrStride().ref() != mData.mCbCrStride || + bufferData->GetYUVColorSpace().isNothing() || + bufferData->GetYUVColorSpace().ref() != mData.mYUVColorSpace || + bufferData->GetColorDepth().isNothing() || + bufferData->GetColorDepth().ref() != mData.mColorDepth || + bufferData->GetStereoMode().isNothing() || + bufferData->GetStereoMode().ref() != mData.mStereoMode || + bufferData->GetChromaSubsampling().isNothing() || + bufferData->GetChromaSubsampling().ref() != mData.mChromaSubsampling) { + return false; + } + return true; +} + +already_AddRefed<TextureClient> YCbCrTextureClientAllocationHelper::Allocate( + KnowsCompositor* aKnowsCompositor) { + return TextureClient::CreateForYCbCr( + aKnowsCompositor, mData.mPictureRect, mYSize, mData.mYStride, mCbCrSize, + mData.mCbCrStride, mData.mStereoMode, mData.mColorDepth, + mData.mYUVColorSpace, mData.mColorRange, mData.mChromaSubsampling, + mTextureFlags); +} + +TextureClientRecycleAllocator::TextureClientRecycleAllocator( + KnowsCompositor* aKnowsCompositor) + : mKnowsCompositor(aKnowsCompositor), + mMaxPooledSize(kMaxPooledSized), + mLock("TextureClientRecycleAllocatorImp.mLock"), + mIsDestroyed(false) {} + +TextureClientRecycleAllocator::~TextureClientRecycleAllocator() { + MutexAutoLock lock(mLock); + while (!mPooledClients.empty()) { + mPooledClients.pop(); + } + MOZ_ASSERT(mInUseClients.empty()); +} + +void TextureClientRecycleAllocator::SetMaxPoolSize(uint32_t aMax) { + mMaxPooledSize = aMax; +} + +already_AddRefed<TextureClient> TextureClientRecycleAllocator::CreateOrRecycle( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags aAllocFlags) { + MOZ_ASSERT(!(aTextureFlags & TextureFlags::RECYCLE)); + DefaultTextureClientAllocationHelper helper(this, aFormat, aSize, aSelector, + aTextureFlags, aAllocFlags); + return CreateOrRecycle(helper); +} + +already_AddRefed<TextureClient> TextureClientRecycleAllocator::CreateOrRecycle( + ITextureClientAllocationHelper& aHelper) { + MOZ_ASSERT(aHelper.mTextureFlags & TextureFlags::RECYCLE); + + RefPtr<TextureClientHolder> textureHolder; + + { + MutexAutoLock lock(mLock); + if (mIsDestroyed || !mKnowsCompositor->GetTextureForwarder()) { + return nullptr; + } + if (!mPooledClients.empty()) { + textureHolder = mPooledClients.top(); + mPooledClients.pop(); + // If the texture's allocator is not open or a pooled TextureClient is + // not compatible, release it. + if (!textureHolder->GetTextureClient()->GetAllocator()->IPCOpen() || + !aHelper.IsCompatible(textureHolder->GetTextureClient())) { + // Release TextureClient. + RefPtr<Runnable> task = + new TextureClientReleaseTask(textureHolder->GetTextureClient()); + textureHolder->ClearTextureClient(); + textureHolder = nullptr; + mKnowsCompositor->GetTextureForwarder()->GetThread()->Dispatch( + task.forget()); + } else { + textureHolder->GetTextureClient()->RecycleTexture( + aHelper.mTextureFlags); + } + } + } + + if (!textureHolder) { + // Allocate new TextureClient + RefPtr<TextureClient> texture = aHelper.Allocate(mKnowsCompositor); + if (!texture) { + return nullptr; + } + textureHolder = new TextureClientHolder(texture); + } + + { + MutexAutoLock lock(mLock); + MOZ_ASSERT(mInUseClients.find(textureHolder->GetTextureClient()) == + mInUseClients.end()); + // Register TextureClient + mInUseClients[textureHolder->GetTextureClient()] = textureHolder; + } + RefPtr<TextureClient> client(textureHolder->GetTextureClient()); + + // Make sure the texture holds a reference to us, and ask it to call + // RecycleTextureClient when its ref count drops to 1. + client->SetRecycleAllocator(this); + return client.forget(); +} + +already_AddRefed<TextureClient> TextureClientRecycleAllocator::Allocate( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags aAllocFlags) { + return TextureClient::CreateForDrawing(mKnowsCompositor, aFormat, aSize, + aSelector, aTextureFlags, aAllocFlags); +} + +void TextureClientRecycleAllocator::ShrinkToMinimumSize() { + MutexAutoLock lock(mLock); + while (!mPooledClients.empty()) { + mPooledClients.pop(); + } + // We can not clear using TextureClients safely. + // Just clear WillRecycle here. + std::map<TextureClient*, RefPtr<TextureClientHolder> >::iterator it; + for (it = mInUseClients.begin(); it != mInUseClients.end(); it++) { + RefPtr<TextureClientHolder> holder = it->second; + holder->ClearWillRecycle(); + } +} + +void TextureClientRecycleAllocator::Destroy() { + MutexAutoLock lock(mLock); + while (!mPooledClients.empty()) { + mPooledClients.pop(); + } + mIsDestroyed = true; +} + +void TextureClientRecycleAllocator::RecycleTextureClient( + TextureClient* aClient) { + // Clearing the recycle allocator drops a reference, so make sure we stay + // alive for the duration of this function. + RefPtr<TextureClientRecycleAllocator> kungFuDeathGrip(this); + aClient->SetRecycleAllocator(nullptr); + + RefPtr<TextureClientHolder> textureHolder; + { + MutexAutoLock lock(mLock); + if (mInUseClients.find(aClient) != mInUseClients.end()) { + textureHolder = + mInUseClients[aClient]; // Keep reference count of + // TextureClientHolder within lock. + if (textureHolder->WillRecycle() && !mIsDestroyed && + mPooledClients.size() < mMaxPooledSize) { + mPooledClients.push(textureHolder); + } + mInUseClients.erase(aClient); + } + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/TextureClientRecycleAllocator.h b/gfx/layers/client/TextureClientRecycleAllocator.h new file mode 100644 index 0000000000..4abaa60c4e --- /dev/null +++ b/gfx/layers/client/TextureClientRecycleAllocator.h @@ -0,0 +1,140 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_TEXTURECLIENT_RECYCLE_ALLOCATOR_H +#define MOZILLA_GFX_TEXTURECLIENT_RECYCLE_ALLOCATOR_H + +#include <map> +#include <stack> +#include "mozilla/gfx/Types.h" +#include "mozilla/layers/TextureForwarder.h" +#include "mozilla/RefPtr.h" +#include "TextureClient.h" +#include "mozilla/Mutex.h" + +namespace mozilla { +namespace layers { + +class TextureClientHolder; +struct PlanarYCbCrData; + +class ITextureClientRecycleAllocator { + protected: + virtual ~ITextureClientRecycleAllocator() = default; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ITextureClientRecycleAllocator) + + protected: + friend class TextureClient; + virtual void RecycleTextureClient(TextureClient* aClient) = 0; +}; + +class ITextureClientAllocationHelper { + public: + ITextureClientAllocationHelper(gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + BackendSelector aSelector, + TextureFlags aTextureFlags, + TextureAllocationFlags aAllocationFlags) + : mFormat(aFormat), + mSize(aSize), + mSelector(aSelector), + mTextureFlags(aTextureFlags | + TextureFlags::RECYCLE) // Set recycle flag + , + mAllocationFlags(aAllocationFlags) {} + + virtual already_AddRefed<TextureClient> Allocate( + KnowsCompositor* aKnowsCompositor) = 0; + virtual bool IsCompatible(TextureClient* aTextureClient) = 0; + + const gfx::SurfaceFormat mFormat; + const gfx::IntSize mSize; + const BackendSelector mSelector; + const TextureFlags mTextureFlags; + const TextureAllocationFlags mAllocationFlags; +}; + +class MOZ_RAII YCbCrTextureClientAllocationHelper + : public ITextureClientAllocationHelper { + public: + YCbCrTextureClientAllocationHelper(const PlanarYCbCrData& aData, + const gfx::IntSize& aYSize, + const gfx::IntSize& aCbCrSize, + TextureFlags aTextureFlags); + + YCbCrTextureClientAllocationHelper(const PlanarYCbCrData& aData, + TextureFlags aTextureFlags); + + bool IsCompatible(TextureClient* aTextureClient) override; + + already_AddRefed<TextureClient> Allocate( + KnowsCompositor* aKnowsCompositor) override; + + protected: + const PlanarYCbCrData& mData; + const gfx::IntSize mYSize; + const gfx::IntSize mCbCrSize; +}; + +/** + * TextureClientRecycleAllocator provides TextureClients allocation and + * recycling capabilities. It expects allocations of same sizes and + * attributres. If a recycled TextureClient is different from + * requested one, the recycled one is dropped and new TextureClient is + * allocated. + * + * By default this uses TextureClient::CreateForDrawing to allocate new texture + * clients. + */ +class TextureClientRecycleAllocator : public ITextureClientRecycleAllocator { + protected: + virtual ~TextureClientRecycleAllocator(); + + public: + explicit TextureClientRecycleAllocator(KnowsCompositor* aKnowsCompositor); + + void SetMaxPoolSize(uint32_t aMax); + + // Creates and allocates a TextureClient. + already_AddRefed<TextureClient> CreateOrRecycle( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags flags = ALLOC_DEFAULT); + + already_AddRefed<TextureClient> CreateOrRecycle( + ITextureClientAllocationHelper& aHelper); + + void ShrinkToMinimumSize(); + + void Destroy(); + + KnowsCompositor* GetKnowsCompositor() { return mKnowsCompositor; } + + protected: + virtual already_AddRefed<TextureClient> Allocate( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags aAllocFlags); + + const RefPtr<KnowsCompositor> mKnowsCompositor; + + friend class DefaultTextureClientAllocationHelper; + void RecycleTextureClient(TextureClient* aClient) override; + + static const uint32_t kMaxPooledSized = 2; + uint32_t mMaxPooledSize; + + std::map<TextureClient*, RefPtr<TextureClientHolder> > mInUseClients; + + // stack is good from Graphics cache usage point of view. + std::stack<RefPtr<TextureClientHolder> > mPooledClients; + Mutex mLock MOZ_UNANNOTATED; + bool mIsDestroyed; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_TEXTURECLIENT_RECYCLE_ALLOCATOR_H */ diff --git a/gfx/layers/client/TextureClientSharedSurface.cpp b/gfx/layers/client/TextureClientSharedSurface.cpp new file mode 100644 index 0000000000..2de958b298 --- /dev/null +++ b/gfx/layers/client/TextureClientSharedSurface.cpp @@ -0,0 +1,138 @@ +/* -*- 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 "TextureClientSharedSurface.h" + +#include "GLContext.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" // for gfxDebug +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/Unused.h" +#include "nsThreadUtils.h" +#include "SharedSurface.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/layers/AndroidHardwareBuffer.h" +#endif + +using namespace mozilla::gl; + +namespace mozilla { +namespace layers { + +/* static */ +already_AddRefed<TextureClient> SharedSurfaceTextureData::CreateTextureClient( + const layers::SurfaceDescriptor& aDesc, const gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, TextureFlags aFlags, LayersIPCChannel* aAllocator) { + auto data = MakeUnique<SharedSurfaceTextureData>(aDesc, aFormat, aSize); + return TextureClient::CreateWithData(data.release(), aFlags, aAllocator); +} + +SharedSurfaceTextureData::SharedSurfaceTextureData( + const SurfaceDescriptor& desc, const gfx::SurfaceFormat format, + const gfx::IntSize size) + : mDesc(desc), mFormat(format), mSize(size) {} + +SharedSurfaceTextureData::~SharedSurfaceTextureData() = default; + +void SharedSurfaceTextureData::Deallocate(LayersIPCChannel*) {} + +void SharedSurfaceTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = mSize; + aInfo.format = mFormat; + aInfo.hasSynchronization = false; + aInfo.supportsMoz2D = false; + aInfo.canExposeMappedData = false; +} + +bool SharedSurfaceTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + if (mDesc.type() != + SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer) { + aOutDescriptor = mDesc; + return true; + } + +#ifdef MOZ_WIDGET_ANDROID + // File descriptor is created heare for reducing its allocation. + const SurfaceDescriptorAndroidHardwareBuffer& desc = + mDesc.get_SurfaceDescriptorAndroidHardwareBuffer(); + RefPtr<AndroidHardwareBuffer> buffer = + AndroidHardwareBufferManager::Get()->GetBuffer(desc.bufferId()); + if (!buffer) { + return false; + } + + int fd[2] = {}; + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fd) != 0) { + return false; + } + + UniqueFileHandle readerFd(fd[0]); + UniqueFileHandle writerFd(fd[1]); + + // Send the AHardwareBuffer to an AF_UNIX socket. It does not acquire or + // retain a reference to the buffer object. The caller is therefore + // responsible for ensuring that the buffer remains alive through the lifetime + // of this file descriptor. + int ret = buffer->SendHandleToUnixSocket(writerFd.get()); + if (ret < 0) { + return false; + } + + aOutDescriptor = layers::SurfaceDescriptorAndroidHardwareBuffer( + ipc::FileDescriptor(std::move(readerFd)), buffer->mId, buffer->mSize, + buffer->mFormat); + return true; +#else + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return false; +#endif +} + +TextureFlags SharedSurfaceTextureData::GetTextureFlags() const { + TextureFlags flags = TextureFlags::NO_FLAGS; + +#ifdef MOZ_WIDGET_ANDROID + if (mDesc.type() == + SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer) { + flags |= TextureFlags::WAIT_HOST_USAGE_END; + } +#endif + return flags; +} + +Maybe<uint64_t> SharedSurfaceTextureData::GetBufferId() const { +#ifdef MOZ_WIDGET_ANDROID + if (mDesc.type() == + SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer) { + const SurfaceDescriptorAndroidHardwareBuffer& desc = + mDesc.get_SurfaceDescriptorAndroidHardwareBuffer(); + return Some(desc.bufferId()); + } +#endif + return Nothing(); +} + +mozilla::ipc::FileDescriptor SharedSurfaceTextureData::GetAcquireFence() { +#ifdef MOZ_WIDGET_ANDROID + if (mDesc.type() == + SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer) { + const SurfaceDescriptorAndroidHardwareBuffer& desc = + mDesc.get_SurfaceDescriptorAndroidHardwareBuffer(); + RefPtr<AndroidHardwareBuffer> buffer = + AndroidHardwareBufferManager::Get()->GetBuffer(desc.bufferId()); + if (!buffer) { + return ipc::FileDescriptor(); + } + + return buffer->GetAcquireFence(); + } +#endif + return ipc::FileDescriptor(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/TextureClientSharedSurface.h b/gfx/layers/client/TextureClientSharedSurface.h new file mode 100644 index 0000000000..913afed848 --- /dev/null +++ b/gfx/layers/client/TextureClientSharedSurface.h @@ -0,0 +1,68 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_TEXTURECLIENT_SHAREDSURFACE_H +#define MOZILLA_GFX_TEXTURECLIENT_SHAREDSURFACE_H + +#include <cstddef> // for size_t +#include <stdint.h> // for uint32_t, uint8_t, uint64_t +#include "GLContextTypes.h" // for GLContext (ptr only), etc +#include "TextureClient.h" +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for RefPtr, RefCounted +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "mozilla/layers/CompositorTypes.h" // for TextureFlags, etc +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor + +namespace mozilla { +namespace gl { +class GLContext; +class SharedSurface; +class SurfaceFactory; +} // namespace gl + +namespace layers { + +class SharedSurfaceTextureClient; + +class SharedSurfaceTextureData : public TextureData { + friend class SharedSurfaceTextureClient; + + public: + const SurfaceDescriptor mDesc; + const gfx::SurfaceFormat mFormat; + const gfx::IntSize mSize; + + static already_AddRefed<TextureClient> CreateTextureClient( + const layers::SurfaceDescriptor& aDesc, const gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, TextureFlags aFlags, LayersIPCChannel* aAllocator); + + SharedSurfaceTextureData(const SurfaceDescriptor&, gfx::SurfaceFormat, + gfx::IntSize); + virtual ~SharedSurfaceTextureData(); + + bool Lock(OpenMode) override { return false; } + + void Unlock() override {} + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + void Deallocate(LayersIPCChannel*) override; + + TextureFlags GetTextureFlags() const override; + + Maybe<uint64_t> GetBufferId() const override; + + mozilla::ipc::FileDescriptor GetAcquireFence() override; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_TEXTURECLIENT_SHAREDSURFACE_H diff --git a/gfx/layers/client/TextureRecorded.cpp b/gfx/layers/client/TextureRecorded.cpp new file mode 100644 index 0000000000..3a88a6addd --- /dev/null +++ b/gfx/layers/client/TextureRecorded.cpp @@ -0,0 +1,122 @@ +/* -*- 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 "TextureRecorded.h" + +#include "RecordedCanvasEventImpl.h" + +namespace mozilla { +namespace layers { + +// The texture ID is used in the GPU process both to lookup the real texture in +// the canvas threads and to lookup the SurfaceDescriptor for that texture in +// the compositor thread. It is therefore important that the ID is unique (per +// recording process), otherwise an old descriptor can be picked up. This means +// we can't use the pointer in the recording process as an ID like we do for +// other objects. +static int64_t sNextRecordedTextureId = 0; + +RecordedTextureData::RecordedTextureData( + already_AddRefed<CanvasChild> aCanvasChild, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, TextureType aTextureType) + : mCanvasChild(aCanvasChild), mSize(aSize), mFormat(aFormat) { + mCanvasChild->EnsureRecorder(aTextureType); +} + +RecordedTextureData::~RecordedTextureData() { + // We need the translator to drop its reference for the DrawTarget first, + // because the TextureData might need to destroy its DrawTarget within a lock. + mDT = nullptr; + mCanvasChild->RecordEvent(RecordedTextureDestruction(mTextureId)); +} + +void RecordedTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = mSize; + aInfo.format = mFormat; + aInfo.supportsMoz2D = true; + aInfo.hasSynchronization = true; +} + +bool RecordedTextureData::Lock(OpenMode aMode) { + mCanvasChild->EnsureBeginTransaction(); + if (!mDT) { + mTextureId = sNextRecordedTextureId++; + mCanvasChild->RecordEvent(RecordedNextTextureId(mTextureId)); + mDT = mCanvasChild->CreateDrawTarget(mSize, mFormat); + if (!mDT) { + return false; + } + + // We lock the TextureData when we create it to get the remote DrawTarget. + mCanvasChild->OnTextureWriteLock(); + mLockedMode = aMode; + return true; + } + + mCanvasChild->RecordEvent(RecordedTextureLock(mTextureId, aMode)); + if (aMode & OpenMode::OPEN_WRITE) { + mCanvasChild->OnTextureWriteLock(); + } + mLockedMode = aMode; + return true; +} + +void RecordedTextureData::Unlock() { + if ((mLockedMode == OpenMode::OPEN_READ_WRITE) && + mCanvasChild->ShouldCacheDataSurface()) { + mSnapshot = mDT->Snapshot(); + mDT->DetachAllSnapshots(); + mCanvasChild->RecordEvent(RecordedCacheDataSurface(mSnapshot.get())); + } + + mCanvasChild->RecordEvent(RecordedTextureUnlock(mTextureId)); + mLockedMode = OpenMode::OPEN_NONE; +} + +already_AddRefed<gfx::DrawTarget> RecordedTextureData::BorrowDrawTarget() { + mSnapshot = nullptr; + return do_AddRef(mDT); +} + +void RecordedTextureData::EndDraw() { + MOZ_ASSERT(mDT->hasOneRef()); + MOZ_ASSERT(mLockedMode == OpenMode::OPEN_READ_WRITE); + + if (mCanvasChild->ShouldCacheDataSurface()) { + mSnapshot = mDT->Snapshot(); + mCanvasChild->RecordEvent(RecordedCacheDataSurface(mSnapshot.get())); + } +} + +already_AddRefed<gfx::SourceSurface> RecordedTextureData::BorrowSnapshot() { + MOZ_ASSERT(mDT); + + if (mSnapshot) { + return mCanvasChild->WrapSurface(mSnapshot); + } + + return mCanvasChild->WrapSurface(mDT->Snapshot()); +} + +void RecordedTextureData::Deallocate(LayersIPCChannel* aAllocator) {} + +bool RecordedTextureData::Serialize(SurfaceDescriptor& aDescriptor) { + aDescriptor = SurfaceDescriptorRecorded(mTextureId); + return true; +} + +void RecordedTextureData::OnForwardedToHost() { + mCanvasChild->OnTextureForwarded(); +} + +TextureFlags RecordedTextureData::GetTextureFlags() const { + // With WebRender, resource open happens asynchronously on RenderThread. + // Use WAIT_HOST_USAGE_END to keep TextureClient alive during host side usage. + return TextureFlags::WAIT_HOST_USAGE_END; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/TextureRecorded.h b/gfx/layers/client/TextureRecorded.h new file mode 100644 index 0000000000..d393d90821 --- /dev/null +++ b/gfx/layers/client/TextureRecorded.h @@ -0,0 +1,60 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_TextureRecorded_h +#define mozilla_layers_TextureRecorded_h + +#include "TextureClient.h" +#include "mozilla/layers/CanvasChild.h" +#include "mozilla/layers/LayersTypes.h" + +namespace mozilla { +namespace layers { + +class RecordedTextureData final : public TextureData { + public: + RecordedTextureData(already_AddRefed<CanvasChild> aCanvasChild, + gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + TextureType aTextureType); + + void FillInfo(TextureData::Info& aInfo) const final; + + bool Lock(OpenMode aMode) final; + + void Unlock() final; + + already_AddRefed<gfx::DrawTarget> BorrowDrawTarget() final; + + void EndDraw() final; + + already_AddRefed<gfx::SourceSurface> BorrowSnapshot() final; + + void Deallocate(LayersIPCChannel* aAllocator) final; + + bool Serialize(SurfaceDescriptor& aDescriptor) final; + + void OnForwardedToHost() final; + + TextureFlags GetTextureFlags() const final; + + private: + DISALLOW_COPY_AND_ASSIGN(RecordedTextureData); + + ~RecordedTextureData() override; + + int64_t mTextureId; + RefPtr<CanvasChild> mCanvasChild; + gfx::IntSize mSize; + gfx::SurfaceFormat mFormat; + RefPtr<gfx::DrawTarget> mDT; + RefPtr<gfx::SourceSurface> mSnapshot; + OpenMode mLockedMode; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_TextureRecorded_h |