diff options
Diffstat (limited to 'gfx/layers/client')
37 files changed, 12387 insertions, 0 deletions
diff --git a/gfx/layers/client/CanvasClient.cpp b/gfx/layers/client/CanvasClient.cpp new file mode 100644 index 000000000..40513984e --- /dev/null +++ b/gfx/layers/client/CanvasClient.cpp @@ -0,0 +1,531 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 "ClientCanvasLayer.h" // for ClientCanvasLayer +#include "GLContext.h" // for GLContext +#include "GLScreenBuffer.h" // for GLScreenBuffer +#include "ScopedGLHelpers.h" +#include "gfx2DGlue.h" // for ImageFormatToSurfaceFormat +#include "gfxPlatform.h" // for gfxPlatform +#include "GLReadTexImageHelper.h" +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/layers/BufferTexture.h" +#include "mozilla/layers/AsyncCanvasRenderer.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/CompositorBridgeChild.h" // for CompositorBridgeChild +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/TextureClient.h" // for TextureClient, etc +#include "mozilla/layers/TextureClientOGL.h" +#include "nsDebug.h" // for printf_stderr, NS_ASSERTION +#include "nsXULAppAPI.h" // for XRE_GetProcessType, etc +#include "TextureClientSharedSurface.h" + +using namespace mozilla::gfx; +using namespace mozilla::gl; + +namespace mozilla { +namespace layers { + +/* static */ already_AddRefed<CanvasClient> +CanvasClient::CreateCanvasClient(CanvasClientType aType, + CompositableForwarder* aForwarder, + TextureFlags aFlags) +{ + switch (aType) { + case CanvasClientTypeShSurf: + return MakeAndAddRef<CanvasClientSharedSurface>(aForwarder, aFlags); + case CanvasClientAsync: + return MakeAndAddRef<CanvasClientBridge>(aForwarder, aFlags); + default: + return MakeAndAddRef<CanvasClient2D>(aForwarder, aFlags); + break; + } +} + +void +CanvasClientBridge::UpdateAsync(AsyncCanvasRenderer* aRenderer) +{ + if (!GetForwarder() || !mLayer || !aRenderer || + !aRenderer->GetCanvasClient()) { + return; + } + + uint64_t asyncID = aRenderer->GetCanvasClientAsyncID(); + if (asyncID == 0 || mAsyncID == asyncID) { + return; + } + + static_cast<ShadowLayerForwarder*>(GetForwarder()) + ->AttachAsyncCompositable(asyncID, mLayer); + mAsyncID = asyncID; +} + +void +CanvasClient2D::UpdateFromTexture(TextureClient* aTexture) +{ + MOZ_ASSERT(aTexture); + + if (!aTexture->IsSharedWithCompositor()) { + if (!AddTextureClient(aTexture)) { + return; + } + } + + mBackBuffer = nullptr; + mFrontBuffer = nullptr; + mBufferProviderTexture = 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); + aTexture->SyncWithObject(GetForwarder()->GetSyncObject()); +} + +void +CanvasClient2D::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) +{ + mBufferProviderTexture = nullptr; + + AutoRemoveTexture autoRemove(this); + if (mBackBuffer && (mBackBuffer->IsReadLocked() || mBackBuffer->GetSize() != aSize)) { + autoRemove.mTexture = mBackBuffer; + mBackBuffer = nullptr; + } + + bool bufferCreated = false; + if (!mBackBuffer) { + bool isOpaque = (aLayer->GetContentFlags() & Layer::CONTENT_OPAQUE); + gfxContentType contentType = isOpaque + ? gfxContentType::COLOR + : gfxContentType::COLOR_ALPHA; + gfx::SurfaceFormat surfaceFormat + = gfxPlatform::GetPlatform()->Optimal2DFormatForContent(contentType); + TextureFlags flags = TextureFlags::DEFAULT; + if (mTextureFlags & TextureFlags::ORIGIN_BOTTOM_LEFT) { + flags |= TextureFlags::ORIGIN_BOTTOM_LEFT; + } + + mBackBuffer = CreateTextureClientForCanvas(surfaceFormat, aSize, flags, aLayer); + if (!mBackBuffer) { + NS_WARNING("Failed to allocate the TextureClient"); + return; + } + mBackBuffer->EnableReadLock(); + MOZ_ASSERT(mBackBuffer->CanExposeDrawTarget()); + + bufferCreated = true; + } + + bool updated = false; + { + TextureClientAutoLock autoLock(mBackBuffer, OpenMode::OPEN_WRITE_ONLY); + if (!autoLock.Succeeded()) { + mBackBuffer = nullptr; + return; + } + + RefPtr<DrawTarget> target = mBackBuffer->BorrowDrawTarget(); + if (target) { + if (!aLayer->UpdateTarget(target)) { + NS_WARNING("Failed to copy the canvas into a TextureClient."); + return; + } + updated = true; + } + } + + if (bufferCreated && !AddTextureClient(mBackBuffer)) { + mBackBuffer = nullptr; + return; + } + + if (updated) { + AutoTArray<CompositableForwarder::TimedTextureClient,1> textures; + CompositableForwarder::TimedTextureClient* t = textures.AppendElement(); + t->mTextureClient = mBackBuffer; + t->mPictureRect = nsIntRect(nsIntPoint(0, 0), mBackBuffer->GetSize()); + t->mFrameID = mFrameID; + GetForwarder()->UseTextures(this, textures); + mBackBuffer->SyncWithObject(GetForwarder()->GetSyncObject()); + } + + mBackBuffer.swap(mFrontBuffer); +} + +already_AddRefed<TextureClient> +CanvasClient2D::CreateTextureClientForCanvas(gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, + TextureFlags aFlags, + ClientCanvasLayer* aLayer) +{ + if (aLayer->IsGLLayer()) { + // We want a cairo backend here as we don't want to be copying into + // an accelerated backend and we like LockBits to work. This is currently + // the most effective way to make this work. + return TextureClient::CreateForRawBufferAccess(GetForwarder(), + aFormat, aSize, BackendType::CAIRO, + mTextureFlags | aFlags); + } + +#ifdef XP_WIN + return CreateTextureClientForDrawing(aFormat, aSize, BackendSelector::Canvas, aFlags); +#else + // 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); +#endif +} + +//////////////////////////////////////////////////////////////////////// + +CanvasClientSharedSurface::CanvasClientSharedSurface(CompositableForwarder* aLayerForwarder, + TextureFlags aFlags) + : CanvasClient(aLayerForwarder, aFlags) +{ } + +CanvasClientSharedSurface::~CanvasClientSharedSurface() +{ + ClearSurfaces(); +} + +//////////////////////////////////////// +// Readback + +// For formats compatible with R8G8B8A8. +static inline void SwapRB_R8G8B8A8(uint8_t* pixel) { + // [RR, GG, BB, AA] + Swap(pixel[0], pixel[2]); +} + +class TexClientFactory +{ + CompositableForwarder* const mAllocator; + const bool mHasAlpha; + const gfx::IntSize mSize; + const gfx::BackendType mBackendType; + const TextureFlags mBaseTexFlags; + const LayersBackend mLayersBackend; + +public: + TexClientFactory(CompositableForwarder* allocator, bool hasAlpha, + const gfx::IntSize& size, gfx::BackendType backendType, + TextureFlags baseTexFlags, LayersBackend layersBackend) + : mAllocator(allocator) + , mHasAlpha(hasAlpha) + , mSize(size) + , mBackendType(backendType) + , mBaseTexFlags(baseTexFlags) + , mLayersBackend(layersBackend) + { + } + +protected: + already_AddRefed<TextureClient> Create(gfx::SurfaceFormat format) { + return TextureClient::CreateForRawBufferAccess(mAllocator, format, + mSize, mBackendType, + mBaseTexFlags); + } + +public: + already_AddRefed<TextureClient> CreateB8G8R8AX8() { + gfx::SurfaceFormat format = mHasAlpha ? gfx::SurfaceFormat::B8G8R8A8 + : gfx::SurfaceFormat::B8G8R8X8; + return Create(format); + } + + already_AddRefed<TextureClient> CreateR8G8B8AX8() { + RefPtr<TextureClient> ret; + + bool areRGBAFormatsBroken = mLayersBackend == LayersBackend::LAYERS_BASIC; + if (!areRGBAFormatsBroken) { + gfx::SurfaceFormat format = mHasAlpha ? gfx::SurfaceFormat::R8G8B8A8 + : gfx::SurfaceFormat::R8G8B8X8; + ret = Create(format); + } + + if (!ret) { + ret = CreateB8G8R8AX8(); + if (ret) { + ret->AddFlags(TextureFlags::RB_SWAPPED); + } + } + + return ret.forget(); + } +}; + +static already_AddRefed<TextureClient> +TexClientFromReadback(SharedSurface* src, CompositableForwarder* allocator, + TextureFlags baseFlags, LayersBackend layersBackend) +{ + auto backendType = gfx::BackendType::CAIRO; + TexClientFactory factory(allocator, src->mHasAlpha, src->mSize, backendType, + baseFlags, layersBackend); + + RefPtr<TextureClient> texClient; + + { + gl::ScopedReadbackFB autoReadback(src); + + // We have a source FB, now we need a format. + GLenum destFormat = LOCAL_GL_BGRA; + GLenum destType = LOCAL_GL_UNSIGNED_BYTE; + GLenum readFormat; + GLenum readType; + + // We actually don't care if they match, since we can handle + // any read{Format,Type} we get. + auto gl = src->mGL; + GetActualReadFormats(gl, destFormat, destType, &readFormat, &readType); + + MOZ_ASSERT(readFormat == LOCAL_GL_RGBA || + readFormat == LOCAL_GL_BGRA); + MOZ_ASSERT(readType == LOCAL_GL_UNSIGNED_BYTE); + + // With a format and type, we can create texClient. + if (readFormat == LOCAL_GL_BGRA && + readType == LOCAL_GL_UNSIGNED_BYTE) + { + // 0xAARRGGBB + // In Lendian: [BB, GG, RR, AA] + texClient = factory.CreateB8G8R8AX8(); + + } else if (readFormat == LOCAL_GL_RGBA && + readType == LOCAL_GL_UNSIGNED_BYTE) + { + // [RR, GG, BB, AA] + texClient = factory.CreateR8G8B8AX8(); + } else { + MOZ_CRASH("GFX: Bad `read{Format,Type}`."); + } + + MOZ_ASSERT(texClient); + if (!texClient) + return nullptr; + + // With a texClient, we can lock for writing. + TextureClientAutoLock autoLock(texClient, OpenMode::OPEN_WRITE); + DebugOnly<bool> succeeded = autoLock.Succeeded(); + MOZ_ASSERT(succeeded, "texture should have locked"); + + MappedTextureData mapped; + texClient->BorrowMappedData(mapped); + + // ReadPixels from the current FB into mapped.data. + auto width = src->mSize.width; + auto height = src->mSize.height; + + { + ScopedPackState scopedPackState(gl); + + MOZ_ASSERT(mapped.stride/4 == mapped.size.width); + gl->raw_fReadPixels(0, 0, width, height, readFormat, readType, mapped.data); + } + + // RB_SWAPPED doesn't work with D3D11. (bug 1051010) + // RB_SWAPPED doesn't work with Basic. (bug ???????) + // RB_SWAPPED doesn't work with D3D9. (bug ???????) + bool layersNeedsManualSwap = layersBackend == LayersBackend::LAYERS_BASIC || + layersBackend == LayersBackend::LAYERS_D3D9 || + layersBackend == LayersBackend::LAYERS_D3D11; + if (texClient->HasFlags(TextureFlags::RB_SWAPPED) && + layersNeedsManualSwap) + { + size_t pixels = width * height; + uint8_t* itr = mapped.data; + for (size_t i = 0; i < pixels; i++) { + SwapRB_R8G8B8A8(itr); + itr += 4; + } + + texClient->RemoveFlags(TextureFlags::RB_SWAPPED); + } + } + + return texClient.forget(); +} + +//////////////////////////////////////// + +static already_AddRefed<SharedSurfaceTextureClient> +CloneSurface(gl::SharedSurface* src, gl::SurfaceFactory* factory) +{ + RefPtr<SharedSurfaceTextureClient> dest = factory->NewTexClient(src->mSize); + if (!dest) { + return nullptr; + } + + gl::SharedSurface* destSurf = dest->Surf(); + + destSurf->ProducerAcquire(); + SharedSurface::ProdCopy(src, dest->Surf(), factory); + destSurf->ProducerRelease(); + + return dest.forget(); +} + +void +CanvasClientSharedSurface::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) +{ + Renderer renderer; + renderer.construct<ClientCanvasLayer*>(aLayer); + UpdateRenderer(aSize, renderer); +} + +void +CanvasClientSharedSurface::UpdateAsync(AsyncCanvasRenderer* aRenderer) +{ + Renderer renderer; + renderer.construct<AsyncCanvasRenderer*>(aRenderer); + UpdateRenderer(aRenderer->GetSize(), renderer); +} + +void +CanvasClientSharedSurface::UpdateRenderer(gfx::IntSize aSize, Renderer& aRenderer) +{ + GLContext* gl = nullptr; + ClientCanvasLayer* layer = nullptr; + AsyncCanvasRenderer* asyncRenderer = nullptr; + if (aRenderer.constructed<ClientCanvasLayer*>()) { + layer = aRenderer.ref<ClientCanvasLayer*>(); + gl = layer->mGLContext; + } else { + asyncRenderer = aRenderer.ref<AsyncCanvasRenderer*>(); + gl = asyncRenderer->mGLContext; + } + gl->MakeCurrent(); + + RefPtr<TextureClient> newFront; + + if (layer && layer->mGLFrontbuffer) { + mShSurfClient = CloneSurface(layer->mGLFrontbuffer.get(), layer->mFactory.get()); + if (!mShSurfClient) { + gfxCriticalError() << "Invalid canvas front buffer"; + return; + } + } else if (layer && layer->mIsMirror) { + mShSurfClient = CloneSurface(gl->Screen()->Front()->Surf(), layer->mFactory.get()); + if (!mShSurfClient) { + return; + } + } else { + mShSurfClient = gl->Screen()->Front(); + if (mShSurfClient && mShSurfClient->GetAllocator() && + mShSurfClient->GetAllocator() != GetForwarder()->GetTextureForwarder()) { + mShSurfClient = CloneSurface(mShSurfClient->Surf(), gl->Screen()->Factory()); + } + if (!mShSurfClient) { + return; + } + } + MOZ_ASSERT(mShSurfClient); + + newFront = mShSurfClient; + + SharedSurface* surf = mShSurfClient->Surf(); + + // Readback if needed. + mReadbackClient = nullptr; + + auto forwarder = GetForwarder(); + + bool needsReadback = (surf->mType == SharedSurfaceType::Basic); + if (needsReadback) { + TextureFlags flags = TextureFlags::IMMUTABLE; + + CompositableForwarder* shadowForwarder = nullptr; + if (layer) { + flags |= layer->Flags(); + shadowForwarder = layer->ClientManager()->AsShadowForwarder(); + } else { + MOZ_ASSERT(asyncRenderer); + flags |= mTextureFlags; + shadowForwarder = GetForwarder(); + } + + auto layersBackend = shadowForwarder->GetCompositorBackendType(); + mReadbackClient = TexClientFromReadback(surf, forwarder, flags, layersBackend); + + newFront = mReadbackClient; + } else { + mReadbackClient = nullptr; + } + + if (asyncRenderer) { + // If surface type is Basic, above codes will readback + // the GLContext to mReadbackClient in order to send frame to + // compositor. We copy from this TextureClient directly by + // calling CopyFromTextureClient(). + // Therefore, if main-thread want the content of GLContext, + // it doesn't have to readback from GLContext again. + // + // Otherwise, if surface type isn't Basic, we will read from + // SharedSurface directly from main-thread. We still pass + // mReadbackClient which is nullptr here to tell + // AsyncCanvasRenderer reset some properties. + asyncRenderer->CopyFromTextureClient(mReadbackClient); + } + + MOZ_ASSERT(newFront); + if (!newFront) { + // May happen in a release build in case of memory pressure. + gfxCriticalError() << "Failed to allocate a TextureClient for SharedSurface Canvas. Size: " << aSize; + return; + } + + mNewFront = newFront; +} + +void +CanvasClientSharedSurface::Updated() +{ + if (!mNewFront) { + return; + } + + auto forwarder = GetForwarder(); + + mFront = mNewFront; + mNewFront = nullptr; + + // Add the new TexClient. + MOZ_ALWAYS_TRUE( AddTextureClient(mFront) ); + + AutoTArray<CompositableForwarder::TimedTextureClient,1> textures; + CompositableForwarder::TimedTextureClient* t = textures.AppendElement(); + t->mTextureClient = mFront; + t->mPictureRect = nsIntRect(nsIntPoint(0, 0), mFront->GetSize()); + t->mFrameID = mFrameID; + forwarder->UseTextures(this, textures); +} + +void +CanvasClientSharedSurface::OnDetach() { + ClearSurfaces(); +} + +void +CanvasClientSharedSurface::ClearSurfaces() +{ + if (mFront) { + mFront->CancelWaitForRecycle(); + } + mFront = nullptr; + mNewFront = nullptr; + mShSurfClient = nullptr; + mReadbackClient = nullptr; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/CanvasClient.h b/gfx/layers/client/CanvasClient.h new file mode 100644 index 000000000..cd88d02ab --- /dev/null +++ b/gfx/layers/client/CanvasClient.h @@ -0,0 +1,216 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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" + +// Fix X11 header brain damage that conflicts with MaybeOneOf::None +#undef None +#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 AsyncCanvasRenderer; +class ClientCanvasLayer; +class CompositableForwarder; +class ShadowableLayer; +class SharedSurfaceTextureClient; + +/** + * Compositable client for 2d and webgl canvas. + */ +class CanvasClient : public CompositableClient +{ +public: + typedef MaybeOneOf<ClientCanvasLayer*, AsyncCanvasRenderer*> Renderer; + + /** + * Creates, configures, and returns a new canvas client. If necessary, a + * message will be sent to the compositor to create a corresponding image + * host. + */ + enum CanvasClientType { + CanvasClientSurface, + CanvasClientGLContext, + CanvasClientTypeShSurf, + CanvasClientAsync, // webgl on workers + }; + static already_AddRefed<CanvasClient> CreateCanvasClient(CanvasClientType aType, + CompositableForwarder* aFwd, + TextureFlags aFlags); + + CanvasClient(CompositableForwarder* aFwd, TextureFlags aFlags) + : CompositableClient(aFwd, aFlags) + , mFrameID(0) + { + mTextureFlags = aFlags; + } + + virtual ~CanvasClient() {} + + virtual void Clear() {}; + + virtual void Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) = 0; + + virtual bool AddTextureClient(TextureClient* aTexture) override + { + ++mFrameID; + return CompositableClient::AddTextureClient(aTexture); + } + + virtual void UpdateAsync(AsyncCanvasRenderer* aRenderer) {} + + virtual void UpdateFromTexture(TextureClient* aTexture) {} + + virtual void Updated() { } + +protected: + int32_t mFrameID; +}; + +// Used for 2D canvases and WebGL canvas on non-GL systems where readback is requried. +class CanvasClient2D : public CanvasClient +{ +public: + CanvasClient2D(CompositableForwarder* aLayerForwarder, + TextureFlags aFlags) + : CanvasClient(aLayerForwarder, aFlags) + { + } + + TextureInfo GetTextureInfo() const override + { + return TextureInfo(CompositableType::IMAGE, mTextureFlags); + } + + virtual void Clear() override + { + mBackBuffer = mFrontBuffer = nullptr; + } + + virtual void Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) override; + + virtual void UpdateFromTexture(TextureClient* aBuffer) override; + + virtual bool AddTextureClient(TextureClient* aTexture) override + { + return CanvasClient::AddTextureClient(aTexture); + } + + virtual void OnDetach() override + { + mBackBuffer = mFrontBuffer = nullptr; + } + +private: + already_AddRefed<TextureClient> + CreateTextureClientForCanvas(gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, + TextureFlags aFlags, + ClientCanvasLayer* aLayer); + + RefPtr<TextureClient> mBackBuffer; + RefPtr<TextureClient> mFrontBuffer; + // We store this texture separately to make sure it is not written into + // in Update() if for some silly reason we end up alternating between + // UpdateFromTexture and Update. + // This code is begging for a cleanup. The situation described above should + // not be made possible. + RefPtr<TextureClient> mBufferProviderTexture; +}; + +// Used for GL canvases where we don't need to do any readback, i.e., with a +// GL backend. +class CanvasClientSharedSurface : public CanvasClient +{ +private: + RefPtr<SharedSurfaceTextureClient> mShSurfClient; + RefPtr<TextureClient> mReadbackClient; + RefPtr<TextureClient> mFront; + RefPtr<TextureClient> mNewFront; + + void ClearSurfaces(); + +public: + CanvasClientSharedSurface(CompositableForwarder* aLayerForwarder, + TextureFlags aFlags); + + ~CanvasClientSharedSurface(); + + virtual TextureInfo GetTextureInfo() const override { + return TextureInfo(CompositableType::IMAGE); + } + + virtual void Clear() override { + ClearSurfaces(); + } + + virtual void Update(gfx::IntSize aSize, + ClientCanvasLayer* aLayer) override; + void UpdateRenderer(gfx::IntSize aSize, Renderer& aRenderer); + + virtual void UpdateAsync(AsyncCanvasRenderer* aRenderer) override; + + virtual void Updated() override; + + virtual void OnDetach() override; +}; + +/** + * Used for OMT<canvas> uploads using the image bridge protocol. + * Actual CanvasClient is on the ImageBridgeChild thread, so we + * only forward its AsyncID here + */ +class CanvasClientBridge final : public CanvasClient +{ +public: + CanvasClientBridge(CompositableForwarder* aLayerForwarder, + TextureFlags aFlags) + : CanvasClient(aLayerForwarder, aFlags) + , mAsyncID(0) + , mLayer(nullptr) + { + } + + TextureInfo GetTextureInfo() const override + { + return TextureInfo(CompositableType::IMAGE); + } + + virtual void Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) override + { + } + + virtual void UpdateAsync(AsyncCanvasRenderer* aRenderer) override; + + void SetLayer(ShadowableLayer* aLayer) + { + mLayer = aLayer; + } + +protected: + uint64_t mAsyncID; + ShadowableLayer* mLayer; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/ClientCanvasLayer.cpp b/gfx/layers/client/ClientCanvasLayer.cpp new file mode 100644 index 000000000..32acf1eb2 --- /dev/null +++ b/gfx/layers/client/ClientCanvasLayer.cpp @@ -0,0 +1,261 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 "ClientCanvasLayer.h" +#include "GLContext.h" // for GLContext +#include "GLScreenBuffer.h" // for GLScreenBuffer +#include "GeckoProfiler.h" // for PROFILER_LABEL +#include "SharedSurfaceEGL.h" // for SurfaceFactory_EGLImage +#include "SharedSurfaceGL.h" // for SurfaceFactory_GLTexture, etc +#include "ClientLayerManager.h" // for ClientLayerManager, etc +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/layers/AsyncCanvasRenderer.h" +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/LayersTypes.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsXULAppAPI.h" // for XRE_GetProcessType, etc +#include "gfxPrefs.h" // for WebGLForceLayersReadback + +using namespace mozilla::gfx; +using namespace mozilla::gl; + +namespace mozilla { +namespace layers { + +ClientCanvasLayer::~ClientCanvasLayer() +{ + MOZ_COUNT_DTOR(ClientCanvasLayer); + if (mCanvasClient) { + mCanvasClient->OnDetach(); + mCanvasClient = nullptr; + } +} + +void +ClientCanvasLayer::Initialize(const Data& aData) +{ + CopyableCanvasLayer::Initialize(aData); + + mCanvasClient = nullptr; + + if (!mGLContext) + return; + + GLScreenBuffer* screen = mGLContext->Screen(); + + SurfaceCaps caps; + if (mGLFrontbuffer) { + // The screen caps are irrelevant if we're using a separate frontbuffer. + caps = mGLFrontbuffer->mHasAlpha ? SurfaceCaps::ForRGBA() + : SurfaceCaps::ForRGB(); + } else { + MOZ_ASSERT(screen); + caps = screen->mCaps; + } + MOZ_ASSERT(caps.alpha == aData.mHasAlpha); + + auto forwarder = ClientManager()->AsShadowForwarder(); + + mFlags = TextureFlags::ORIGIN_BOTTOM_LEFT; + if (!aData.mIsGLAlphaPremult) { + mFlags |= TextureFlags::NON_PREMULTIPLIED; + } + + UniquePtr<SurfaceFactory> factory = GLScreenBuffer::CreateFactory(mGLContext, caps, forwarder, mFlags); + + if (mGLFrontbuffer || aData.mIsMirror) { + // We're using a source other than the one in the default screen. + // (SkiaGL) + mFactory = Move(factory); + if (!mFactory) { + // Absolutely must have a factory here, so create a basic one + mFactory = MakeUnique<SurfaceFactory_Basic>(mGLContext, caps, mFlags); + } + } else { + if (factory) + screen->Morph(Move(factory)); + } +} + +void +ClientCanvasLayer::RenderLayer() +{ + PROFILER_LABEL("ClientCanvasLayer", "RenderLayer", + js::ProfileEntry::Category::GRAPHICS); + + RenderMaskLayers(this); + + if (!mCanvasClient) { + TextureFlags flags = TextureFlags::DEFAULT; + if (mOriginPos == gl::OriginPos::BottomLeft) { + flags |= TextureFlags::ORIGIN_BOTTOM_LEFT; + } + + if (!mIsAlphaPremultiplied) { + flags |= TextureFlags::NON_PREMULTIPLIED; + } + + mCanvasClient = CanvasClient::CreateCanvasClient(GetCanvasClientType(), + ClientManager()->AsShadowForwarder(), + flags); + if (!mCanvasClient) { + return; + } + if (HasShadow()) { + if (mAsyncRenderer) { + static_cast<CanvasClientBridge*>(mCanvasClient.get())->SetLayer(this); + } else { + mCanvasClient->Connect(); + ClientManager()->AsShadowForwarder()->Attach(mCanvasClient, this); + } + } + } + + if (mCanvasClient && mAsyncRenderer) { + mCanvasClient->UpdateAsync(mAsyncRenderer); + } + + if (!IsDirty()) { + return; + } + Painted(); + + FirePreTransactionCallback(); + if (mBufferProvider && mBufferProvider->GetTextureClient()) { + if (!mBufferProvider->SetForwarder(ClientManager()->AsShadowForwarder())) { + gfxCriticalNote << "BufferProvider::SetForwarder failed"; + return; + } + mCanvasClient->UpdateFromTexture(mBufferProvider->GetTextureClient()); + } else { + mCanvasClient->Update(gfx::IntSize(mBounds.width, mBounds.height), this); + } + + FireDidTransactionCallback(); + + ClientManager()->Hold(this); + mCanvasClient->Updated(); +} + +bool +ClientCanvasLayer::UpdateTarget(DrawTarget* aDestTarget) +{ + MOZ_ASSERT(aDestTarget); + if (!aDestTarget) { + return false; + } + + RefPtr<SourceSurface> surface; + + if (!mGLContext) { + AutoReturnSnapshot autoReturn; + + if (mAsyncRenderer) { + surface = mAsyncRenderer->GetSurface(); + } else if (mBufferProvider) { + surface = mBufferProvider->BorrowSnapshot(); + autoReturn.mSnapshot = &surface; + autoReturn.mBufferProvider = mBufferProvider; + } + + MOZ_ASSERT(surface); + if (!surface) { + return false; + } + + aDestTarget->CopySurface(surface, + IntRect(0, 0, mBounds.width, mBounds.height), + IntPoint(0, 0)); + return true; + } + + SharedSurface* frontbuffer = nullptr; + if (mGLFrontbuffer) { + frontbuffer = mGLFrontbuffer.get(); + } else { + GLScreenBuffer* screen = mGLContext->Screen(); + const auto& front = screen->Front(); + if (front) { + frontbuffer = front->Surf(); + } + } + + if (!frontbuffer) { + NS_WARNING("Null frame received."); + return false; + } + + IntSize readSize(frontbuffer->mSize); + SurfaceFormat format = (GetContentFlags() & CONTENT_OPAQUE) + ? SurfaceFormat::B8G8R8X8 + : SurfaceFormat::B8G8R8A8; + bool needsPremult = frontbuffer->mHasAlpha && !mIsAlphaPremultiplied; + + // Try to read back directly into aDestTarget's output buffer + uint8_t* destData; + IntSize destSize; + int32_t destStride; + SurfaceFormat destFormat; + if (aDestTarget->LockBits(&destData, &destSize, &destStride, &destFormat)) { + if (destSize == readSize && destFormat == format) { + RefPtr<DataSourceSurface> data = + Factory::CreateWrappingDataSourceSurface(destData, destStride, destSize, destFormat); + mGLContext->Readback(frontbuffer, data); + if (needsPremult) { + gfxUtils::PremultiplyDataSurface(data, data); + } + aDestTarget->ReleaseBits(destData); + return true; + } + aDestTarget->ReleaseBits(destData); + } + + RefPtr<DataSourceSurface> resultSurf = GetTempSurface(readSize, format); + // There will already be a warning from inside of GetTempSurface, but + // it doesn't hurt to complain: + if (NS_WARN_IF(!resultSurf)) { + return false; + } + + // Readback handles Flush/MarkDirty. + mGLContext->Readback(frontbuffer, resultSurf); + if (needsPremult) { + gfxUtils::PremultiplyDataSurface(resultSurf, resultSurf); + } + + aDestTarget->CopySurface(resultSurf, + IntRect(0, 0, readSize.width, readSize.height), + IntPoint(0, 0)); + + return true; +} + +CanvasClient::CanvasClientType +ClientCanvasLayer::GetCanvasClientType() +{ + if (mAsyncRenderer) { + return CanvasClient::CanvasClientAsync; + } + + if (mGLContext) { + return CanvasClient::CanvasClientTypeShSurf; + } + return CanvasClient::CanvasClientSurface; +} + +already_AddRefed<CanvasLayer> +ClientLayerManager::CreateCanvasLayer() +{ + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr<ClientCanvasLayer> layer = + new ClientCanvasLayer(this); + CREATE_SHADOW(Canvas); + return layer.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ClientCanvasLayer.h b/gfx/layers/client/ClientCanvasLayer.h new file mode 100644 index 000000000..9a655727c --- /dev/null +++ b/gfx/layers/client/ClientCanvasLayer.h @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 GFX_CLIENTCANVASLAYER_H +#define GFX_CLIENTCANVASLAYER_H + +#include "CanvasClient.h" // for CanvasClient, etc +#include "ClientLayerManager.h" // for ClientLayerManager, etc +#include "CopyableCanvasLayer.h" // for CopyableCanvasLayer +#include "Layers.h" // for CanvasLayer, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/layers/LayersMessages.h" // for CanvasLayerAttributes, etc +#include "mozilla/mozalloc.h" // for operator delete +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsRegion.h" // for nsIntRegion + +namespace mozilla { +namespace gl { +class SurfaceFactory; +} // namespace gl + +namespace layers { + +class CompositableClient; +class ShadowableLayer; + +class ClientCanvasLayer : public CopyableCanvasLayer, + public ClientLayer +{ + typedef CanvasClient::CanvasClientType CanvasClientType; +public: + explicit ClientCanvasLayer(ClientLayerManager* aLayerManager) : + CopyableCanvasLayer(aLayerManager, static_cast<ClientLayer*>(this)) + { + MOZ_COUNT_CTOR(ClientCanvasLayer); + } + +protected: + virtual ~ClientCanvasLayer(); + +public: + virtual void SetVisibleRegion(const LayerIntRegion& aRegion) override + { + NS_ASSERTION(ClientManager()->InConstruction(), + "Can only set properties in construction phase"); + CanvasLayer::SetVisibleRegion(aRegion); + } + + virtual void Initialize(const Data& aData) override; + + virtual void RenderLayer() override; + + virtual void ClearCachedResources() override + { + if (mCanvasClient) { + mCanvasClient->Clear(); + } + } + + virtual void HandleMemoryPressure() override + { + if (mCanvasClient) { + mCanvasClient->HandleMemoryPressure(); + } + } + + virtual void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) override + { + aAttrs = CanvasLayerAttributes(mSamplingFilter, mBounds); + } + + virtual Layer* AsLayer() override { return this; } + virtual ShadowableLayer* AsShadowableLayer() override { return this; } + + virtual void Disconnect() override + { + mCanvasClient = nullptr; + ClientLayer::Disconnect(); + } + + virtual CompositableClient* GetCompositableClient() override + { + return mCanvasClient; + } + + const TextureFlags& Flags() const { return mFlags; } + +protected: + + bool UpdateTarget(gfx::DrawTarget* aDestTarget = nullptr); + + ClientLayerManager* ClientManager() + { + return static_cast<ClientLayerManager*>(mManager); + } + + CanvasClientType GetCanvasClientType(); + + RefPtr<CanvasClient> mCanvasClient; + + UniquePtr<gl::SurfaceFactory> mFactory; + + TextureFlags mFlags; + + friend class CanvasClient2D; + friend class CanvasClientSharedSurface; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/ClientColorLayer.cpp b/gfx/layers/client/ClientColorLayer.cpp new file mode 100644 index 000000000..aa086f35a --- /dev/null +++ b/gfx/layers/client/ClientColorLayer.cpp @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 "ClientLayerManager.h" // for ClientLayerManager, etc +#include "Layers.h" // for ColorLayer, etc +#include "mozilla/layers/LayersMessages.h" // for ColorLayerAttributes, etc +#include "mozilla/mozalloc.h" // for operator new +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "nsRegion.h" // for nsIntRegion + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +class ClientColorLayer : public ColorLayer, + public ClientLayer { +public: + explicit ClientColorLayer(ClientLayerManager* aLayerManager) : + ColorLayer(aLayerManager, static_cast<ClientLayer*>(this)) + { + MOZ_COUNT_CTOR(ClientColorLayer); + } + +protected: + virtual ~ClientColorLayer() + { + MOZ_COUNT_DTOR(ClientColorLayer); + } + +public: + virtual void SetVisibleRegion(const LayerIntRegion& aRegion) + { + NS_ASSERTION(ClientManager()->InConstruction(), + "Can only set properties in construction phase"); + ColorLayer::SetVisibleRegion(aRegion); + } + + virtual void RenderLayer() + { + RenderMaskLayers(this); + } + + virtual void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) + { + aAttrs = ColorLayerAttributes(GetColor(), GetBounds()); + } + + virtual Layer* AsLayer() { return this; } + virtual ShadowableLayer* AsShadowableLayer() { return this; } + + virtual void Disconnect() + { + ClientLayer::Disconnect(); + } + +protected: + ClientLayerManager* ClientManager() + { + return static_cast<ClientLayerManager*>(mManager); + } +}; + +already_AddRefed<ColorLayer> +ClientLayerManager::CreateColorLayer() +{ + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr<ClientColorLayer> layer = + new ClientColorLayer(this); + CREATE_SHADOW(Color); + return layer.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ClientContainerLayer.cpp b/gfx/layers/client/ClientContainerLayer.cpp new file mode 100644 index 000000000..f72854af9 --- /dev/null +++ b/gfx/layers/client/ClientContainerLayer.cpp @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 "ClientContainerLayer.h" +#include "ClientLayerManager.h" // for ClientLayerManager, etc +#include "mozilla/mozalloc.h" // for operator new +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsISupportsImpl.h" // for Layer::AddRef, etc + +namespace mozilla { +namespace layers { + +already_AddRefed<ContainerLayer> +ClientLayerManager::CreateContainerLayer() +{ + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr<ClientContainerLayer> layer = + new ClientContainerLayer(this); + CREATE_SHADOW(Container); + return layer.forget(); +} + +already_AddRefed<RefLayer> +ClientLayerManager::CreateRefLayer() +{ + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr<ClientRefLayer> layer = + new ClientRefLayer(this); + CREATE_SHADOW(Ref); + return layer.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ClientContainerLayer.h b/gfx/layers/client/ClientContainerLayer.h new file mode 100644 index 000000000..de10a55b4 --- /dev/null +++ b/gfx/layers/client/ClientContainerLayer.h @@ -0,0 +1,189 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 GFX_CLIENTCONTAINERLAYER_H +#define GFX_CLIENTCONTAINERLAYER_H + +#include <stdint.h> // for uint32_t +#include "ClientLayerManager.h" // for ClientLayerManager, etc +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsISupportsUtils.h" // for NS_ADDREF, NS_RELEASE +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for AutoTArray +#include "ReadbackProcessor.h" +#include "ClientPaintedLayer.h" + +namespace mozilla { +namespace layers { + +class ShadowableLayer; + +class ClientContainerLayer : public ContainerLayer, + public ClientLayer +{ +public: + explicit ClientContainerLayer(ClientLayerManager* aManager) : + ContainerLayer(aManager, static_cast<ClientLayer*>(this)) + { + MOZ_COUNT_CTOR(ClientContainerLayer); + mSupportsComponentAlphaChildren = true; + } + +protected: + virtual ~ClientContainerLayer() + { + while (mFirstChild) { + ContainerLayer::RemoveChild(mFirstChild); + } + + MOZ_COUNT_DTOR(ClientContainerLayer); + } + +public: + virtual void RenderLayer() override + { + RenderMaskLayers(this); + + DefaultComputeSupportsComponentAlphaChildren(); + + AutoTArray<Layer*, 12> children; + SortChildrenBy3DZOrder(children); + + ReadbackProcessor readback; + readback.BuildUpdates(this); + + for (uint32_t i = 0; i < children.Length(); i++) { + Layer* child = children.ElementAt(i); + + ToClientLayer(child)->RenderLayerWithReadback(&readback); + + if (!ClientManager()->GetRepeatTransaction() && + !child->GetInvalidRegion().IsEmpty()) { + child->Mutated(); + } + } + } + + virtual void SetVisibleRegion(const LayerIntRegion& aRegion) override + { + NS_ASSERTION(ClientManager()->InConstruction(), + "Can only set properties in construction phase"); + ContainerLayer::SetVisibleRegion(aRegion); + } + virtual bool InsertAfter(Layer* aChild, Layer* aAfter) override + { + if(!ClientManager()->InConstruction()) { + NS_ERROR("Can only set properties in construction phase"); + return false; + } + + if (!ContainerLayer::InsertAfter(aChild, aAfter)) { + return false; + } + + ClientManager()->AsShadowForwarder()->InsertAfter(ClientManager()->Hold(this), + ClientManager()->Hold(aChild), + aAfter ? ClientManager()->Hold(aAfter) : nullptr); + return true; + } + + virtual bool RemoveChild(Layer* aChild) override + { + if (!ClientManager()->InConstruction()) { + NS_ERROR("Can only set properties in construction phase"); + return false; + } + // hold on to aChild before we remove it! + ShadowableLayer *heldChild = ClientManager()->Hold(aChild); + if (!ContainerLayer::RemoveChild(aChild)) { + return false; + } + ClientManager()->AsShadowForwarder()->RemoveChild(ClientManager()->Hold(this), heldChild); + return true; + } + + virtual bool RepositionChild(Layer* aChild, Layer* aAfter) override + { + if (!ClientManager()->InConstruction()) { + NS_ERROR("Can only set properties in construction phase"); + return false; + } + if (!ContainerLayer::RepositionChild(aChild, aAfter)) { + return false; + } + ClientManager()->AsShadowForwarder()->RepositionChild(ClientManager()->Hold(this), + ClientManager()->Hold(aChild), + aAfter ? ClientManager()->Hold(aAfter) : nullptr); + return true; + } + + virtual Layer* AsLayer() override { return this; } + virtual ShadowableLayer* AsShadowableLayer() override { return this; } + + virtual void ComputeEffectiveTransforms(const gfx::Matrix4x4& aTransformToSurface) override + { + DefaultComputeEffectiveTransforms(aTransformToSurface); + } + + void ForceIntermediateSurface() { mUseIntermediateSurface = true; } + + void SetSupportsComponentAlphaChildren(bool aSupports) { mSupportsComponentAlphaChildren = aSupports; } + + virtual void Disconnect() override + { + ClientLayer::Disconnect(); + } + +protected: + ClientLayerManager* ClientManager() + { + return static_cast<ClientLayerManager*>(mManager); + } +}; + +class ClientRefLayer : public RefLayer, + public ClientLayer { +public: + explicit ClientRefLayer(ClientLayerManager* aManager) : + RefLayer(aManager, static_cast<ClientLayer*>(this)) + { + MOZ_COUNT_CTOR(ClientRefLayer); + } + +protected: + virtual ~ClientRefLayer() + { + MOZ_COUNT_DTOR(ClientRefLayer); + } + +public: + virtual Layer* AsLayer() { return this; } + virtual ShadowableLayer* AsShadowableLayer() { return this; } + + virtual void Disconnect() + { + ClientLayer::Disconnect(); + } + + virtual void RenderLayer() { } + + virtual void ComputeEffectiveTransforms(const gfx::Matrix4x4& aTransformToSurface) + { + DefaultComputeEffectiveTransforms(aTransformToSurface); + } + +private: + ClientLayerManager* ClientManager() + { + return static_cast<ClientLayerManager*>(mManager); + } +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/ClientImageLayer.cpp b/gfx/layers/client/ClientImageLayer.cpp new file mode 100644 index 000000000..8703f77a5 --- /dev/null +++ b/gfx/layers/client/ClientImageLayer.cpp @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 "ClientLayerManager.h" // for ClientLayerManager, etc +#include "ImageContainer.h" // for AutoLockImage, etc +#include "ImageLayers.h" // for ImageLayer +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/ImageClient.h" // for ImageClient, etc +#include "mozilla/layers/LayersMessages.h" // for ImageLayerAttributes, etc +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "nsRegion.h" // for nsIntRegion + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +class ClientImageLayer : public ImageLayer, + public ClientLayer { +public: + explicit ClientImageLayer(ClientLayerManager* aLayerManager) + : ImageLayer(aLayerManager, static_cast<ClientLayer*>(this)) + , mImageClientTypeContainer(CompositableType::UNKNOWN) + { + MOZ_COUNT_CTOR(ClientImageLayer); + } + +protected: + virtual ~ClientImageLayer() + { + DestroyBackBuffer(); + MOZ_COUNT_DTOR(ClientImageLayer); + } + + virtual void SetContainer(ImageContainer* aContainer) override + { + ImageLayer::SetContainer(aContainer); + mImageClientTypeContainer = CompositableType::UNKNOWN; + } + + virtual void SetVisibleRegion(const LayerIntRegion& aRegion) override + { + NS_ASSERTION(ClientManager()->InConstruction(), + "Can only set properties in construction phase"); + ImageLayer::SetVisibleRegion(aRegion); + } + + virtual void RenderLayer() override; + + virtual void ClearCachedResources() override + { + DestroyBackBuffer(); + } + + virtual void HandleMemoryPressure() override + { + if (mImageClient) { + mImageClient->HandleMemoryPressure(); + } + } + + virtual void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) override + { + aAttrs = ImageLayerAttributes(mSamplingFilter, mScaleToSize, mScaleMode); + } + + virtual Layer* AsLayer() override { return this; } + virtual ShadowableLayer* AsShadowableLayer() override { return this; } + + virtual void Disconnect() override + { + DestroyBackBuffer(); + ClientLayer::Disconnect(); + } + + void DestroyBackBuffer() + { + if (mImageClient) { + mImageClient->SetLayer(nullptr); + mImageClient->OnDetach(); + mImageClient = nullptr; + } + } + + virtual CompositableClient* GetCompositableClient() override + { + return mImageClient; + } + +protected: + ClientLayerManager* ClientManager() + { + return static_cast<ClientLayerManager*>(mManager); + } + + CompositableType GetImageClientType() + { + if (mImageClientTypeContainer != CompositableType::UNKNOWN) { + return mImageClientTypeContainer; + } + + if (mContainer->IsAsync()) { + mImageClientTypeContainer = CompositableType::IMAGE_BRIDGE; + return mImageClientTypeContainer; + } + + AutoLockImage autoLock(mContainer); + + mImageClientTypeContainer = autoLock.HasImage() + ? CompositableType::IMAGE : CompositableType::UNKNOWN; + return mImageClientTypeContainer; + } + + RefPtr<ImageClient> mImageClient; + CompositableType mImageClientTypeContainer; +}; + +void +ClientImageLayer::RenderLayer() +{ + RenderMaskLayers(this); + + if (!mContainer) { + return; + } + + if (!mImageClient || + !mImageClient->UpdateImage(mContainer, GetContentFlags())) { + CompositableType type = GetImageClientType(); + if (type == CompositableType::UNKNOWN) { + return; + } + TextureFlags flags = TextureFlags::DEFAULT; + mImageClient = ImageClient::CreateImageClient(type, + ClientManager()->AsShadowForwarder(), + flags); + if (!mImageClient) { + return; + } + mImageClient->SetLayer(this); + if (HasShadow() && !mContainer->IsAsync()) { + mImageClient->Connect(); + ClientManager()->AsShadowForwarder()->Attach(mImageClient, this); + } + if (!mImageClient->UpdateImage(mContainer, GetContentFlags())) { + return; + } + } + ClientManager()->Hold(this); +} + +already_AddRefed<ImageLayer> +ClientLayerManager::CreateImageLayer() +{ + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr<ClientImageLayer> layer = + new ClientImageLayer(this); + CREATE_SHADOW(Image); + return layer.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ClientLayerManager.cpp b/gfx/layers/client/ClientLayerManager.cpp new file mode 100644 index 000000000..074807e8c --- /dev/null +++ b/gfx/layers/client/ClientLayerManager.cpp @@ -0,0 +1,907 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 "ClientLayerManager.h" +#include "GeckoProfiler.h" // for PROFILER_LABEL +#include "gfxPrefs.h" // for gfxPrefs::LayersTile... +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Hal.h" +#include "mozilla/dom/ScreenOrientation.h" // for ScreenOrientation +#include "mozilla/dom/TabChild.h" // for TabChild +#include "mozilla/hal_sandbox/PHal.h" // for ScreenConfiguration +#include "mozilla/layers/CompositableClient.h" +#include "mozilla/layers/CompositorBridgeChild.h" // for CompositorBridgeChild +#include "mozilla/layers/ContentClient.h" +#include "mozilla/layers/FrameUniformityData.h" +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/LayersMessages.h" // for EditReply, etc +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/PLayerChild.h" // for PLayerChild +#include "mozilla/layers/LayerTransactionChild.h" +#include "mozilla/layers/ShadowLayerChild.h" +#include "mozilla/layers/PersistentBufferProvider.h" +#include "ClientReadbackLayer.h" // for ClientReadbackLayer +#include "nsAString.h" +#include "nsDisplayList.h" +#include "nsIWidgetListener.h" +#include "nsTArray.h" // for AutoTArray +#include "nsXULAppAPI.h" // for XRE_GetProcessType, etc +#include "TiledLayerBuffer.h" +#include "mozilla/dom/WindowBinding.h" // for Overfill Callback +#include "FrameLayerBuilder.h" // for FrameLayerbuilder +#ifdef MOZ_WIDGET_ANDROID +#include "AndroidBridge.h" +#include "LayerMetricsWrapper.h" +#endif +#ifdef XP_WIN +#include "mozilla/gfx/DeviceManagerDx.h" +#endif + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +void +ClientLayerManager::MemoryPressureObserver::Destroy() +{ + UnregisterMemoryPressureEvent(); + mClientLayerManager = nullptr; +} + +NS_IMETHODIMP +ClientLayerManager::MemoryPressureObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aSomeData) +{ + if (!mClientLayerManager || strcmp(aTopic, "memory-pressure")) { + return NS_OK; + } + + mClientLayerManager->HandleMemoryPressure(); + return NS_OK; +} + +void +ClientLayerManager::MemoryPressureObserver::RegisterMemoryPressureEvent() +{ + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + MOZ_ASSERT(observerService); + + if (observerService) { + observerService->AddObserver(this, "memory-pressure", false); + } +} + +void +ClientLayerManager::MemoryPressureObserver::UnregisterMemoryPressureEvent() +{ + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + if (observerService) { + observerService->RemoveObserver(this, "memory-pressure"); + } +} + +NS_IMPL_ISUPPORTS(ClientLayerManager::MemoryPressureObserver, nsIObserver) + +ClientLayerManager::ClientLayerManager(nsIWidget* aWidget) + : mPhase(PHASE_NONE) + , mWidget(aWidget) + , mLatestTransactionId(0) + , mLastPaintTime(TimeDuration::Forever()) + , mTargetRotation(ROTATION_0) + , mRepeatTransaction(false) + , mIsRepeatTransaction(false) + , mTransactionIncomplete(false) + , mCompositorMightResample(false) + , mNeedsComposite(false) + , mPaintSequenceNumber(0) + , mForwarder(new ShadowLayerForwarder(this)) + , mDeviceCounter(gfxPlatform::GetPlatform()->GetDeviceCounter()) +{ + MOZ_COUNT_CTOR(ClientLayerManager); + mMemoryPressureObserver = new MemoryPressureObserver(this); +} + + +ClientLayerManager::~ClientLayerManager() +{ + mMemoryPressureObserver->Destroy(); + ClearCachedResources(); + // Stop receiveing AsyncParentMessage at Forwarder. + // After the call, the message is directly handled by LayerTransactionChild. + // Basically this function should be called in ShadowLayerForwarder's + // destructor. But when the destructor is triggered by + // CompositorBridgeChild::Destroy(), the destructor can not handle it correctly. + // See Bug 1000525. + mForwarder->StopReceiveAsyncParentMessge(); + mRoot = nullptr; + + MOZ_COUNT_DTOR(ClientLayerManager); +} + +void +ClientLayerManager::Destroy() +{ + // It's important to call ClearCachedResource before Destroy because the + // former will early-return if the later has already run. + ClearCachedResources(); + LayerManager::Destroy(); + + if (mTransactionIdAllocator) { + // Make sure to notify the refresh driver just in case it's waiting on a + // pending transaction. Do this at the top of the event loop so we don't + // cause a paint to occur during compositor shutdown. + RefPtr<TransactionIdAllocator> allocator = mTransactionIdAllocator; + uint64_t id = mLatestTransactionId; + + RefPtr<Runnable> task = NS_NewRunnableFunction([allocator, id] () -> void { + allocator->NotifyTransactionCompleted(id); + }); + NS_DispatchToMainThread(task.forget()); + } + + // Forget the widget pointer in case we outlive our owning widget. + mWidget = nullptr; +} + +int32_t +ClientLayerManager::GetMaxTextureSize() const +{ + return mForwarder->GetMaxTextureSize(); +} + +void +ClientLayerManager::SetDefaultTargetConfiguration(BufferMode aDoubleBuffering, + ScreenRotation aRotation) +{ + mTargetRotation = aRotation; + } + +void +ClientLayerManager::SetRoot(Layer* aLayer) +{ + if (mRoot != aLayer) { + // Have to hold the old root and its children in order to + // maintain the same view of the layer tree in this process as + // the parent sees. Otherwise layers can be destroyed + // mid-transaction and bad things can happen (v. bug 612573) + if (mRoot) { + Hold(mRoot); + } + mForwarder->SetRoot(Hold(aLayer)); + NS_ASSERTION(aLayer, "Root can't be null"); + NS_ASSERTION(aLayer->Manager() == this, "Wrong manager"); + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + mRoot = aLayer; + } +} + +void +ClientLayerManager::Mutated(Layer* aLayer) +{ + LayerManager::Mutated(aLayer); + + NS_ASSERTION(InConstruction() || InDrawing(), "wrong phase"); + mForwarder->Mutated(Hold(aLayer)); +} + +already_AddRefed<ReadbackLayer> +ClientLayerManager::CreateReadbackLayer() +{ + RefPtr<ReadbackLayer> layer = new ClientReadbackLayer(this); + return layer.forget(); +} + +bool +ClientLayerManager::BeginTransactionWithTarget(gfxContext* aTarget) +{ + MOZ_ASSERT(mForwarder, "ClientLayerManager::BeginTransaction without forwarder"); + if (!mForwarder->IPCOpen()) { + gfxCriticalNote << "ClientLayerManager::BeginTransaction with IPC channel down. GPU process may have died."; + return false; + } + + mInTransaction = true; + mTransactionStart = TimeStamp::Now(); + +#ifdef MOZ_LAYERS_HAVE_LOG + MOZ_LAYERS_LOG(("[----- BeginTransaction")); + Log(); +#endif + + NS_ASSERTION(!InTransaction(), "Nested transactions not allowed"); + mPhase = PHASE_CONSTRUCTION; + + if (DependsOnStaleDevice()) { + FrameLayerBuilder::InvalidateAllLayers(this); + mDeviceCounter = gfxPlatform::GetPlatform()->GetDeviceCounter(); + } + + MOZ_ASSERT(mKeepAlive.IsEmpty(), "uncommitted txn?"); + + // If the last transaction was incomplete (a failed DoEmptyTransaction), + // don't signal a new transaction to ShadowLayerForwarder. Carry on adding + // to the previous transaction. + dom::ScreenOrientationInternal orientation; + if (dom::TabChild* window = mWidget->GetOwningTabChild()) { + orientation = window->GetOrientation(); + } else { + hal::ScreenConfiguration currentConfig; + hal::GetCurrentScreenConfiguration(¤tConfig); + orientation = currentConfig.orientation(); + } + LayoutDeviceIntRect targetBounds = mWidget->GetNaturalBounds(); + targetBounds.x = targetBounds.y = 0; + mForwarder->BeginTransaction(targetBounds.ToUnknownRect(), mTargetRotation, + orientation); + + // If we're drawing on behalf of a context with async pan/zoom + // enabled, then the entire buffer of painted layers might be + // composited (including resampling) asynchronously before we get + // a chance to repaint, so we have to ensure that it's all valid + // and not rotated. + // + // Desktop does not support async zoom yet, so we ignore this for those + // platforms. +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_UIKIT) + if (mWidget && mWidget->GetOwningTabChild()) { + mCompositorMightResample = AsyncPanZoomEnabled(); + } +#endif + + // If we have a non-default target, we need to let our shadow manager draw + // to it. This will happen at the end of the transaction. + if (aTarget && XRE_IsParentProcess()) { + mShadowTarget = aTarget; + } else { + NS_ASSERTION(!aTarget, + "Content-process ClientLayerManager::BeginTransactionWithTarget not supported"); + } + + // If this is a new paint, increment the paint sequence number. + if (!mIsRepeatTransaction) { + // Increment the paint sequence number even if test logging isn't + // enabled in this process; it may be enabled in the parent process, + // and the parent process expects unique sequence numbers. + ++mPaintSequenceNumber; + if (gfxPrefs::APZTestLoggingEnabled()) { + mApzTestData.StartNewPaint(mPaintSequenceNumber); + } + } + return true; +} + +bool +ClientLayerManager::BeginTransaction() +{ + return BeginTransactionWithTarget(nullptr); +} + +bool +ClientLayerManager::EndTransactionInternal(DrawPaintedLayerCallback aCallback, + void* aCallbackData, + EndTransactionFlags) +{ + PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Rasterization); + + PROFILER_LABEL("ClientLayerManager", "EndTransactionInternal", + js::ProfileEntry::Category::GRAPHICS); + +#ifdef MOZ_LAYERS_HAVE_LOG + MOZ_LAYERS_LOG((" ----- (beginning paint)")); + Log(); +#endif + profiler_tracing("Paint", "Rasterize", TRACING_INTERVAL_START); + + NS_ASSERTION(InConstruction(), "Should be in construction phase"); + mPhase = PHASE_DRAWING; + + ClientLayer* root = ClientLayer::ToClientLayer(GetRoot()); + + mTransactionIncomplete = false; + + // Apply pending tree updates before recomputing effective + // properties. + GetRoot()->ApplyPendingUpdatesToSubtree(); + + mPaintedLayerCallback = aCallback; + mPaintedLayerCallbackData = aCallbackData; + + GetRoot()->ComputeEffectiveTransforms(Matrix4x4()); + + // Skip the painting if the device is in device-reset status. + if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) { + if (gfxPrefs::AlwaysPaint() && XRE_IsContentProcess()) { + TimeStamp start = TimeStamp::Now(); + root->RenderLayer(); + mLastPaintTime = TimeStamp::Now() - start; + } else { + root->RenderLayer(); + } + } else { + gfxCriticalNote << "LayerManager::EndTransaction skip RenderLayer()."; + } + + if (!mRepeatTransaction && !GetRoot()->GetInvalidRegion().IsEmpty()) { + GetRoot()->Mutated(); + } + + if (!mIsRepeatTransaction) { + mAnimationReadyTime = TimeStamp::Now(); + GetRoot()->StartPendingAnimations(mAnimationReadyTime); + } + + mPaintedLayerCallback = nullptr; + mPaintedLayerCallbackData = nullptr; + + // Go back to the construction phase if the transaction isn't complete. + // Layout will update the layer tree and call EndTransaction(). + mPhase = mTransactionIncomplete ? PHASE_CONSTRUCTION : PHASE_NONE; + + NS_ASSERTION(!aCallback || !mTransactionIncomplete, + "If callback is not null, transaction must be complete"); + + if (gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) { + FrameLayerBuilder::InvalidateAllLayers(this); + } + + return !mTransactionIncomplete; +} + +void +ClientLayerManager::StorePluginWidgetConfigurations(const nsTArray<nsIWidget::Configuration>& aConfigurations) +{ + if (mForwarder) { + mForwarder->StorePluginWidgetConfigurations(aConfigurations); + } +} + +void +ClientLayerManager::EndTransaction(DrawPaintedLayerCallback aCallback, + void* aCallbackData, + EndTransactionFlags aFlags) +{ + if (!mForwarder->IPCOpen()) { + mInTransaction = false; + return; + } + + if (mWidget) { + mWidget->PrepareWindowEffects(); + } + EndTransactionInternal(aCallback, aCallbackData, aFlags); + ForwardTransaction(!(aFlags & END_NO_REMOTE_COMPOSITE)); + + if (mRepeatTransaction) { + mRepeatTransaction = false; + mIsRepeatTransaction = true; + if (BeginTransaction()) { + ClientLayerManager::EndTransaction(aCallback, aCallbackData, aFlags); + } + mIsRepeatTransaction = false; + } else { + MakeSnapshotIfRequired(); + } + + mInTransaction = false; + mTransactionStart = TimeStamp(); +} + +bool +ClientLayerManager::EndEmptyTransaction(EndTransactionFlags aFlags) +{ + mInTransaction = false; + + if (!mRoot || !mForwarder->IPCOpen()) { + return false; + } + + if (!EndTransactionInternal(nullptr, nullptr, aFlags)) { + // Return without calling ForwardTransaction. This leaves the + // ShadowLayerForwarder transaction open; the following + // EndTransaction will complete it. + return false; + } + if (mWidget) { + mWidget->PrepareWindowEffects(); + } + ForwardTransaction(!(aFlags & END_NO_REMOTE_COMPOSITE)); + MakeSnapshotIfRequired(); + return true; +} + +CompositorBridgeChild * +ClientLayerManager::GetRemoteRenderer() +{ + if (!mWidget) { + return nullptr; + } + + return mWidget->GetRemoteRenderer(); +} + +CompositorBridgeChild* +ClientLayerManager::GetCompositorBridgeChild() +{ + if (!XRE_IsParentProcess()) { + return CompositorBridgeChild::Get(); + } + return GetRemoteRenderer(); +} + +void +ClientLayerManager::Composite() +{ + mForwarder->Composite(); +} + +void +ClientLayerManager::DidComposite(uint64_t aTransactionId, + const TimeStamp& aCompositeStart, + const TimeStamp& aCompositeEnd) +{ + MOZ_ASSERT(mWidget); + + // |aTransactionId| will be > 0 if the compositor is acknowledging a shadow + // layers transaction. + if (aTransactionId) { + nsIWidgetListener *listener = mWidget->GetWidgetListener(); + if (listener) { + listener->DidCompositeWindow(aTransactionId, aCompositeStart, aCompositeEnd); + } + listener = mWidget->GetAttachedWidgetListener(); + if (listener) { + listener->DidCompositeWindow(aTransactionId, aCompositeStart, aCompositeEnd); + } + mTransactionIdAllocator->NotifyTransactionCompleted(aTransactionId); + } + + // These observers fire whether or not we were in a transaction. + for (size_t i = 0; i < mDidCompositeObservers.Length(); i++) { + mDidCompositeObservers[i]->DidComposite(); + } +} + +void +ClientLayerManager::GetCompositorSideAPZTestData(APZTestData* aData) const +{ + if (mForwarder->HasShadowManager()) { + if (!mForwarder->GetShadowManager()->SendGetAPZTestData(aData)) { + NS_WARNING("Call to PLayerTransactionChild::SendGetAPZTestData() failed"); + } + } +} + +float +ClientLayerManager::RequestProperty(const nsAString& aProperty) +{ + if (mForwarder->HasShadowManager()) { + float value; + if (!mForwarder->GetShadowManager()->SendRequestProperty(nsString(aProperty), &value)) { + NS_WARNING("Call to PLayerTransactionChild::SendGetAPZTestData() failed"); + } + return value; + } + return -1; +} + +void +ClientLayerManager::StartNewRepaintRequest(SequenceNumber aSequenceNumber) +{ + if (gfxPrefs::APZTestLoggingEnabled()) { + mApzTestData.StartNewRepaintRequest(aSequenceNumber); + } +} + +void +ClientLayerManager::GetFrameUniformity(FrameUniformityData* aOutData) +{ + MOZ_ASSERT(XRE_IsParentProcess(), "Frame Uniformity only supported in parent process"); + + if (HasShadowManager()) { + CompositorBridgeChild* child = GetRemoteRenderer(); + child->SendGetFrameUniformity(aOutData); + return; + } + + return LayerManager::GetFrameUniformity(aOutData); +} + +bool +ClientLayerManager::RequestOverfill(mozilla::dom::OverfillCallback* aCallback) +{ + MOZ_ASSERT(aCallback != nullptr); + MOZ_ASSERT(HasShadowManager(), "Request Overfill only supported on b2g for now"); + + if (HasShadowManager()) { + CompositorBridgeChild* child = GetRemoteRenderer(); + NS_ASSERTION(child, "Could not get CompositorBridgeChild"); + + child->AddOverfillObserver(this); + child->SendRequestOverfill(); + mOverfillCallbacks.AppendElement(aCallback); + } + + return true; +} + +void +ClientLayerManager::RunOverfillCallback(const uint32_t aOverfill) +{ + for (size_t i = 0; i < mOverfillCallbacks.Length(); i++) { + ErrorResult error; + mOverfillCallbacks[i]->Call(aOverfill, error); + } + + mOverfillCallbacks.Clear(); +} + +void +ClientLayerManager::MakeSnapshotIfRequired() +{ + if (!mShadowTarget) { + return; + } + if (mWidget) { + if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) { + // The compositor doesn't draw to a different sized surface + // when there's a rotation. Instead we rotate the result + // when drawing into dt + LayoutDeviceIntRect outerBounds = mWidget->GetBounds(); + + IntRect bounds = ToOutsideIntRect(mShadowTarget->GetClipExtents()); + if (mTargetRotation) { + bounds = + RotateRect(bounds, outerBounds.ToUnknownRect(), mTargetRotation); + } + + SurfaceDescriptor inSnapshot; + if (!bounds.IsEmpty() && + mForwarder->AllocSurfaceDescriptor(bounds.Size(), + gfxContentType::COLOR_ALPHA, + &inSnapshot)) { + + // Make a copy of |inSnapshot| because the call to send it over IPC + // will call forget() on the Shmem inside, and zero it out. + SurfaceDescriptor outSnapshot = inSnapshot; + + if (remoteRenderer->SendMakeSnapshot(inSnapshot, bounds)) { + RefPtr<DataSourceSurface> surf = GetSurfaceForDescriptor(outSnapshot); + DrawTarget* dt = mShadowTarget->GetDrawTarget(); + + Rect dstRect(bounds.x, bounds.y, bounds.width, bounds.height); + Rect srcRect(0, 0, bounds.width, bounds.height); + + gfx::Matrix rotate = + ComputeTransformForUnRotation(outerBounds.ToUnknownRect(), + mTargetRotation); + + gfx::Matrix oldMatrix = dt->GetTransform(); + dt->SetTransform(rotate * oldMatrix); + dt->DrawSurface(surf, dstRect, srcRect, + DrawSurfaceOptions(), + DrawOptions(1.0f, CompositionOp::OP_OVER)); + dt->SetTransform(oldMatrix); + } + mForwarder->DestroySurfaceDescriptor(&outSnapshot); + } + } + } + mShadowTarget = nullptr; +} + +void +ClientLayerManager::FlushRendering() +{ + if (mWidget) { + if (CompositorBridgeChild* remoteRenderer = mWidget->GetRemoteRenderer()) { + remoteRenderer->SendFlushRendering(); + } + } +} + +void +ClientLayerManager::UpdateTextureFactoryIdentifier(const TextureFactoryIdentifier& aNewIdentifier) +{ + mForwarder->IdentifyTextureHost(aNewIdentifier); +} + +void +ClientLayerManager::SendInvalidRegion(const nsIntRegion& aRegion) +{ + if (mWidget) { + if (CompositorBridgeChild* remoteRenderer = mWidget->GetRemoteRenderer()) { + remoteRenderer->SendNotifyRegionInvalidated(aRegion); + } + } +} + +uint32_t +ClientLayerManager::StartFrameTimeRecording(int32_t aBufferSize) +{ + CompositorBridgeChild* renderer = GetRemoteRenderer(); + if (renderer) { + uint32_t startIndex; + renderer->SendStartFrameTimeRecording(aBufferSize, &startIndex); + return startIndex; + } + return -1; +} + +void +ClientLayerManager::StopFrameTimeRecording(uint32_t aStartIndex, + nsTArray<float>& aFrameIntervals) +{ + CompositorBridgeChild* renderer = GetRemoteRenderer(); + if (renderer) { + renderer->SendStopFrameTimeRecording(aStartIndex, &aFrameIntervals); + } +} + +void +ClientLayerManager::ForwardTransaction(bool aScheduleComposite) +{ + TimeStamp start = TimeStamp::Now(); + + // Skip the synchronization for buffer since we also skip the painting during + // device-reset status. + if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) { + if (mForwarder->GetSyncObject() && + mForwarder->GetSyncObject()->IsSyncObjectValid()) { + mForwarder->GetSyncObject()->FinalizeFrame(); + } + } + + mPhase = PHASE_FORWARD; + + mLatestTransactionId = mTransactionIdAllocator->GetTransactionId(); + TimeStamp transactionStart; + if (!mTransactionIdAllocator->GetTransactionStart().IsNull()) { + transactionStart = mTransactionIdAllocator->GetTransactionStart(); + } else { + transactionStart = mTransactionStart; + } + + if (gfxPrefs::AlwaysPaint() && XRE_IsContentProcess()) { + mForwarder->SendPaintTime(mLatestTransactionId, mLastPaintTime); + } + + // forward this transaction's changeset to our LayerManagerComposite + bool sent; + AutoTArray<EditReply, 10> replies; + if (mForwarder->EndTransaction(&replies, mRegionToClear, + mLatestTransactionId, aScheduleComposite, mPaintSequenceNumber, + mIsRepeatTransaction, transactionStart, &sent)) { + for (nsTArray<EditReply>::size_type i = 0; i < replies.Length(); ++i) { + const EditReply& reply = replies[i]; + + switch (reply.type()) { + case EditReply::TOpContentBufferSwap: { + MOZ_LAYERS_LOG(("[LayersForwarder] DoubleBufferSwap")); + + const OpContentBufferSwap& obs = reply.get_OpContentBufferSwap(); + + RefPtr<CompositableClient> compositable = + CompositableClient::FromIPDLActor(obs.compositableChild()); + ContentClientRemote* contentClient = + static_cast<ContentClientRemote*>(compositable.get()); + MOZ_ASSERT(contentClient); + + contentClient->SwapBuffers(obs.frontUpdatedRegion()); + + break; + } + default: + NS_RUNTIMEABORT("not reached"); + } + } + + if (sent) { + mNeedsComposite = false; + } + } else if (HasShadowManager()) { + NS_WARNING("failed to forward Layers transaction"); + } + + if (!sent) { + // Clear the transaction id so that it doesn't get returned + // unless we forwarded to somewhere that doesn't actually + // have a compositor. + mTransactionIdAllocator->RevokeTransactionId(mLatestTransactionId); + } + + mPhase = PHASE_NONE; + + // this may result in Layers being deleted, which results in + // PLayer::Send__delete__() and DeallocShmem() + mKeepAlive.Clear(); + + TabChild* window = mWidget ? mWidget->GetOwningTabChild() : nullptr; + if (window) { + TimeStamp end = TimeStamp::Now(); + window->DidRequestComposite(start, end); + } +} + +ShadowableLayer* +ClientLayerManager::Hold(Layer* aLayer) +{ + MOZ_ASSERT(HasShadowManager(), + "top-level tree, no shadow tree to remote to"); + + ShadowableLayer* shadowable = ClientLayer::ToClientLayer(aLayer); + MOZ_ASSERT(shadowable, "trying to remote an unshadowable layer"); + + mKeepAlive.AppendElement(aLayer); + return shadowable; +} + +bool +ClientLayerManager::IsCompositingCheap() +{ + // Whether compositing is cheap depends on the parent backend. + return mForwarder->mShadowManager && + LayerManager::IsCompositingCheap(mForwarder->GetCompositorBackendType()); +} + +bool +ClientLayerManager::AreComponentAlphaLayersEnabled() +{ + return GetCompositorBackendType() != LayersBackend::LAYERS_BASIC && + AsShadowForwarder()->SupportsComponentAlpha() && + LayerManager::AreComponentAlphaLayersEnabled(); +} + +void +ClientLayerManager::SetIsFirstPaint() +{ + mForwarder->SetIsFirstPaint(); +} + +void +ClientLayerManager::ClearCachedResources(Layer* aSubtree) +{ + if (mDestroyed) { + // ClearCachedResource was already called by ClientLayerManager::Destroy + return; + } + MOZ_ASSERT(!HasShadowManager() || !aSubtree); + mForwarder->ClearCachedResources(); + if (aSubtree) { + ClearLayer(aSubtree); + } else if (mRoot) { + ClearLayer(mRoot); + } +} + +void +ClientLayerManager::HandleMemoryPressure() +{ + if (mRoot) { + HandleMemoryPressureLayer(mRoot); + } + + if (GetCompositorBridgeChild()) { + GetCompositorBridgeChild()->HandleMemoryPressure(); + } +} + +void +ClientLayerManager::ClearLayer(Layer* aLayer) +{ + ClientLayer::ToClientLayer(aLayer)->ClearCachedResources(); + for (Layer* child = aLayer->GetFirstChild(); child; + child = child->GetNextSibling()) { + ClearLayer(child); + } +} + +void +ClientLayerManager::HandleMemoryPressureLayer(Layer* aLayer) +{ + ClientLayer::ToClientLayer(aLayer)->HandleMemoryPressure(); + for (Layer* child = aLayer->GetFirstChild(); child; + child = child->GetNextSibling()) { + HandleMemoryPressureLayer(child); + } +} + +void +ClientLayerManager::GetBackendName(nsAString& aName) +{ + switch (mForwarder->GetCompositorBackendType()) { + case LayersBackend::LAYERS_NONE: aName.AssignLiteral("None"); return; + case LayersBackend::LAYERS_BASIC: aName.AssignLiteral("Basic"); return; + case LayersBackend::LAYERS_OPENGL: aName.AssignLiteral("OpenGL"); return; + case LayersBackend::LAYERS_D3D9: aName.AssignLiteral("Direct3D 9"); return; + case LayersBackend::LAYERS_D3D11: { +#ifdef XP_WIN + if (DeviceManagerDx::Get()->IsWARP()) { + aName.AssignLiteral("Direct3D 11 WARP"); + } else { + aName.AssignLiteral("Direct3D 11"); + } +#endif + return; + } + default: NS_RUNTIMEABORT("Invalid backend"); + } +} + +bool +ClientLayerManager::AsyncPanZoomEnabled() const +{ + return mWidget && mWidget->AsyncPanZoomEnabled(); +} + +void +ClientLayerManager::SetNextPaintSyncId(int32_t aSyncId) +{ + mForwarder->SetPaintSyncId(aSyncId); +} + +void +ClientLayerManager::SetLayerObserverEpoch(uint64_t aLayerObserverEpoch) +{ + mForwarder->SetLayerObserverEpoch(aLayerObserverEpoch); +} + +void +ClientLayerManager::AddDidCompositeObserver(DidCompositeObserver* aObserver) +{ + if (!mDidCompositeObservers.Contains(aObserver)) { + mDidCompositeObservers.AppendElement(aObserver); + } +} + +void +ClientLayerManager::RemoveDidCompositeObserver(DidCompositeObserver* aObserver) +{ + mDidCompositeObservers.RemoveElement(aObserver); +} + +bool +ClientLayerManager::DependsOnStaleDevice() const +{ + return gfxPlatform::GetPlatform()->GetDeviceCounter() != mDeviceCounter; +} + + +already_AddRefed<PersistentBufferProvider> +ClientLayerManager::CreatePersistentBufferProvider(const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat) +{ + // Don't use a shared buffer provider if compositing is considered "not cheap" + // because the canvas will most likely be flattened into a thebes layer instead + // of being sent to the compositor, in which case rendering into shared memory + // is wasteful. + if (IsCompositingCheap() && + gfxPrefs::PersistentBufferProviderSharedEnabled()) { + RefPtr<PersistentBufferProvider> provider + = PersistentBufferProviderShared::Create(aSize, aFormat, AsShadowForwarder()); + if (provider) { + return provider.forget(); + } + } + + return LayerManager::CreatePersistentBufferProvider(aSize, aFormat); +} + + +ClientLayer::~ClientLayer() +{ + if (HasShadow()) { + PLayerChild::Send__delete__(GetShadow()); + } + MOZ_COUNT_DTOR(ClientLayer); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ClientLayerManager.h b/gfx/layers/client/ClientLayerManager.h new file mode 100644 index 000000000..5bcd5e412 --- /dev/null +++ b/gfx/layers/client/ClientLayerManager.h @@ -0,0 +1,422 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 GFX_CLIENTLAYERMANAGER_H +#define GFX_CLIENTLAYERMANAGER_H + +#include <stdint.h> // for int32_t +#include "Layers.h" +#include "gfxContext.h" // for gfxContext +#include "mozilla/Attributes.h" // for override +#include "mozilla/LinkedList.h" // For LinkedList +#include "mozilla/WidgetUtils.h" // for ScreenRotation +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/LayersTypes.h" // for BufferMode, LayersBackend, etc +#include "mozilla/layers/ShadowLayers.h" // for ShadowLayerForwarder, etc +#include "mozilla/layers/APZTestData.h" // for APZTestData +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsIObserver.h" // for nsIObserver +#include "nsISupportsImpl.h" // for Layer::Release, etc +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsTArray.h" // for nsTArray +#include "nscore.h" // for nsAString +#include "mozilla/layers/TransactionIdAllocator.h" +#include "nsIWidget.h" // For plugin window configuration information structs + +namespace mozilla { +namespace layers { + +class ClientPaintedLayer; +class CompositorBridgeChild; +class ImageLayer; +class PLayerChild; +class FrameUniformityData; + +class ClientLayerManager final : public LayerManager +{ + typedef nsTArray<RefPtr<Layer> > LayerRefArray; + +public: + explicit ClientLayerManager(nsIWidget* aWidget); + + virtual void Destroy() override; + +protected: + virtual ~ClientLayerManager(); + +public: + virtual ShadowLayerForwarder* AsShadowForwarder() override + { + return mForwarder; + } + + virtual ClientLayerManager* AsClientLayerManager() override + { + return this; + } + + virtual int32_t GetMaxTextureSize() const override; + + virtual void SetDefaultTargetConfiguration(BufferMode aDoubleBuffering, ScreenRotation aRotation); + virtual bool BeginTransactionWithTarget(gfxContext* aTarget) override; + virtual bool BeginTransaction() override; + virtual bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override; + virtual void EndTransaction(DrawPaintedLayerCallback aCallback, + void* aCallbackData, + EndTransactionFlags aFlags = END_DEFAULT) override; + + virtual LayersBackend GetBackendType() override { return LayersBackend::LAYERS_CLIENT; } + virtual LayersBackend GetCompositorBackendType() override + { + return AsShadowForwarder()->GetCompositorBackendType(); + } + virtual void GetBackendName(nsAString& name) override; + virtual const char* Name() const override { return "Client"; } + + virtual void SetRoot(Layer* aLayer) override; + + virtual void Mutated(Layer* aLayer) override; + + virtual already_AddRefed<PaintedLayer> CreatePaintedLayer() override; + virtual already_AddRefed<PaintedLayer> CreatePaintedLayerWithHint(PaintedLayerCreationHint aHint) override; + virtual already_AddRefed<ContainerLayer> CreateContainerLayer() override; + virtual already_AddRefed<ImageLayer> CreateImageLayer() override; + virtual already_AddRefed<CanvasLayer> CreateCanvasLayer() override; + virtual already_AddRefed<ReadbackLayer> CreateReadbackLayer() override; + virtual already_AddRefed<ColorLayer> CreateColorLayer() override; + virtual already_AddRefed<RefLayer> CreateRefLayer() override; + + void UpdateTextureFactoryIdentifier(const TextureFactoryIdentifier& aNewIdentifier); + TextureFactoryIdentifier GetTextureFactoryIdentifier() + { + return AsShadowForwarder()->GetTextureFactoryIdentifier(); + } + + virtual void FlushRendering() override; + void SendInvalidRegion(const nsIntRegion& aRegion); + + virtual uint32_t StartFrameTimeRecording(int32_t aBufferSize) override; + + virtual void StopFrameTimeRecording(uint32_t aStartIndex, + nsTArray<float>& aFrameIntervals) override; + + virtual bool NeedsWidgetInvalidation() override { return false; } + + ShadowableLayer* Hold(Layer* aLayer); + + bool HasShadowManager() const { return mForwarder->HasShadowManager(); } + + virtual bool IsCompositingCheap() override; + virtual bool HasShadowManagerInternal() const override { return HasShadowManager(); } + + virtual void SetIsFirstPaint() override; + + /** + * Pass through call to the forwarder for nsPresContext's + * CollectPluginGeometryUpdates. Passes widget configuration information + * to the compositor for transmission to the chrome process. This + * configuration gets set when the window paints. + */ + void StorePluginWidgetConfigurations(const nsTArray<nsIWidget::Configuration>& + aConfigurations) override; + + // Drop cached resources and ask our shadow manager to do the same, + // if we have one. + virtual void ClearCachedResources(Layer* aSubtree = nullptr) override; + + void HandleMemoryPressure(); + + void SetRepeatTransaction() { mRepeatTransaction = true; } + bool GetRepeatTransaction() { return mRepeatTransaction; } + + bool IsRepeatTransaction() { return mIsRepeatTransaction; } + + void SetTransactionIncomplete() { mTransactionIncomplete = true; } + + bool HasShadowTarget() { return !!mShadowTarget; } + + void SetShadowTarget(gfxContext* aTarget) { mShadowTarget = aTarget; } + + bool CompositorMightResample() { return mCompositorMightResample; } + + DrawPaintedLayerCallback GetPaintedLayerCallback() const + { return mPaintedLayerCallback; } + + void* GetPaintedLayerCallbackData() const + { return mPaintedLayerCallbackData; } + + CompositorBridgeChild* GetRemoteRenderer(); + + CompositorBridgeChild* GetCompositorBridgeChild(); + + // Disable component alpha layers with the software compositor. + virtual bool ShouldAvoidComponentAlphaLayers() override { return !IsCompositingCheap(); } + + bool InConstruction() { return mPhase == PHASE_CONSTRUCTION; } +#ifdef DEBUG + bool InDrawing() { return mPhase == PHASE_DRAWING; } + bool InForward() { return mPhase == PHASE_FORWARD; } +#endif + bool InTransaction() { return mPhase != PHASE_NONE; } + + void SetNeedsComposite(bool aNeedsComposite) + { + mNeedsComposite = aNeedsComposite; + } + bool NeedsComposite() const { return mNeedsComposite; } + + virtual void Composite() override; + virtual void GetFrameUniformity(FrameUniformityData* aFrameUniformityData) override; + virtual bool RequestOverfill(mozilla::dom::OverfillCallback* aCallback) override; + virtual void RunOverfillCallback(const uint32_t aOverfill) override; + + void DidComposite(uint64_t aTransactionId, + const mozilla::TimeStamp& aCompositeStart, + const mozilla::TimeStamp& aCompositeEnd); + + virtual bool AreComponentAlphaLayersEnabled() override; + + // Log APZ test data for the current paint. We supply the paint sequence + // number ourselves, and take care of calling APZTestData::StartNewPaint() + // when a new paint is started. + void LogTestDataForCurrentPaint(FrameMetrics::ViewID aScrollId, + const std::string& aKey, + const std::string& aValue) + { + mApzTestData.LogTestDataForPaint(mPaintSequenceNumber, aScrollId, aKey, aValue); + } + + // Log APZ test data for a repaint request. The sequence number must be + // passed in from outside, and APZTestData::StartNewRepaintRequest() needs + // to be called from the outside as well when a new repaint request is started. + void StartNewRepaintRequest(SequenceNumber aSequenceNumber); + + // TODO(botond): When we start using this and write a wrapper similar to + // nsLayoutUtils::LogTestDataForPaint(), make sure that wrapper checks + // gfxPrefs::APZTestLoggingEnabled(). + void LogTestDataForRepaintRequest(SequenceNumber aSequenceNumber, + FrameMetrics::ViewID aScrollId, + const std::string& aKey, + const std::string& aValue) + { + mApzTestData.LogTestDataForRepaintRequest(aSequenceNumber, aScrollId, aKey, aValue); + } + + // Get the content-side APZ test data for reading. For writing, use the + // LogTestData...() functions. + const APZTestData& GetAPZTestData() const { + return mApzTestData; + } + + // Get a copy of the compositor-side APZ test data for our layers ID. + void GetCompositorSideAPZTestData(APZTestData* aData) const; + + void SetTransactionIdAllocator(TransactionIdAllocator* aAllocator) { mTransactionIdAllocator = aAllocator; } + + float RequestProperty(const nsAString& aProperty) override; + + bool AsyncPanZoomEnabled() const override; + + void SetNextPaintSyncId(int32_t aSyncId); + + void SetLayerObserverEpoch(uint64_t aLayerObserverEpoch); + + class DidCompositeObserver { + public: + virtual void DidComposite() = 0; + }; + + void AddDidCompositeObserver(DidCompositeObserver* aObserver); + void RemoveDidCompositeObserver(DidCompositeObserver* aObserver); + + virtual already_AddRefed<PersistentBufferProvider> + CreatePersistentBufferProvider(const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) override; + +protected: + enum TransactionPhase { + PHASE_NONE, PHASE_CONSTRUCTION, PHASE_DRAWING, PHASE_FORWARD + }; + TransactionPhase mPhase; + +private: + // Listen memory-pressure event for ClientLayerManager + class MemoryPressureObserver final : public nsIObserver + { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit MemoryPressureObserver(ClientLayerManager* aClientLayerManager) + : mClientLayerManager(aClientLayerManager) + { + RegisterMemoryPressureEvent(); + } + + void Destroy(); + + private: + virtual ~MemoryPressureObserver() {} + void RegisterMemoryPressureEvent(); + void UnregisterMemoryPressureEvent(); + + ClientLayerManager* mClientLayerManager; + }; + + /** + * Forward transaction results to the parent context. + */ + void ForwardTransaction(bool aScheduleComposite); + + /** + * Take a snapshot of the parent context, and copy + * it into mShadowTarget. + */ + void MakeSnapshotIfRequired(); + + void ClearLayer(Layer* aLayer); + + void HandleMemoryPressureLayer(Layer* aLayer); + + bool EndTransactionInternal(DrawPaintedLayerCallback aCallback, + void* aCallbackData, + EndTransactionFlags); + + bool DependsOnStaleDevice() const; + + LayerRefArray mKeepAlive; + + nsIWidget* mWidget; + + /* PaintedLayer callbacks; valid at the end of a transaciton, + * while rendering */ + DrawPaintedLayerCallback mPaintedLayerCallback; + void *mPaintedLayerCallbackData; + + // When we're doing a transaction in order to draw to a non-default + // target, the layers transaction is only performed in order to send + // a PLayers:Update. We save the original non-default target to + // mShadowTarget, and then perform the transaction using + // mDummyTarget as the render target. After the transaction ends, + // we send a message to our remote side to capture the actual pixels + // being drawn to the default target, and then copy those pixels + // back to mShadowTarget. + RefPtr<gfxContext> mShadowTarget; + + RefPtr<TransactionIdAllocator> mTransactionIdAllocator; + uint64_t mLatestTransactionId; + TimeDuration mLastPaintTime; + + // Sometimes we draw to targets that don't natively support + // landscape/portrait orientation. When we need to implement that + // ourselves, |mTargetRotation| describes the induced transform we + // need to apply when compositing content to our target. + ScreenRotation mTargetRotation; + + // Used to repeat the transaction right away (to avoid rebuilding + // a display list) to support progressive drawing. + bool mRepeatTransaction; + bool mIsRepeatTransaction; + bool mTransactionIncomplete; + bool mCompositorMightResample; + bool mNeedsComposite; + + // An incrementing sequence number for paints. + // Incremented in BeginTransaction(), but not for repeat transactions. + uint32_t mPaintSequenceNumber; + + APZTestData mApzTestData; + + RefPtr<ShadowLayerForwarder> mForwarder; + AutoTArray<dom::OverfillCallback*,0> mOverfillCallbacks; + mozilla::TimeStamp mTransactionStart; + + nsTArray<DidCompositeObserver*> mDidCompositeObservers; + + RefPtr<MemoryPressureObserver> mMemoryPressureObserver; + uint64_t mDeviceCounter; +}; + +class ClientLayer : public ShadowableLayer +{ +public: + ClientLayer() + { + MOZ_COUNT_CTOR(ClientLayer); + } + + ~ClientLayer(); + + void SetShadow(PLayerChild* aShadow) + { + MOZ_ASSERT(!mShadow, "can't have two shadows (yet)"); + mShadow = aShadow; + } + + virtual void Disconnect() + { + // This is an "emergency Disconnect()", called when the compositing + // process has died. |mShadow| and our Shmem buffers are + // automatically managed by IPDL, so we don't need to explicitly + // free them here (it's hard to get that right on emergency + // shutdown anyway). + mShadow = nullptr; + } + + virtual void ClearCachedResources() { } + + // Shrink memory usage. + // Called when "memory-pressure" is observed. + virtual void HandleMemoryPressure() { } + + virtual void RenderLayer() = 0; + virtual void RenderLayerWithReadback(ReadbackProcessor *aReadback) { RenderLayer(); } + + virtual ClientPaintedLayer* AsThebes() { return nullptr; } + + static inline ClientLayer * + ToClientLayer(Layer* aLayer) + { + return static_cast<ClientLayer*>(aLayer->ImplData()); + } + + template <typename LayerType> + static inline void RenderMaskLayers(LayerType* aLayer) { + if (aLayer->GetMaskLayer()) { + ToClientLayer(aLayer->GetMaskLayer())->RenderLayer(); + } + for (size_t i = 0; i < aLayer->GetAncestorMaskLayerCount(); i++) { + ToClientLayer(aLayer->GetAncestorMaskLayerAt(i))->RenderLayer(); + } + } +}; + +// Create a shadow layer (PLayerChild) for aLayer, if we're forwarding +// our layer tree to a parent process. Record the new layer creation +// in the current open transaction as a side effect. +template<typename CreatedMethod> void +CreateShadowFor(ClientLayer* aLayer, + ClientLayerManager* aMgr, + CreatedMethod aMethod) +{ + PLayerChild* shadow = aMgr->AsShadowForwarder()->ConstructShadowFor(aLayer); + if (!shadow) { + return; + } + + aLayer->SetShadow(shadow); + (aMgr->AsShadowForwarder()->*aMethod)(aLayer); + aMgr->Hold(aLayer->AsLayer()); +} + +#define CREATE_SHADOW(_type) \ + CreateShadowFor(layer, this, \ + &ShadowLayerForwarder::Created ## _type ## Layer) + + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_CLIENTLAYERMANAGER_H */ diff --git a/gfx/layers/client/ClientPaintedLayer.cpp b/gfx/layers/client/ClientPaintedLayer.cpp new file mode 100644 index 000000000..871f10559 --- /dev/null +++ b/gfx/layers/client/ClientPaintedLayer.cpp @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 "ClientPaintedLayer.h" +#include "ClientTiledPaintedLayer.h" // for ClientTiledPaintedLayer +#include <stdint.h> // for uint32_t +#include "GeckoProfiler.h" // for PROFILER_LABEL +#include "client/ClientLayerManager.h" // for ClientLayerManager, etc +#include "gfxContext.h" // for gfxContext +#include "gfxRect.h" // for gfxRect +#include "gfxPrefs.h" // for gfxPrefs +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/Matrix.h" // for Matrix +#include "mozilla/gfx/Rect.h" // for Rect, IntRect +#include "mozilla/gfx/Types.h" // for Float, etc +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/Preferences.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "gfx2DGlue.h" +#include "ReadbackProcessor.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +void +ClientPaintedLayer::PaintThebes() +{ + PROFILER_LABEL("ClientPaintedLayer", "PaintThebes", + js::ProfileEntry::Category::GRAPHICS); + + NS_ASSERTION(ClientManager()->InDrawing(), + "Can only draw in drawing phase"); + + uint32_t flags = RotatedContentBuffer::PAINT_CAN_DRAW_ROTATED; +#ifndef MOZ_IGNORE_PAINT_WILL_RESAMPLE + if (ClientManager()->CompositorMightResample()) { + flags |= RotatedContentBuffer::PAINT_WILL_RESAMPLE; + } + if (!(flags & RotatedContentBuffer::PAINT_WILL_RESAMPLE)) { + if (MayResample()) { + flags |= RotatedContentBuffer::PAINT_WILL_RESAMPLE; + } + } +#endif + PaintState state = + mContentClient->BeginPaintBuffer(this, flags); + mValidRegion.Sub(mValidRegion, state.mRegionToInvalidate); + + if (!state.mRegionToDraw.IsEmpty() && !ClientManager()->GetPaintedLayerCallback()) { + ClientManager()->SetTransactionIncomplete(); + return; + } + + // The area that became invalid and is visible needs to be repainted + // (this could be the whole visible area if our buffer switched + // from RGB to RGBA, because we might need to repaint with + // subpixel AA) + state.mRegionToInvalidate.And(state.mRegionToInvalidate, + GetLocalVisibleRegion().ToUnknownRegion()); + + bool didUpdate = false; + RotatedContentBuffer::DrawIterator iter; + while (DrawTarget* target = mContentClient->BorrowDrawTargetForPainting(state, &iter)) { + if (!target || !target->IsValid()) { + if (target) { + mContentClient->ReturnDrawTargetToBuffer(target); + } + continue; + } + + SetAntialiasingFlags(this, target); + + RefPtr<gfxContext> ctx = gfxContext::CreatePreservingTransformOrNull(target); + MOZ_ASSERT(ctx); // already checked the target above + + ClientManager()->GetPaintedLayerCallback()(this, + ctx, + iter.mDrawRegion, + iter.mDrawRegion, + state.mClip, + state.mRegionToInvalidate, + ClientManager()->GetPaintedLayerCallbackData()); + + ctx = nullptr; + mContentClient->ReturnDrawTargetToBuffer(target); + didUpdate = true; + } + + if (didUpdate) { + Mutated(); + + mValidRegion.Or(mValidRegion, state.mRegionToDraw); + + ContentClientRemote* contentClientRemote = static_cast<ContentClientRemote*>(mContentClient.get()); + MOZ_ASSERT(contentClientRemote->GetIPDLActor()); + + // Hold(this) ensures this layer is kept alive through the current transaction + // The ContentClient assumes this layer is kept alive (e.g., in CreateBuffer), + // so deleting this Hold for whatever reason will break things. + ClientManager()->Hold(this); + contentClientRemote->Updated(state.mRegionToDraw, + mVisibleRegion.ToUnknownRegion(), + state.mDidSelfCopy); + } +} + +void +ClientPaintedLayer::RenderLayerWithReadback(ReadbackProcessor *aReadback) +{ + RenderMaskLayers(this); + + if (!mContentClient) { + mContentClient = ContentClient::CreateContentClient(ClientManager()->AsShadowForwarder()); + if (!mContentClient) { + return; + } + mContentClient->Connect(); + ClientManager()->AsShadowForwarder()->Attach(mContentClient, this); + MOZ_ASSERT(mContentClient->GetForwarder()); + } + + nsTArray<ReadbackProcessor::Update> readbackUpdates; + nsIntRegion readbackRegion; + if (aReadback && UsedForReadback()) { + aReadback->GetPaintedLayerUpdates(this, &readbackUpdates); + } + + IntPoint origin(mVisibleRegion.GetBounds().x, mVisibleRegion.GetBounds().y); + mContentClient->BeginPaint(); + PaintThebes(); + mContentClient->EndPaint(&readbackUpdates); +} + +already_AddRefed<PaintedLayer> +ClientLayerManager::CreatePaintedLayer() +{ + return CreatePaintedLayerWithHint(NONE); +} + +already_AddRefed<PaintedLayer> +ClientLayerManager::CreatePaintedLayerWithHint(PaintedLayerCreationHint aHint) +{ + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + if (gfxPrefs::LayersTilesEnabled()) { + RefPtr<ClientTiledPaintedLayer> layer = new ClientTiledPaintedLayer(this, aHint); + CREATE_SHADOW(Painted); + return layer.forget(); + } else { + RefPtr<ClientPaintedLayer> layer = new ClientPaintedLayer(this, aHint); + CREATE_SHADOW(Painted); + return layer.forget(); + } +} + +void +ClientPaintedLayer::PrintInfo(std::stringstream& aStream, const char* aPrefix) +{ + PaintedLayer::PrintInfo(aStream, aPrefix); + if (mContentClient) { + aStream << "\n"; + nsAutoCString pfx(aPrefix); + pfx += " "; + mContentClient->PrintInfo(aStream, pfx.get()); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ClientPaintedLayer.h b/gfx/layers/client/ClientPaintedLayer.h new file mode 100644 index 000000000..949f746ae --- /dev/null +++ b/gfx/layers/client/ClientPaintedLayer.h @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 GFX_CLIENTPAINTEDLAYER_H +#define GFX_CLIENTPAINTEDLAYER_H + +#include "ClientLayerManager.h" // for ClientLayerManager, etc +#include "Layers.h" // for PaintedLayer, etc +#include "RotatedBuffer.h" // for RotatedContentBuffer, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/layers/ContentClient.h" // for ContentClient +#include "mozilla/mozalloc.h" // for operator delete +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsRegion.h" // for nsIntRegion +#include "mozilla/layers/PLayerTransaction.h" // for PaintedLayerAttributes + +namespace mozilla { +namespace layers { + +class CompositableClient; +class ShadowableLayer; +class SpecificLayerAttributes; + +class ClientPaintedLayer : public PaintedLayer, + public ClientLayer { +public: + typedef RotatedContentBuffer::PaintState PaintState; + typedef RotatedContentBuffer::ContentType ContentType; + + explicit ClientPaintedLayer(ClientLayerManager* aLayerManager, + LayerManager::PaintedLayerCreationHint aCreationHint = LayerManager::NONE) : + PaintedLayer(aLayerManager, static_cast<ClientLayer*>(this), aCreationHint), + mContentClient(nullptr) + { + MOZ_COUNT_CTOR(ClientPaintedLayer); + } + +protected: + virtual ~ClientPaintedLayer() + { + if (mContentClient) { + mContentClient->OnDetach(); + mContentClient = nullptr; + } + MOZ_COUNT_DTOR(ClientPaintedLayer); + } + +public: + virtual void SetVisibleRegion(const LayerIntRegion& aRegion) override + { + NS_ASSERTION(ClientManager()->InConstruction(), + "Can only set properties in construction phase"); + PaintedLayer::SetVisibleRegion(aRegion); + } + virtual void InvalidateRegion(const nsIntRegion& aRegion) override + { + NS_ASSERTION(ClientManager()->InConstruction(), + "Can only set properties in construction phase"); + mInvalidRegion.Add(aRegion); + mValidRegion.Sub(mValidRegion, mInvalidRegion.GetRegion()); + } + + virtual void RenderLayer() override { RenderLayerWithReadback(nullptr); } + + virtual void RenderLayerWithReadback(ReadbackProcessor *aReadback) override; + + virtual void ClearCachedResources() override + { + if (mContentClient) { + mContentClient->Clear(); + } + mValidRegion.SetEmpty(); + DestroyBackBuffer(); + } + + virtual void HandleMemoryPressure() override + { + if (mContentClient) { + mContentClient->HandleMemoryPressure(); + } + } + + virtual void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) override + { + aAttrs = PaintedLayerAttributes(GetValidRegion()); + } + + ClientLayerManager* ClientManager() + { + return static_cast<ClientLayerManager*>(mManager); + } + + virtual Layer* AsLayer() override { return this; } + virtual ShadowableLayer* AsShadowableLayer() override { return this; } + + virtual CompositableClient* GetCompositableClient() override + { + return mContentClient; + } + + virtual void Disconnect() override + { + mContentClient = nullptr; + ClientLayer::Disconnect(); + } + +protected: + void PaintThebes(); + + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + void DestroyBackBuffer() + { + mContentClient = nullptr; + } + + RefPtr<ContentClient> mContentClient; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/ClientReadbackLayer.h b/gfx/layers/client/ClientReadbackLayer.h new file mode 100644 index 000000000..5f58486ca --- /dev/null +++ b/gfx/layers/client/ClientReadbackLayer.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 GFX_CLIENTREADBACKLAYER_H +#define GFX_CLIENTREADBACKLAYER_H + +#include "ClientLayerManager.h" +#include "ReadbackLayer.h" + +namespace mozilla { +namespace layers { + +class ClientReadbackLayer : + public ReadbackLayer, + public ClientLayer +{ +public: + explicit ClientReadbackLayer(ClientLayerManager *aManager) + : ReadbackLayer(aManager, nullptr) + { + mImplData = static_cast<ClientLayer*>(this); + } + + virtual ShadowableLayer* AsShadowableLayer() override { return this; } + virtual Layer* AsLayer() override { return this; } + virtual void RenderLayer() override {} +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_CLIENTREADBACKLAYER_H */ diff --git a/gfx/layers/client/ClientTiledPaintedLayer.cpp b/gfx/layers/client/ClientTiledPaintedLayer.cpp new file mode 100644 index 000000000..5a07681f6 --- /dev/null +++ b/gfx/layers/client/ClientTiledPaintedLayer.cpp @@ -0,0 +1,615 @@ +/* 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 "ClientTiledPaintedLayer.h" +#include "FrameMetrics.h" // for FrameMetrics +#include "Units.h" // for ScreenIntRect, CSSPoint, etc +#include "UnitTransforms.h" // for TransformTo +#include "ClientLayerManager.h" // for ClientLayerManager, etc +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxPrefs.h" // for gfxPrefs +#include "gfxRect.h" // for gfxRect +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/Rect.h" // for Rect, RectTyped +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper +#include "mozilla/layers/LayersMessages.h" +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "LayersLogging.h" +#include "mozilla/layers/SingleTiledContentClient.h" + +namespace mozilla { +namespace layers { + +using gfx::Rect; +using gfx::IntRect; +using gfx::IntSize; + +ClientTiledPaintedLayer::ClientTiledPaintedLayer(ClientLayerManager* const aManager, + ClientLayerManager::PaintedLayerCreationHint aCreationHint) + : PaintedLayer(aManager, static_cast<ClientLayer*>(this), aCreationHint) + , mContentClient() + , mHaveSingleTiledContentClient(false) +{ + MOZ_COUNT_CTOR(ClientTiledPaintedLayer); + mPaintData.mLastScrollOffset = ParentLayerPoint(0, 0); + mPaintData.mFirstPaint = true; +} + +ClientTiledPaintedLayer::~ClientTiledPaintedLayer() +{ + MOZ_COUNT_DTOR(ClientTiledPaintedLayer); +} + +void +ClientTiledPaintedLayer::ClearCachedResources() +{ + if (mContentClient) { + mContentClient->ClearCachedResources(); + } + mValidRegion.SetEmpty(); + mContentClient = nullptr; +} + +void +ClientTiledPaintedLayer::FillSpecificAttributes(SpecificLayerAttributes& aAttrs) +{ + aAttrs = PaintedLayerAttributes(GetValidRegion()); +} + +static Maybe<LayerRect> +ApplyParentLayerToLayerTransform(const ParentLayerToLayerMatrix4x4& aTransform, + const ParentLayerRect& aParentLayerRect, + const LayerRect& aClip) +{ + return UntransformBy(aTransform, aParentLayerRect, aClip); +} + +static LayerToParentLayerMatrix4x4 +GetTransformToAncestorsParentLayer(Layer* aStart, const LayerMetricsWrapper& aAncestor) +{ + gfx::Matrix4x4 transform; + const LayerMetricsWrapper& ancestorParent = aAncestor.GetParent(); + for (LayerMetricsWrapper iter(aStart, LayerMetricsWrapper::StartAt::BOTTOM); + ancestorParent ? iter != ancestorParent : iter.IsValid(); + iter = iter.GetParent()) { + transform = transform * iter.GetTransform(); + + if (gfxPrefs::LayoutUseContainersForRootFrames()) { + // When scrolling containers, layout adds a post-scale into the transform + // of the displayport-ancestor (which we pick up in GetTransform() above) + // to cancel out the pres shell resolution (for historical reasons). The + // compositor in turn cancels out this post-scale (i.e., scales by the + // pres shell resolution), and to get correct calculations, we need to do + // so here, too. + // + // With containerless scrolling, the offending post-scale is on the + // parent layer of the displayport-ancestor, which we don't reach in this + // loop, so we don't need to worry about it. + float presShellResolution = iter.GetPresShellResolution(); + transform.PostScale(presShellResolution, presShellResolution, 1.0f); + } + } + return ViewAs<LayerToParentLayerMatrix4x4>(transform); +} + +void +ClientTiledPaintedLayer::GetAncestorLayers(LayerMetricsWrapper* aOutScrollAncestor, + LayerMetricsWrapper* aOutDisplayPortAncestor, + bool* aOutHasTransformAnimation) +{ + LayerMetricsWrapper scrollAncestor; + LayerMetricsWrapper displayPortAncestor; + bool hasTransformAnimation = false; + for (LayerMetricsWrapper ancestor(this, LayerMetricsWrapper::StartAt::BOTTOM); ancestor; ancestor = ancestor.GetParent()) { + hasTransformAnimation |= ancestor.HasTransformAnimation(); + const FrameMetrics& metrics = ancestor.Metrics(); + if (!scrollAncestor && metrics.GetScrollId() != FrameMetrics::NULL_SCROLL_ID) { + scrollAncestor = ancestor; + } + if (!metrics.GetDisplayPort().IsEmpty()) { + displayPortAncestor = ancestor; + // Any layer that has a displayport must be scrollable, so we can break + // here. + break; + } + } + if (aOutScrollAncestor) { + *aOutScrollAncestor = scrollAncestor; + } + if (aOutDisplayPortAncestor) { + *aOutDisplayPortAncestor = displayPortAncestor; + } + if (aOutHasTransformAnimation) { + *aOutHasTransformAnimation = hasTransformAnimation; + } +} + +void +ClientTiledPaintedLayer::BeginPaint() +{ + mPaintData.ResetPaintData(); + + if (!GetBaseTransform().Is2D()) { + // Give up if there is a complex CSS transform on the layer. We might + // eventually support these but for now it's too complicated to handle + // given that it's a pretty rare scenario. + return; + } + + // Get the metrics of the nearest scrollable layer and the nearest layer + // with a displayport. + LayerMetricsWrapper scrollAncestor; + LayerMetricsWrapper displayPortAncestor; + bool hasTransformAnimation; + GetAncestorLayers(&scrollAncestor, &displayPortAncestor, &hasTransformAnimation); + + if (!displayPortAncestor || !scrollAncestor) { + // No displayport or scroll ancestor, so we can't do progressive rendering. +#if defined(MOZ_WIDGET_ANDROID) + // Both Android and b2g on phones are guaranteed to have a displayport set, so this + // should never happen. + NS_WARNING("Tiled PaintedLayer with no scrollable container ancestor"); +#endif + return; + } + + TILING_LOG("TILING %p: Found scrollAncestor %p, displayPortAncestor %p, transform %d\n", this, + scrollAncestor.GetLayer(), displayPortAncestor.GetLayer(), hasTransformAnimation); + + const FrameMetrics& scrollMetrics = scrollAncestor.Metrics(); + const FrameMetrics& displayportMetrics = displayPortAncestor.Metrics(); + + // Calculate the transform required to convert ParentLayer space of our + // display port ancestor to the Layer space of this layer. + ParentLayerToLayerMatrix4x4 transformDisplayPortToLayer = + GetTransformToAncestorsParentLayer(this, displayPortAncestor).Inverse(); + + LayerRect layerBounds = ViewAs<LayerPixel>(Rect(GetLayerBounds())); + + // Compute the critical display port that applies to this layer in the + // LayoutDevice space of this layer, but only if there is no OMT animation + // on this layer. If there is an OMT animation then we need to draw the whole + // visible region of this layer as determined by layout, because we don't know + // what parts of it might move into view in the compositor. + mPaintData.mHasTransformAnimation = hasTransformAnimation; + if (!mPaintData.mHasTransformAnimation && + mContentClient->GetLowPrecisionTiledBuffer()) { + ParentLayerRect criticalDisplayPort = + (displayportMetrics.GetCriticalDisplayPort() * displayportMetrics.GetZoom()) + + displayportMetrics.GetCompositionBounds().TopLeft(); + Maybe<LayerRect> criticalDisplayPortTransformed = + ApplyParentLayerToLayerTransform(transformDisplayPortToLayer, criticalDisplayPort, layerBounds); + if (criticalDisplayPortTransformed) { + mPaintData.mCriticalDisplayPort = Some(RoundedToInt(*criticalDisplayPortTransformed)); + } else { + mPaintData.mCriticalDisplayPort = Some(LayerIntRect(0, 0, 0, 0)); + } + } + TILING_LOG("TILING %p: Critical displayport %s\n", this, + mPaintData.mCriticalDisplayPort ? + Stringify(*mPaintData.mCriticalDisplayPort).c_str() : "not set"); + + // Store the resolution from the displayport ancestor layer. Because this is Gecko-side, + // before any async transforms have occurred, we can use the zoom for this. + mPaintData.mResolution = displayportMetrics.GetZoom(); + TILING_LOG("TILING %p: Resolution %s\n", this, Stringify(mPaintData.mResolution).c_str()); + + // Store the applicable composition bounds in this layer's Layer units. + mPaintData.mTransformToCompBounds = + GetTransformToAncestorsParentLayer(this, scrollAncestor); + ParentLayerToLayerMatrix4x4 transformToBounds = mPaintData.mTransformToCompBounds.Inverse(); + Maybe<LayerRect> compositionBoundsTransformed = ApplyParentLayerToLayerTransform( + transformToBounds, scrollMetrics.GetCompositionBounds(), layerBounds); + if (compositionBoundsTransformed) { + mPaintData.mCompositionBounds = *compositionBoundsTransformed; + } else { + mPaintData.mCompositionBounds.SetEmpty(); + } + TILING_LOG("TILING %p: Composition bounds %s\n", this, Stringify(mPaintData.mCompositionBounds).c_str()); + + // Calculate the scroll offset since the last transaction + mPaintData.mScrollOffset = displayportMetrics.GetScrollOffset() * displayportMetrics.GetZoom(); + TILING_LOG("TILING %p: Scroll offset %s\n", this, Stringify(mPaintData.mScrollOffset).c_str()); +} + +bool +ClientTiledPaintedLayer::IsScrollingOnCompositor(const FrameMetrics& aParentMetrics) +{ + CompositorBridgeChild* compositor = nullptr; + if (Manager() && Manager()->AsClientLayerManager()) { + compositor = Manager()->AsClientLayerManager()->GetCompositorBridgeChild(); + } + + if (!compositor) { + return false; + } + + FrameMetrics compositorMetrics; + if (!compositor->LookupCompositorFrameMetrics(aParentMetrics.GetScrollId(), + compositorMetrics)) { + return false; + } + + // 1 is a tad high for a fuzzy equals epsilon however if our scroll delta + // is so small then we have nothing to gain from using paint heuristics. + float COORDINATE_EPSILON = 1.f; + + return !FuzzyEqualsAdditive(compositorMetrics.GetScrollOffset().x, + aParentMetrics.GetScrollOffset().x, + COORDINATE_EPSILON) || + !FuzzyEqualsAdditive(compositorMetrics.GetScrollOffset().y, + aParentMetrics.GetScrollOffset().y, + COORDINATE_EPSILON); +} + +bool +ClientTiledPaintedLayer::UseProgressiveDraw() { + if (!gfxPrefs::ProgressivePaint()) { + // pref is disabled, so never do progressive + return false; + } + + if (!mContentClient->GetTiledBuffer()->SupportsProgressiveUpdate()) { + return false; + } + + if (ClientManager()->HasShadowTarget()) { + // This condition is true when we are in a reftest scenario. We don't want + // to draw progressively here because it can cause intermittent reftest + // failures because the harness won't wait for all the tiles to be drawn. + return false; + } + + if (GetIsFixedPosition() || GetParent()->GetIsFixedPosition()) { + // This layer is fixed-position and so even if it does have a scrolling + // ancestor it will likely be entirely on-screen all the time, so we + // should draw it all at once + return false; + } + + if (mPaintData.mHasTransformAnimation) { + // The compositor is going to animate this somehow, so we want it all + // on the screen at once. + return false; + } + + if (ClientManager()->AsyncPanZoomEnabled()) { + LayerMetricsWrapper scrollAncestor; + GetAncestorLayers(&scrollAncestor, nullptr, nullptr); + MOZ_ASSERT(scrollAncestor); // because mPaintData.mCriticalDisplayPort is set + if (!scrollAncestor) { + return false; + } + const FrameMetrics& parentMetrics = scrollAncestor.Metrics(); + if (!IsScrollingOnCompositor(parentMetrics)) { + return false; + } + } + + return true; +} + +bool +ClientTiledPaintedLayer::RenderHighPrecision(nsIntRegion& aInvalidRegion, + const nsIntRegion& aVisibleRegion, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData) +{ + // If we have started drawing low-precision already, then we + // shouldn't do anything there. + if (mPaintData.mLowPrecisionPaintCount != 0) { + return false; + } + + // Only draw progressively when there is something to paint and the + // resolution is unchanged + if (!aInvalidRegion.IsEmpty() && + UseProgressiveDraw() && + mContentClient->GetTiledBuffer()->GetFrameResolution() == mPaintData.mResolution) { + // Store the old valid region, then clear it before painting. + // We clip the old valid region to the visible region, as it only gets + // used to decide stale content (currently valid and previously visible) + nsIntRegion oldValidRegion = mContentClient->GetTiledBuffer()->GetValidRegion(); + oldValidRegion.And(oldValidRegion, aVisibleRegion); + if (mPaintData.mCriticalDisplayPort) { + oldValidRegion.And(oldValidRegion, mPaintData.mCriticalDisplayPort->ToUnknownRect()); + } + + TILING_LOG("TILING %p: Progressive update with old valid region %s\n", this, Stringify(oldValidRegion).c_str()); + + return mContentClient->GetTiledBuffer()->ProgressiveUpdate(mValidRegion, aInvalidRegion, + oldValidRegion, &mPaintData, aCallback, aCallbackData); + } + + // Otherwise do a non-progressive paint. We must do this even when + // the region to paint is empty as the valid region may have shrunk. + + mValidRegion = aVisibleRegion; + if (mPaintData.mCriticalDisplayPort) { + mValidRegion.And(mValidRegion, mPaintData.mCriticalDisplayPort->ToUnknownRect()); + } + + TILING_LOG("TILING %p: Non-progressive paint invalid region %s\n", this, Stringify(aInvalidRegion).c_str()); + TILING_LOG("TILING %p: Non-progressive paint new valid region %s\n", this, Stringify(mValidRegion).c_str()); + + mContentClient->GetTiledBuffer()->SetFrameResolution(mPaintData.mResolution); + mContentClient->GetTiledBuffer()->PaintThebes(mValidRegion, aInvalidRegion, aInvalidRegion, + aCallback, aCallbackData); + mPaintData.mPaintFinished = true; + return true; +} + +bool +ClientTiledPaintedLayer::RenderLowPrecision(nsIntRegion& aInvalidRegion, + const nsIntRegion& aVisibleRegion, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData) +{ + // Render the low precision buffer, if the visible region is larger than the + // critical display port. + if (!mPaintData.mCriticalDisplayPort || + !nsIntRegion(mPaintData.mCriticalDisplayPort->ToUnknownRect()).Contains(aVisibleRegion)) { + nsIntRegion oldValidRegion = mContentClient->GetLowPrecisionTiledBuffer()->GetValidRegion(); + oldValidRegion.And(oldValidRegion, aVisibleRegion); + + bool updatedBuffer = false; + + // If the frame resolution or format have changed, invalidate the buffer + if (mContentClient->GetLowPrecisionTiledBuffer()->GetFrameResolution() != mPaintData.mResolution || + mContentClient->GetLowPrecisionTiledBuffer()->HasFormatChanged()) { + if (!mLowPrecisionValidRegion.IsEmpty()) { + updatedBuffer = true; + } + oldValidRegion.SetEmpty(); + mLowPrecisionValidRegion.SetEmpty(); + mContentClient->GetLowPrecisionTiledBuffer()->ResetPaintedAndValidState(); + mContentClient->GetLowPrecisionTiledBuffer()->SetFrameResolution(mPaintData.mResolution); + aInvalidRegion = aVisibleRegion; + } + + // Invalidate previously valid content that is no longer visible + if (mPaintData.mLowPrecisionPaintCount == 1) { + mLowPrecisionValidRegion.And(mLowPrecisionValidRegion, aVisibleRegion); + } + mPaintData.mLowPrecisionPaintCount++; + + // Remove the valid high-precision region from the invalid low-precision + // region. We don't want to spend time drawing things twice. + aInvalidRegion.Sub(aInvalidRegion, mValidRegion); + + TILING_LOG("TILING %p: Progressive paint: low-precision invalid region is %s\n", this, Stringify(aInvalidRegion).c_str()); + TILING_LOG("TILING %p: Progressive paint: low-precision old valid region is %s\n", this, Stringify(oldValidRegion).c_str()); + + if (!aInvalidRegion.IsEmpty()) { + updatedBuffer = mContentClient->GetLowPrecisionTiledBuffer()->ProgressiveUpdate( + mLowPrecisionValidRegion, aInvalidRegion, oldValidRegion, + &mPaintData, aCallback, aCallbackData); + } + + TILING_LOG("TILING %p: Progressive paint: low-precision new valid region is %s\n", this, Stringify(mLowPrecisionValidRegion).c_str()); + return updatedBuffer; + } + if (!mLowPrecisionValidRegion.IsEmpty()) { + TILING_LOG("TILING %p: Clearing low-precision buffer\n", this); + // Clear the low precision tiled buffer. + mLowPrecisionValidRegion.SetEmpty(); + mContentClient->GetLowPrecisionTiledBuffer()->ResetPaintedAndValidState(); + // Return true here so we send a Painted callback after clearing the valid + // region of the low precision buffer. This allows the shadow buffer's valid + // region to be updated and the associated resources to be freed. + return true; + } + return false; +} + +void +ClientTiledPaintedLayer::EndPaint() +{ + mPaintData.mLastScrollOffset = mPaintData.mScrollOffset; + mPaintData.mPaintFinished = true; + mPaintData.mFirstPaint = false; + TILING_LOG("TILING %p: Paint finished\n", this); +} + +void +ClientTiledPaintedLayer::RenderLayer() +{ + LayerManager::DrawPaintedLayerCallback callback = + ClientManager()->GetPaintedLayerCallback(); + void *data = ClientManager()->GetPaintedLayerCallbackData(); + + IntSize layerSize = mVisibleRegion.ToUnknownRegion().GetBounds().Size(); + IntSize tileSize = gfx::gfxVars::TileSize(); + bool isHalfTileWidthOrHeight = layerSize.width <= tileSize.width / 2 || + layerSize.height <= tileSize.height / 2; + + // Use single tile when layer is not scrollable, is smaller than one + // tile, or when more than half of the tiles' pixels in either + // dimension would be wasted. + bool wantSingleTiledContentClient = + (mCreationHint == LayerManager::NONE || + layerSize <= tileSize || + isHalfTileWidthOrHeight) && + SingleTiledContentClient::ClientSupportsLayerSize(layerSize, ClientManager()) && + gfxPrefs::LayersSingleTileEnabled(); + + if (mContentClient && mHaveSingleTiledContentClient && !wantSingleTiledContentClient) { + mContentClient = nullptr; + mValidRegion.SetEmpty(); + } + + if (!mContentClient) { + if (wantSingleTiledContentClient) { + mContentClient = new SingleTiledContentClient(*this, ClientManager()); + mHaveSingleTiledContentClient = true; + } else { + mContentClient = new MultiTiledContentClient(*this, ClientManager()); + mHaveSingleTiledContentClient = false; + } + + mContentClient->Connect(); + ClientManager()->AsShadowForwarder()->Attach(mContentClient, this); + MOZ_ASSERT(mContentClient->GetForwarder()); + } + + if (mContentClient->GetTiledBuffer()->HasFormatChanged()) { + mValidRegion = nsIntRegion(); + mContentClient->GetTiledBuffer()->ResetPaintedAndValidState(); + } + + TILING_LOG("TILING %p: Initial visible region %s\n", this, Stringify(mVisibleRegion).c_str()); + TILING_LOG("TILING %p: Initial valid region %s\n", this, Stringify(mValidRegion).c_str()); + TILING_LOG("TILING %p: Initial low-precision valid region %s\n", this, Stringify(mLowPrecisionValidRegion).c_str()); + + nsIntRegion neededRegion = mVisibleRegion.ToUnknownRegion(); +#ifndef MOZ_IGNORE_PAINT_WILL_RESAMPLE + // This is handled by PadDrawTargetOutFromRegion in TiledContentClient for mobile + if (MayResample()) { + // If we're resampling then bilinear filtering can read up to 1 pixel + // outside of our texture coords. Make the visible region a single rect, + // and pad it out by 1 pixel (restricted to tile boundaries) so that + // we always have valid content or transparent pixels to sample from. + IntRect bounds = neededRegion.GetBounds(); + IntRect wholeTiles = bounds; + wholeTiles.InflateToMultiple(gfx::gfxVars::TileSize()); + IntRect padded = bounds; + padded.Inflate(1); + padded.IntersectRect(padded, wholeTiles); + neededRegion = padded; + } +#endif + + nsIntRegion invalidRegion; + invalidRegion.Sub(neededRegion, mValidRegion); + if (invalidRegion.IsEmpty()) { + EndPaint(); + return; + } + + if (!callback) { + ClientManager()->SetTransactionIncomplete(); + return; + } + + if (!ClientManager()->IsRepeatTransaction()) { + // Only paint the mask layers on the first transaction. + RenderMaskLayers(this); + + // For more complex cases we need to calculate a bunch of metrics before we + // can do the paint. + BeginPaint(); + if (mPaintData.mPaintFinished) { + return; + } + + // Make sure that tiles that fall outside of the visible region or outside of the + // critical displayport are discarded on the first update. Also make sure that we + // only draw stuff inside the critical displayport on the first update. + mValidRegion.And(mValidRegion, neededRegion); + if (mPaintData.mCriticalDisplayPort) { + mValidRegion.And(mValidRegion, mPaintData.mCriticalDisplayPort->ToUnknownRect()); + invalidRegion.And(invalidRegion, mPaintData.mCriticalDisplayPort->ToUnknownRect()); + } + + TILING_LOG("TILING %p: First-transaction valid region %s\n", this, Stringify(mValidRegion).c_str()); + TILING_LOG("TILING %p: First-transaction invalid region %s\n", this, Stringify(invalidRegion).c_str()); + } else { + if (mPaintData.mCriticalDisplayPort) { + invalidRegion.And(invalidRegion, mPaintData.mCriticalDisplayPort->ToUnknownRect()); + } + TILING_LOG("TILING %p: Repeat-transaction invalid region %s\n", this, Stringify(invalidRegion).c_str()); + } + + nsIntRegion lowPrecisionInvalidRegion; + if (mContentClient->GetLowPrecisionTiledBuffer()) { + // Calculate the invalid region for the low precision buffer. Make sure + // to remove the valid high-precision area so we don't double-paint it. + lowPrecisionInvalidRegion.Sub(neededRegion, mLowPrecisionValidRegion); + lowPrecisionInvalidRegion.Sub(lowPrecisionInvalidRegion, mValidRegion); + } + TILING_LOG("TILING %p: Low-precision invalid region %s\n", this, Stringify(lowPrecisionInvalidRegion).c_str()); + + bool updatedHighPrecision = RenderHighPrecision(invalidRegion, + neededRegion, + callback, data); + if (updatedHighPrecision) { + ClientManager()->Hold(this); + mContentClient->UpdatedBuffer(TiledContentClient::TILED_BUFFER); + + if (!mPaintData.mPaintFinished) { + // There is still more high-res stuff to paint, so we're not + // done yet. A subsequent transaction will take care of this. + ClientManager()->SetRepeatTransaction(); + return; + } + } + + // If there is nothing to draw in low-precision, then we're done. + if (lowPrecisionInvalidRegion.IsEmpty()) { + EndPaint(); + return; + } + + if (updatedHighPrecision) { + // If there are low precision updates, but we just did some high-precision + // updates, then mark the paint as unfinished and request a repeat transaction. + // This is so that we don't perform low-precision updates in the same transaction + // as high-precision updates. + TILING_LOG("TILING %p: Scheduling repeat transaction for low-precision painting\n", this); + ClientManager()->SetRepeatTransaction(); + mPaintData.mLowPrecisionPaintCount = 1; + mPaintData.mPaintFinished = false; + return; + } + + bool updatedLowPrecision = RenderLowPrecision(lowPrecisionInvalidRegion, + neededRegion, + callback, data); + if (updatedLowPrecision) { + ClientManager()->Hold(this); + mContentClient->UpdatedBuffer(TiledContentClient::LOW_PRECISION_TILED_BUFFER); + + if (!mPaintData.mPaintFinished) { + // There is still more low-res stuff to paint, so we're not + // done yet. A subsequent transaction will take care of this. + ClientManager()->SetRepeatTransaction(); + return; + } + } + + // If we get here, we've done all the high- and low-precision + // paints we wanted to do, so we can finish the paint and chill. + EndPaint(); +} + +bool +ClientTiledPaintedLayer::IsOptimizedFor(LayerManager::PaintedLayerCreationHint aHint) +{ + // The only creation hint is whether the layer is scrollable or not, and this + // is only respected on B2G and OSX, where it's used to determine whether to + // use a tiled content client or not. + // There are pretty nasty performance consequences for not using tiles on + // large, scrollable layers, so we want the layer to be recreated in this + // situation. + return aHint == GetCreationHint(); +} + +void +ClientTiledPaintedLayer::PrintInfo(std::stringstream& aStream, const char* aPrefix) +{ + PaintedLayer::PrintInfo(aStream, aPrefix); + if (mContentClient) { + aStream << "\n"; + nsAutoCString pfx(aPrefix); + pfx += " "; + mContentClient->PrintInfo(aStream, pfx.get()); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ClientTiledPaintedLayer.h b/gfx/layers/client/ClientTiledPaintedLayer.h new file mode 100644 index 000000000..9e1e003ee --- /dev/null +++ b/gfx/layers/client/ClientTiledPaintedLayer.h @@ -0,0 +1,153 @@ +/* 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 GFX_CLIENTTILEDPAINTEDLAYER_H +#define GFX_CLIENTTILEDPAINTEDLAYER_H + +#include "ClientLayerManager.h" // for ClientLayer, etc +#include "Layers.h" // for PaintedLayer, etc +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/layers/TiledContentClient.h" +#include "nsDebug.h" // for NS_RUNTIMEABORT +#include "nsRegion.h" // for nsIntRegion + +namespace mozilla { +namespace layers { + +class ShadowableLayer; +class SpecificLayerAttributes; + +/** + * An implementation of PaintedLayer that ONLY supports remote + * composition that is backed by tiles. This painted layer implementation + * is better suited to mobile hardware to work around slow implementation + * of glTexImage2D (for OGL compositors), and restrait memory bandwidth. + * + * Tiled PaintedLayers use a different protocol compared with other + * layers. A copy of the tiled buffer is made and sent to the compositing + * thread via the layers protocol. Tiles are uploaded by the buffers + * asynchonously without using IPC, that means they are not safe for cross- + * process use (bug 747811). Each tile has a TextureHost/Client pair but + * they communicate directly rather than using the Texture protocol. + * + * There is no ContentClient for tiled layers. There is a ContentHost, however. + */ +class ClientTiledPaintedLayer : public PaintedLayer, + public ClientLayer +{ + typedef PaintedLayer Base; + +public: + explicit ClientTiledPaintedLayer(ClientLayerManager* const aManager, + ClientLayerManager::PaintedLayerCreationHint aCreationHint = LayerManager::NONE); + +protected: + ~ClientTiledPaintedLayer(); + + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + +public: + // Override name to distinguish it from ClientPaintedLayer in layer dumps + virtual const char* Name() const override { return "TiledPaintedLayer"; } + + // PaintedLayer + virtual Layer* AsLayer() override { return this; } + virtual void InvalidateRegion(const nsIntRegion& aRegion) override { + mInvalidRegion.Add(aRegion); + nsIntRegion invalidRegion = mInvalidRegion.GetRegion(); + mValidRegion.Sub(mValidRegion, invalidRegion); + mLowPrecisionValidRegion.Sub(mLowPrecisionValidRegion, invalidRegion); + } + + // Shadow methods + virtual void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) override; + virtual ShadowableLayer* AsShadowableLayer() override { return this; } + + virtual void Disconnect() override + { + ClientLayer::Disconnect(); + } + + virtual void RenderLayer() override; + + virtual void ClearCachedResources() override; + + virtual void HandleMemoryPressure() override + { + if (mContentClient) { + mContentClient->HandleMemoryPressure(); + } + } + + /** + * Helper method to find the nearest ancestor layers which + * scroll and have a displayport. The parameters are out-params + * which hold the return values; the values passed in may be null. + */ + void GetAncestorLayers(LayerMetricsWrapper* aOutScrollAncestor, + LayerMetricsWrapper* aOutDisplayPortAncestor, + bool* aOutHasTransformAnimation); + + virtual bool IsOptimizedFor(LayerManager::PaintedLayerCreationHint aCreationHint) override; + +private: + ClientLayerManager* ClientManager() + { + return static_cast<ClientLayerManager*>(mManager); + } + + /** + * For the initial PaintThebes of a transaction, calculates all the data + * needed for that paint and any repeated transactions. + */ + void BeginPaint(); + + /** + * Check if the layer is being scrolled by APZ on the compositor. + */ + bool IsScrollingOnCompositor(const FrameMetrics& aParentMetrics); + + /** + * Check if we should use progressive draw on this layer. We will + * disable progressive draw based on a preference or if the layer + * is not being scrolled. + */ + bool UseProgressiveDraw(); + + /** + * Helper function to do the high-precision paint. + * This function returns true if it updated the paint buffer. + */ + bool RenderHighPrecision(nsIntRegion& aInvalidRegion, + const nsIntRegion& aVisibleRegion, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData); + + /** + * Helper function to do the low-precision paint. + * This function returns true if it updated the paint buffer. + */ + bool RenderLowPrecision(nsIntRegion& aInvalidRegion, + const nsIntRegion& aVisibleRegion, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData); + + /** + * This causes the paint to be marked as finished, and updates any data + * necessary to persist until the next paint. + */ + void EndPaint(); + + RefPtr<TiledContentClient> mContentClient; + // Flag to indicate if mContentClient is a SingleTiledContentClient. This is + // only valid when mContentClient is non-null. + bool mHaveSingleTiledContentClient; + nsIntRegion mLowPrecisionValidRegion; + BasicTiledLayerPaintData mPaintData; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/CompositableChild.cpp b/gfx/layers/client/CompositableChild.cpp new file mode 100644 index 000000000..34a0e0696 --- /dev/null +++ b/gfx/layers/client/CompositableChild.cpp @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "CompositableChild.h" +#include "CompositableClient.h" + +namespace mozilla { +namespace layers { + +/* static */ PCompositableChild* +CompositableChild::CreateActor() +{ + CompositableChild* child = new CompositableChild(); + child->AddRef(); + return child; +} + +/* static */ void +CompositableChild::DestroyActor(PCompositableChild* aChild) +{ + static_cast<CompositableChild*>(aChild)->Release(); +} + +CompositableChild::CompositableChild() + : mCompositableClient(nullptr), + mAsyncID(0), + mCanSend(true) +{ + MOZ_COUNT_CTOR(CompositableChild); +} + +CompositableChild::~CompositableChild() +{ + MOZ_COUNT_DTOR(CompositableChild); +} + +bool +CompositableChild::IsConnected() const +{ + return mCompositableClient && mCanSend; +} + +void +CompositableChild::Init(CompositableClient* aCompositable, uint64_t aAsyncID) +{ + mCompositableClient = aCompositable; + mAsyncID = aAsyncID; +} + +void +CompositableChild::RevokeCompositableClient() +{ + mCompositableClient = nullptr; +} + +RefPtr<CompositableClient> +CompositableChild::GetCompositableClient() +{ + return mCompositableClient; +} + +void +CompositableChild::ActorDestroy(ActorDestroyReason) +{ + MOZ_ASSERT(NS_IsMainThread()); + + mCanSend = false; + + if (mCompositableClient) { + mCompositableClient->mCompositableChild = nullptr; + mCompositableClient = nullptr; + } +} + +/* static */ PCompositableChild* +AsyncCompositableChild::CreateActor() +{ + AsyncCompositableChild* child = new AsyncCompositableChild(); + child->AddRef(); + return child; +} + +AsyncCompositableChild::AsyncCompositableChild() + : mLock("AsyncCompositableChild.mLock") +{ +} + +AsyncCompositableChild::~AsyncCompositableChild() +{ +} + +void +AsyncCompositableChild::ActorDestroy(ActorDestroyReason) +{ + mCanSend = false; + + // We do not revoke CompositableClient::mCompositableChild here, since that + // could race with the main thread. + RevokeCompositableClient(); +} + +void +AsyncCompositableChild::RevokeCompositableClient() +{ + MutexAutoLock lock(mLock); + mCompositableClient = nullptr; +} + +RefPtr<CompositableClient> +AsyncCompositableChild::GetCompositableClient() +{ + MutexAutoLock lock(mLock); + return CompositableChild::GetCompositableClient(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/CompositableChild.h b/gfx/layers/client/CompositableChild.h new file mode 100644 index 000000000..381d8051b --- /dev/null +++ b/gfx/layers/client/CompositableChild.h @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_layers_client_CompositableChild_h +#define mozilla_gfx_layers_client_CompositableChild_h + +#include <stdint.h> +#include "IPDLActor.h" +#include "mozilla/Mutex.h" +#include "mozilla/layers/PCompositableChild.h" + +namespace mozilla { +namespace layers { + +class CompositableClient; +class AsyncCompositableChild; + +/** + * IPDL actor used by CompositableClient to match with its corresponding + * CompositableHost on the compositor side. + * + * CompositableChild is owned by a CompositableClient. + */ +class CompositableChild : public PCompositableChild +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositableChild) + + static PCompositableChild* CreateActor(); + static void DestroyActor(PCompositableChild* aChild); + + void Init(CompositableClient* aCompositable, uint64_t aAsyncID); + virtual void RevokeCompositableClient(); + + virtual void ActorDestroy(ActorDestroyReason) override; + + virtual RefPtr<CompositableClient> GetCompositableClient(); + + virtual AsyncCompositableChild* AsAsyncCompositableChild() { + return nullptr; + } + + uint64_t GetAsyncID() const { + return mAsyncID; + } + + // These should only be called on the IPDL thread. + bool IsConnected() const; + bool CanSend() const { + return mCanSend; + } + +protected: + CompositableChild(); + virtual ~CompositableChild(); + +protected: + CompositableClient* mCompositableClient; + uint64_t mAsyncID; + bool mCanSend; +}; + +// This CompositableChild can be used off the main thread. +class AsyncCompositableChild final : public CompositableChild +{ +public: + static PCompositableChild* CreateActor(); + + void RevokeCompositableClient() override; + RefPtr<CompositableClient> GetCompositableClient() override; + + void ActorDestroy(ActorDestroyReason) override; + + AsyncCompositableChild* AsAsyncCompositableChild() override { + return this; + } + +protected: + AsyncCompositableChild(); + ~AsyncCompositableChild() override; + +private: + Mutex mLock; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_client_CompositableChild_h diff --git a/gfx/layers/client/CompositableClient.cpp b/gfx/layers/client/CompositableClient.cpp new file mode 100644 index 000000000..52b9a4637 --- /dev/null +++ b/gfx/layers/client/CompositableClient.cpp @@ -0,0 +1,274 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/CompositableChild.h" +#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/mozalloc.h" // for operator delete, etc +#include "mozilla/layers/PCompositableChild.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#ifdef XP_WIN +#include "gfxWindowsPlatform.h" // for gfxWindowsPlatform +#include "mozilla/layers/TextureD3D11.h" +#include "mozilla/layers/TextureD3D9.h" +#endif +#include "gfxUtils.h" +#include "IPDLActor.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +void +CompositableClient::InitIPDLActor(PCompositableChild* aActor, uint64_t aAsyncID) +{ + MOZ_ASSERT(aActor); + + mForwarder->AssertInForwarderThread(); + + mCompositableChild = static_cast<CompositableChild*>(aActor); + mCompositableChild->Init(this, aAsyncID); +} + +/* static */ RefPtr<CompositableClient> +CompositableClient::FromIPDLActor(PCompositableChild* aActor) +{ + MOZ_ASSERT(aActor); + + RefPtr<CompositableClient> client = static_cast<CompositableChild*>(aActor)->GetCompositableClient(); + if (!client) { + return nullptr; + } + + client->mForwarder->AssertInForwarderThread(); + return client; +} + +CompositableClient::CompositableClient(CompositableForwarder* aForwarder, + TextureFlags aTextureFlags) +: mForwarder(aForwarder) +, mTextureFlags(aTextureFlags) +{ + MOZ_COUNT_CTOR(CompositableClient); +} + +CompositableClient::~CompositableClient() +{ + MOZ_COUNT_DTOR(CompositableClient); + Destroy(); +} + +LayersBackend +CompositableClient::GetCompositorBackendType() const +{ + return mForwarder->GetCompositorBackendType(); +} + +PCompositableChild* +CompositableClient::GetIPDLActor() const +{ + return mCompositableChild; +} + +bool +CompositableClient::Connect(ImageContainer* aImageContainer) +{ + MOZ_ASSERT(!mCompositableChild); + if (!GetForwarder() || GetIPDLActor()) { + 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 mCompositableChild && mCompositableChild->IsConnected(); +} + +void +CompositableClient::Destroy() +{ + if (!mCompositableChild) { + return; + } + + if (mTextureClientRecycler) { + mTextureClientRecycler->Destroy(); + } + + // Take away our IPDL's actor reference back to us. + mCompositableChild->RevokeCompositableClient(); + + // Schedule the IPDL actor to be destroyed on the forwarder's thread. + mForwarder->Destroy(mCompositableChild); + mCompositableChild = nullptr; +} + +uint64_t +CompositableClient::GetAsyncID() const +{ + if (mCompositableChild) { + return mCompositableChild->GetAsyncID(); + } + return 0; // zero is always an invalid async ID +} + +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() +{ + if (mTextureClientRecycler) { + mTextureClientRecycler->ShrinkToMinimumSize(); + } +} + +void +CompositableClient::HandleMemoryPressure() +{ + if (mTextureClientRecycler) { + mTextureClientRecycler->ShrinkToMinimumSize(); + } +} + +void +CompositableClient::RemoveTexture(TextureClient* aTexture) +{ + mForwarder->RemoveTextureFromCompositable(this, aTexture); +} + +TextureClientRecycleAllocator* +CompositableClient::GetTextureClientRecycler() +{ + if (mTextureClientRecycler) { + return mTextureClientRecycler; + } + + if (!mForwarder) { + return nullptr; + } + + if(!mForwarder->GetTextureForwarder()->UsesImageBridge()) { + MOZ_ASSERT(NS_IsMainThread()); + mTextureClientRecycler = new layers::TextureClientRecycleAllocator(mForwarder); + return mTextureClientRecycler; + } + + // Handle a case that mForwarder is ImageBridge + + if (InImageBridgeChildThread()) { + mTextureClientRecycler = new layers::TextureClientRecycleAllocator(mForwarder); + return mTextureClientRecycler; + } + + ReentrantMonitor barrier("CompositableClient::GetTextureClientRecycler"); + ReentrantMonitorAutoEnter mainThreadAutoMon(barrier); + bool done = false; + + RefPtr<Runnable> runnable = + NS_NewRunnableFunction([&]() { + if (!mTextureClientRecycler) { + mTextureClientRecycler = new layers::TextureClientRecycleAllocator(mForwarder); + } + ReentrantMonitorAutoEnter childThreadAutoMon(barrier); + done = true; + barrier.NotifyAll(); + }); + + ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(runnable.forget()); + + // should stop the thread until done. + while (!done) { + barrier.Wait(); + } + + return mTextureClientRecycler; +} + +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 000000000..07df59d59 --- /dev/null +++ b/gfx/layers/client/CompositableClient.h @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 <vector> // for vector +#include <map> // for map +#include "mozilla/Assertions.h" // for MOZ_CRASH +#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 "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc + +namespace mozilla { +namespace layers { + +class CompositableClient; +class ImageBridgeChild; +class ImageContainer; +class CompositableForwarder; +class CompositableChild; +class PCompositableChild; +class TextureClientRecycleAllocator; + +/** + * 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 in-transaction texture transfer (the default), call + * ShadowLayerForwarder::Attach(CompositableClient*, ShadowableLayer*). This + * will let the LayerComposite on the compositor side know which CompositableHost + * to use for compositing. + * + * 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; + + PCompositableChild* GetIPDLActor() 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 zero, it means the compositable is not async (it is used + * on the main thread). + */ + uint64_t GetAsyncID() const; + + /** + * 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 (for + * example to properly keep track of fences on Gonk). + * + * See AutoRemoveTexture to automatically invoke this at the end of a scope. + */ + virtual void RemoveTexture(TextureClient* aTexture); + + static RefPtr<CompositableClient> FromIPDLActor(PCompositableChild* aActor); + + void InitIPDLActor(PCompositableChild* aActor, uint64_t aAsyncID = 0); + + TextureFlags GetTextureFlags() const { return mTextureFlags; } + + TextureClientRecycleAllocator* GetTextureClientRecycler(); + + bool HasTextureClientRecycler() { return !!mTextureClientRecycler; } + + static void DumpTextureClient(std::stringstream& aStream, + TextureClient* aTexture, + TextureDumpMode aCompress); +protected: + RefPtr<CompositableChild> mCompositableChild; + RefPtr<CompositableForwarder> mForwarder; + // Some layers may want to enforce some flags to all their textures + // (like disallowing tiling) + TextureFlags mTextureFlags; + RefPtr<TextureClientRecycleAllocator> mTextureClientRecycler; + + 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/ContentClient.cpp b/gfx/layers/client/ContentClient.cpp new file mode 100644 index 000000000..3373230a9 --- /dev/null +++ b/gfx/layers/client/ContentClient.cpp @@ -0,0 +1,668 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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/ContentClient.h" +#include "BasicLayers.h" // for BasicLayerManager +#include "gfxContext.h" // for gfxContext, etc +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxEnv.h" // for gfxEnv +#include "gfxPrefs.h" // for gfxPrefs +#include "gfxPoint.h" // for IntSize, gfxPoint +#include "gfxUtils.h" // for gfxUtils +#include "ipc/ShadowLayers.h" // for ShadowLayerForwarder +#include "mozilla/ArrayUtils.h" // for ArrayLength +#include "mozilla/Maybe.h" +#include "mozilla/gfx/2D.h" // for DrawTarget, Factory +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" +#include "mozilla/layers/CompositorBridgeChild.h" // for CompositorBridgeChild +#include "mozilla/layers/LayerManagerComposite.h" +#include "mozilla/layers/LayersMessages.h" // for ThebesBufferData +#include "mozilla/layers/LayersTypes.h" +#include "nsDebug.h" // for NS_ASSERTION, NS_WARNING, etc +#include "nsISupportsImpl.h" // for gfxContext::Release, etc +#include "nsIWidget.h" // for nsIWidget +#include "nsLayoutUtils.h" +#ifdef XP_WIN +#include "gfxWindowsPlatform.h" +#endif +#ifdef MOZ_WIDGET_GTK +#include "gfxPlatformGtk.h" +#endif +#include "ReadbackLayer.h" + +#include <vector> + +using namespace std; + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +static TextureFlags TextureFlagsForRotatedContentBufferFlags(uint32_t aBufferFlags) +{ + TextureFlags result = TextureFlags::NO_FLAGS; + + if (aBufferFlags & RotatedContentBuffer::BUFFER_COMPONENT_ALPHA) { + result |= TextureFlags::COMPONENT_ALPHA; + } + + return result; +} + +/* static */ already_AddRefed<ContentClient> +ContentClient::CreateContentClient(CompositableForwarder* aForwarder) +{ + LayersBackend backend = aForwarder->GetCompositorBackendType(); + if (backend != LayersBackend::LAYERS_OPENGL && + backend != LayersBackend::LAYERS_D3D9 && + backend != LayersBackend::LAYERS_D3D11 && + backend != LayersBackend::LAYERS_BASIC) { + return nullptr; + } + + bool useDoubleBuffering = false; + +#ifdef XP_WIN + if (backend == LayersBackend::LAYERS_D3D11) { + useDoubleBuffering = gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend(); + } else +#endif +#ifdef MOZ_WIDGET_GTK + // We can't use double buffering when using image content with + // Xrender support on Linux, as ContentHostDoubleBuffered is not + // suited for direct uploads to the server. + if (!gfxPlatformGtk::GetPlatform()->UseImageOffscreenSurfaces() || + !gfxVars::UseXRender()) +#endif + { + useDoubleBuffering = (LayerManagerComposite::SupportsDirectTexturing() && + backend != LayersBackend::LAYERS_D3D9) || + backend == LayersBackend::LAYERS_BASIC; + } + + if (useDoubleBuffering || gfxEnv::ForceDoubleBuffering()) { + return MakeAndAddRef<ContentClientDoubleBuffered>(aForwarder); + } + return MakeAndAddRef<ContentClientSingleBuffered>(aForwarder); +} + +void +ContentClient::EndPaint(nsTArray<ReadbackProcessor::Update>* aReadbackUpdates) +{ +} + +void +ContentClient::PrintInfo(std::stringstream& aStream, const char* aPrefix) +{ + aStream << aPrefix; + aStream << nsPrintfCString("ContentClient (0x%p)", this).get(); + + if (profiler_feature_active("displaylistdump")) { + nsAutoCString pfx(aPrefix); + pfx += " "; + + Dump(aStream, pfx.get(), false); + } +} + +// We pass a null pointer for the ContentClient Forwarder argument, which means +// this client will not have a ContentHost on the other side. +ContentClientBasic::ContentClientBasic(gfx::BackendType aBackend) + : ContentClient(nullptr) + , RotatedContentBuffer(ContainsVisibleBounds) + , mBackend(aBackend) +{} + +void +ContentClientBasic::CreateBuffer(ContentType aType, + const IntRect& aRect, + uint32_t aFlags, + RefPtr<gfx::DrawTarget>* aBlackDT, + RefPtr<gfx::DrawTarget>* aWhiteDT) +{ + MOZ_ASSERT(!(aFlags & BUFFER_COMPONENT_ALPHA)); + if (aFlags & BUFFER_COMPONENT_ALPHA) { + gfxDevCrash(LogReason::AlphaWithBasicClient) << "Asking basic content client for component alpha"; + } + + IntSize size(aRect.width, aRect.height); +#ifdef XP_WIN + if (mBackend == BackendType::CAIRO && + (aType == gfxContentType::COLOR || aType == gfxContentType::COLOR_ALPHA)) { + RefPtr<gfxASurface> surf = + new gfxWindowsSurface(size, aType == gfxContentType::COLOR ? gfxImageFormat::X8R8G8B8_UINT32 : + gfxImageFormat::A8R8G8B8_UINT32); + *aBlackDT = gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(surf, size); + + if (*aBlackDT) { + return; + } + } +#endif + + *aBlackDT = gfxPlatform::GetPlatform()->CreateDrawTargetForBackend( + mBackend, size, + gfxPlatform::GetPlatform()->Optimal2DFormatForContent(aType)); +} + +void +ContentClientRemoteBuffer::DestroyBuffers() +{ + if (!mTextureClient) { + return; + } + + mOldTextures.AppendElement(mTextureClient); + mTextureClient = nullptr; + if (mTextureClientOnWhite) { + mOldTextures.AppendElement(mTextureClientOnWhite); + mTextureClientOnWhite = nullptr; + } + + DestroyFrontBuffer(); +} + +class RemoteBufferReadbackProcessor : public TextureReadbackSink +{ +public: + RemoteBufferReadbackProcessor(nsTArray<ReadbackProcessor::Update>* aReadbackUpdates, + const IntRect& aBufferRect, const nsIntPoint& aBufferRotation) + : mReadbackUpdates(*aReadbackUpdates) + , mBufferRect(aBufferRect) + , mBufferRotation(aBufferRotation) + { + for (uint32_t i = 0; i < mReadbackUpdates.Length(); ++i) { + mLayerRefs.push_back(mReadbackUpdates[i].mLayer); + } + } + + virtual void ProcessReadback(gfx::DataSourceSurface *aSourceSurface) + { + SourceRotatedBuffer rotBuffer(aSourceSurface, nullptr, mBufferRect, mBufferRotation); + + for (uint32_t i = 0; i < mReadbackUpdates.Length(); ++i) { + ReadbackProcessor::Update& update = mReadbackUpdates[i]; + nsIntPoint offset = update.mLayer->GetBackgroundLayerOffset(); + + ReadbackSink* sink = update.mLayer->GetSink(); + + if (!sink) { + continue; + } + + if (!aSourceSurface) { + sink->SetUnknown(update.mSequenceCounter); + continue; + } + + RefPtr<DrawTarget> dt = + sink->BeginUpdate(update.mUpdateRect + offset, update.mSequenceCounter); + if (!dt) { + continue; + } + + dt->SetTransform(Matrix::Translation(offset.x, offset.y)); + + rotBuffer.DrawBufferWithRotation(dt, RotatedBuffer::BUFFER_BLACK); + + update.mLayer->GetSink()->EndUpdate(update.mUpdateRect + offset); + } + } + +private: + nsTArray<ReadbackProcessor::Update> mReadbackUpdates; + // This array is used to keep the layers alive until the callback. + vector<RefPtr<Layer>> mLayerRefs; + + IntRect mBufferRect; + nsIntPoint mBufferRotation; +}; + +void +ContentClientRemoteBuffer::BeginPaint() +{ + EnsureBackBufferIfFrontBuffer(); + + // XXX: So we might not have a TextureClient yet.. because it will + // only be created by CreateBuffer.. which will deliver a locked surface!. + if (mTextureClient) { + SetBufferProvider(mTextureClient); + } + if (mTextureClientOnWhite) { + SetBufferProviderOnWhite(mTextureClientOnWhite); + } +} + +void +ContentClientRemoteBuffer::EndPaint(nsTArray<ReadbackProcessor::Update>* aReadbackUpdates) +{ + MOZ_ASSERT(!mTextureClientOnWhite || !aReadbackUpdates || aReadbackUpdates->Length() == 0); + + // XXX: We might still not have a texture client if PaintThebes + // decided we didn't need one yet because the region to draw was empty. + SetBufferProvider(nullptr); + SetBufferProviderOnWhite(nullptr); + for (unsigned i = 0; i< mOldTextures.Length(); ++i) { + if (mOldTextures[i]->IsLocked()) { + mOldTextures[i]->Unlock(); + } + } + mOldTextures.Clear(); + + if (mTextureClient && mTextureClient->IsLocked()) { + if (aReadbackUpdates->Length() > 0) { + RefPtr<TextureReadbackSink> readbackSink = new RemoteBufferReadbackProcessor(aReadbackUpdates, mBufferRect, mBufferRotation); + + mTextureClient->SetReadbackSink(readbackSink); + } + + mTextureClient->Unlock(); + mTextureClient->SyncWithObject(mForwarder->GetSyncObject()); + } + if (mTextureClientOnWhite && mTextureClientOnWhite->IsLocked()) { + mTextureClientOnWhite->Unlock(); + mTextureClientOnWhite->SyncWithObject(mForwarder->GetSyncObject()); + } + + ContentClientRemote::EndPaint(aReadbackUpdates); +} + +void +ContentClientRemoteBuffer::BuildTextureClients(SurfaceFormat aFormat, + const IntRect& aRect, + uint32_t aFlags) +{ + // If we hit this assertion, then it might be due to an empty transaction + // followed by a real transaction. Our buffers should be created (but not + // painted in the empty transaction) and then painted (but not created) in the + // real transaction. That is kind of fragile, and this assert will catch + // circumstances where we screw that up, e.g., by unnecessarily recreating our + // buffers. + MOZ_ASSERT(!mIsNewBuffer, + "Bad! Did we create a buffer twice without painting?"); + + mIsNewBuffer = true; + + DestroyBuffers(); + + mSurfaceFormat = aFormat; + mSize = IntSize(aRect.width, aRect.height); + mTextureFlags = TextureFlagsForRotatedContentBufferFlags(aFlags); + + if (aFlags & BUFFER_COMPONENT_ALPHA) { + mTextureFlags |= TextureFlags::COMPONENT_ALPHA; + } + + CreateBackBuffer(mBufferRect); +} + +void +ContentClientRemoteBuffer::CreateBackBuffer(const IntRect& aBufferRect) +{ + // gfx::BackendType::NONE means fallback to the content backend + TextureAllocationFlags textureAllocFlags + = (mTextureFlags & TextureFlags::COMPONENT_ALPHA) ? + TextureAllocationFlags::ALLOC_CLEAR_BUFFER_BLACK : + TextureAllocationFlags::ALLOC_CLEAR_BUFFER; + + mTextureClient = CreateTextureClientForDrawing( + mSurfaceFormat, mSize, BackendSelector::Content, + mTextureFlags | ExtraTextureFlags(), + textureAllocFlags + ); + if (!mTextureClient || !AddTextureClient(mTextureClient)) { + AbortTextureClientCreation(); + return; + } + + if (mTextureFlags & TextureFlags::COMPONENT_ALPHA) { + mTextureClientOnWhite = mTextureClient->CreateSimilar( + mForwarder->GetCompositorBackendType(), + mTextureFlags | ExtraTextureFlags(), + TextureAllocationFlags::ALLOC_CLEAR_BUFFER_WHITE + ); + if (!mTextureClientOnWhite || !AddTextureClient(mTextureClientOnWhite)) { + AbortTextureClientCreation(); + return; + } + } +} + +void +ContentClientRemoteBuffer::CreateBuffer(ContentType aType, + const IntRect& aRect, + uint32_t aFlags, + RefPtr<gfx::DrawTarget>* aBlackDT, + RefPtr<gfx::DrawTarget>* aWhiteDT) +{ + BuildTextureClients(gfxPlatform::GetPlatform()->Optimal2DFormatForContent(aType), aRect, aFlags); + if (!mTextureClient) { + return; + } + + // We just created the textures and we are about to get their draw targets + // so we have to lock them here. + DebugOnly<bool> locked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE); + MOZ_ASSERT(locked, "Could not lock the TextureClient"); + + *aBlackDT = mTextureClient->BorrowDrawTarget(); + if (aFlags & BUFFER_COMPONENT_ALPHA) { + locked = mTextureClientOnWhite->Lock(OpenMode::OPEN_READ_WRITE); + MOZ_ASSERT(locked, "Could not lock the second TextureClient for component alpha"); + + *aWhiteDT = mTextureClientOnWhite->BorrowDrawTarget(); + } +} + +nsIntRegion +ContentClientRemoteBuffer::GetUpdatedRegion(const nsIntRegion& aRegionToDraw, + const nsIntRegion& aVisibleRegion, + bool aDidSelfCopy) +{ + nsIntRegion updatedRegion; + if (mIsNewBuffer || aDidSelfCopy) { + // A buffer reallocation clears both buffers. The front buffer has all the + // content by now, but the back buffer is still clear. Here, in effect, we + // are saying to copy all of the pixels of the front buffer to the back. + // Also when we self-copied in the buffer, the buffer space + // changes and some changed buffer content isn't reflected in the + // draw or invalidate region (on purpose!). When this happens, we + // need to read back the entire buffer too. + updatedRegion = aVisibleRegion.GetBounds(); + mIsNewBuffer = false; + } else { + updatedRegion = aRegionToDraw; + } + + NS_ASSERTION(BufferRect().Contains(aRegionToDraw.GetBounds()), + "Update outside of buffer rect!"); + MOZ_ASSERT(mTextureClient, "should have a back buffer by now"); + + return updatedRegion; +} + +void +ContentClientRemoteBuffer::Updated(const nsIntRegion& aRegionToDraw, + const nsIntRegion& aVisibleRegion, + bool aDidSelfCopy) +{ + nsIntRegion updatedRegion = GetUpdatedRegion(aRegionToDraw, + aVisibleRegion, + aDidSelfCopy); + + MOZ_ASSERT(mTextureClient); + if (mTextureClientOnWhite) { + mForwarder->UseComponentAlphaTextures(this, mTextureClient, + mTextureClientOnWhite); + } else { + AutoTArray<CompositableForwarder::TimedTextureClient,1> textures; + CompositableForwarder::TimedTextureClient* t = textures.AppendElement(); + t->mTextureClient = mTextureClient; + IntSize size = mTextureClient->GetSize(); + t->mPictureRect = nsIntRect(0, 0, size.width, size.height); + GetForwarder()->UseTextures(this, textures); + } + mForwarder->UpdateTextureRegion(this, + ThebesBufferData(BufferRect(), + BufferRotation()), + updatedRegion); +} + +void +ContentClientRemoteBuffer::SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) +{ + mFrontAndBackBufferDiffer = true; +} + +void +ContentClientRemoteBuffer::Dump(std::stringstream& aStream, + const char* aPrefix, + bool aDumpHtml, TextureDumpMode aCompress) +{ + // TODO We should combine the OnWhite/OnBlack here an just output a single image. + if (!aDumpHtml) { + aStream << "\n" << aPrefix << "Surface: "; + } + CompositableClient::DumpTextureClient(aStream, mTextureClient, aCompress); +} + +void +ContentClientDoubleBuffered::Dump(std::stringstream& aStream, + const char* aPrefix, + bool aDumpHtml, TextureDumpMode aCompress) +{ + // TODO We should combine the OnWhite/OnBlack here an just output a single image. + if (!aDumpHtml) { + aStream << "\n" << aPrefix << "Surface: "; + } + CompositableClient::DumpTextureClient(aStream, mFrontClient, aCompress); +} + +void +ContentClientDoubleBuffered::DestroyFrontBuffer() +{ + if (mFrontClient) { + mOldTextures.AppendElement(mFrontClient); + mFrontClient = nullptr; + } + + if (mFrontClientOnWhite) { + mOldTextures.AppendElement(mFrontClientOnWhite); + mFrontClientOnWhite = nullptr; + } +} + +void +ContentClientDoubleBuffered::Updated(const nsIntRegion& aRegionToDraw, + const nsIntRegion& aVisibleRegion, + bool aDidSelfCopy) +{ + ContentClientRemoteBuffer::Updated(aRegionToDraw, aVisibleRegion, aDidSelfCopy); +} + +void +ContentClientDoubleBuffered::SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) +{ + mFrontUpdatedRegion = aFrontUpdatedRegion; + + RefPtr<TextureClient> oldBack = mTextureClient; + mTextureClient = mFrontClient; + mFrontClient = oldBack; + + oldBack = mTextureClientOnWhite; + mTextureClientOnWhite = mFrontClientOnWhite; + mFrontClientOnWhite = oldBack; + + IntRect oldBufferRect = mBufferRect; + mBufferRect = mFrontBufferRect; + mFrontBufferRect = oldBufferRect; + + nsIntPoint oldBufferRotation = mBufferRotation; + mBufferRotation = mFrontBufferRotation; + mFrontBufferRotation = oldBufferRotation; + + MOZ_ASSERT(mFrontClient); + + ContentClientRemoteBuffer::SwapBuffers(aFrontUpdatedRegion); +} + +void +ContentClientDoubleBuffered::BeginPaint() +{ + ContentClientRemoteBuffer::BeginPaint(); + + mIsNewBuffer = false; + + if (!mFrontAndBackBufferDiffer) { + return; + } + + if (mDidSelfCopy) { + // We can't easily draw our front buffer into us, since we're going to be + // copying stuff around anyway it's easiest if we just move our situation + // to non-rotated while we're at it. If this situation occurs we'll have + // hit a self-copy path in PaintThebes before as well anyway. + mBufferRect.MoveTo(mFrontBufferRect.TopLeft()); + mBufferRotation = nsIntPoint(); + return; + } + mBufferRect = mFrontBufferRect; + mBufferRotation = mFrontBufferRotation; +} + +// Sync front/back buffers content +// After executing, the new back buffer has the same (interesting) pixels as +// the new front buffer, and mValidRegion et al. are correct wrt the new +// back buffer (i.e. as they were for the old back buffer) +void +ContentClientDoubleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw) +{ + if (mTextureClient) { + DebugOnly<bool> locked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE); + MOZ_ASSERT(locked); + } + if (mTextureClientOnWhite) { + DebugOnly<bool> locked = mTextureClientOnWhite->Lock(OpenMode::OPEN_READ_WRITE); + MOZ_ASSERT(locked); + } + + if (!mFrontAndBackBufferDiffer) { + MOZ_ASSERT(!mDidSelfCopy, "If we have to copy the world, then our buffers are different, right?"); + return; + } + MOZ_ASSERT(mFrontClient); + if (!mFrontClient) { + return; + } + + MOZ_LAYERS_LOG(("BasicShadowableThebes(%p): reading back <x=%d,y=%d,w=%d,h=%d>", + this, + mFrontUpdatedRegion.GetBounds().x, + mFrontUpdatedRegion.GetBounds().y, + mFrontUpdatedRegion.GetBounds().width, + mFrontUpdatedRegion.GetBounds().height)); + + mFrontAndBackBufferDiffer = false; + + nsIntRegion updateRegion = mFrontUpdatedRegion; + if (mDidSelfCopy) { + mDidSelfCopy = false; + updateRegion = mBufferRect; + } + + // No point in sync'ing what we are going to draw over anyway. And if there is + // nothing to sync at all, there is nothing to do and we can go home early. + updateRegion.Sub(updateRegion, aRegionToDraw); + if (updateRegion.IsEmpty()) { + return; + } + + // We need to ensure that we lock these two buffers in the same + // order as the compositor to prevent deadlocks. + TextureClientAutoLock frontLock(mFrontClient, OpenMode::OPEN_READ_ONLY); + if (!frontLock.Succeeded()) { + return; + } + Maybe<TextureClientAutoLock> frontOnWhiteLock; + if (mFrontClientOnWhite) { + frontOnWhiteLock.emplace(mFrontClientOnWhite, OpenMode::OPEN_READ_ONLY); + if (!frontOnWhiteLock->Succeeded()) { + return; + } + } + + // Restrict the DrawTargets and frontBuffer to a scope to make + // sure there is no more external references to the DrawTargets + // when we Unlock the TextureClients. + gfx::DrawTarget* dt = mFrontClient->BorrowDrawTarget(); + gfx::DrawTarget* dtw = mFrontClientOnWhite ? mFrontClientOnWhite->BorrowDrawTarget() : nullptr; + if (dt && dt->IsValid()) { + RefPtr<SourceSurface> surf = dt->Snapshot(); + RefPtr<SourceSurface> surfOnWhite = dtw ? dtw->Snapshot() : nullptr; + SourceRotatedBuffer frontBuffer(surf, + surfOnWhite, + mFrontBufferRect, + mFrontBufferRotation); + UpdateDestinationFrom(frontBuffer, updateRegion); + } else { + // We know this can happen, but we want to track it somewhat, in case it leads + // to other problems. + gfxCriticalNote << "Invalid draw target(s) " << hexa(dt) << " and " << hexa(dtw); + } +} + +void +ContentClientDoubleBuffered::EnsureBackBufferIfFrontBuffer() +{ + if (!mTextureClient && mFrontClient) { + CreateBackBuffer(mFrontBufferRect); + + mBufferRect = mFrontBufferRect; + mBufferRotation = mFrontBufferRotation; + } +} + +void +ContentClientDoubleBuffered::UpdateDestinationFrom(const RotatedBuffer& aSource, + const nsIntRegion& aUpdateRegion) +{ + DrawIterator iter; + while (DrawTarget* destDT = + BorrowDrawTargetForQuadrantUpdate(aUpdateRegion.GetBounds(), BUFFER_BLACK, &iter)) { + bool isClippingCheap = IsClippingCheap(destDT, iter.mDrawRegion); + if (isClippingCheap) { + gfxUtils::ClipToRegion(destDT, iter.mDrawRegion); + } + + aSource.DrawBufferWithRotation(destDT, BUFFER_BLACK, 1.0, CompositionOp::OP_SOURCE); + if (isClippingCheap) { + destDT->PopClip(); + } + // Flush the destination before the sources become inaccessible (Unlock). + destDT->Flush(); + ReturnDrawTargetToBuffer(destDT); + } + + if (aSource.HaveBufferOnWhite()) { + MOZ_ASSERT(HaveBufferOnWhite()); + DrawIterator whiteIter; + while (DrawTarget* destDT = + BorrowDrawTargetForQuadrantUpdate(aUpdateRegion.GetBounds(), BUFFER_WHITE, &whiteIter)) { + bool isClippingCheap = IsClippingCheap(destDT, whiteIter.mDrawRegion); + if (isClippingCheap) { + gfxUtils::ClipToRegion(destDT, whiteIter.mDrawRegion); + } + + aSource.DrawBufferWithRotation(destDT, BUFFER_WHITE, 1.0, CompositionOp::OP_SOURCE); + if (isClippingCheap) { + destDT->PopClip(); + } + // Flush the destination before the sources become inaccessible (Unlock). + destDT->Flush(); + ReturnDrawTargetToBuffer(destDT); + } + } +} + +void +ContentClientSingleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw) +{ + if (mTextureClient) { + DebugOnly<bool> locked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE); + MOZ_ASSERT(locked); + } + if (mTextureClientOnWhite) { + DebugOnly<bool> locked = mTextureClientOnWhite->Lock(OpenMode::OPEN_READ_WRITE); + MOZ_ASSERT(locked); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ContentClient.h b/gfx/layers/client/ContentClient.h new file mode 100644 index 000000000..d26f01464 --- /dev/null +++ b/gfx/layers/client/ContentClient.h @@ -0,0 +1,412 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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_CONTENTCLIENT_H +#define MOZILLA_GFX_CONTENTCLIENT_H + +#include <stdint.h> // for uint32_t +#include "RotatedBuffer.h" // for RotatedContentBuffer, etc +#include "gfxTypes.h" +#include "gfxPlatform.h" // for gfxPlatform +#include "mozilla/Assertions.h" // for MOZ_CRASH +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/layers/CompositableClient.h" // for CompositableClient +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/LayersTypes.h" // for TextureDumpMode +#include "mozilla/layers/TextureClient.h" // for TextureClient +#include "mozilla/mozalloc.h" // for operator delete +#include "ReadbackProcessor.h" // For ReadbackProcessor::Update +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for nsTArray + +namespace mozilla { +namespace gfx { +class DrawTarget; +} // namespace gfx + +namespace layers { + +class PaintedLayer; + +/** + * A compositable client for PaintedLayers. These are different to Image/Canvas + * clients due to sending a valid region across IPC and because we do a lot more + * optimisation work, encapsualted in RotatedContentBuffers. + * + * We use content clients for OMTC and non-OMTC, basic rendering so that + * BasicPaintedLayer has only one interface to deal with. We support single and + * double buffered flavours. For tiled layers, we do not use a ContentClient + * although we do have a ContentHost, and we do use texture clients and texture + * hosts. + * + * The interface presented by ContentClient is used by the BasicPaintedLayer + * methods - PaintThebes, which is the same for MT and OMTC, and PaintBuffer + * which is different (the OMTC one does a little more). The 'buffer' in the + * names of a lot of these method is actually the TextureClient. But, 'buffer' + * for the RotatedContentBuffer (as in SetBuffer) means a gfxSurface. See the + * comments for SetBuffer and SetBufferProvider in RotatedContentBuffer. To keep + * these mapped buffers alive, we store a pointer in mOldTextures if the + * RotatedContentBuffer's surface is not the one from our texture client, once we + * are done painting we unmap the surface/texture client and don't need to keep + * it alive anymore, so we clear mOldTextures. + * + * The sequence for painting is: call BeginPaint on the content client; + * call BeginPaintBuffer on the content client. That will initialise the buffer + * for painting, by calling RotatedContentBuffer::BeginPaint (usually) which + * will call back to ContentClient::FinalizeFrame to finalize update of the + * buffers before drawing (i.e., it finalizes the previous frame). Then call + * BorrowDrawTargetForPainting to get a DrawTarget to paint into. Then paint. + * Then return that DrawTarget using ReturnDrawTarget. + * Call EndPaint on the content client; + * + * SwapBuffers is called in response to the transaction reply from the compositor. + */ +class ContentClient : public CompositableClient +{ +public: + /** + * Creates, configures, and returns a new content client. If necessary, a + * message will be sent to the compositor to create a corresponding content + * host. + */ + static already_AddRefed<ContentClient> CreateContentClient(CompositableForwarder* aFwd); + + explicit ContentClient(CompositableForwarder* aForwarder) + : CompositableClient(aForwarder) + {} + virtual ~ContentClient() + {} + + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix); + + virtual void Clear() = 0; + virtual RotatedContentBuffer::PaintState BeginPaintBuffer(PaintedLayer* aLayer, + uint32_t aFlags) = 0; + virtual gfx::DrawTarget* BorrowDrawTargetForPainting(RotatedContentBuffer::PaintState& aPaintState, + RotatedContentBuffer::DrawIterator* aIter = nullptr) = 0; + virtual void ReturnDrawTargetToBuffer(gfx::DrawTarget*& aReturned) = 0; + + // Called as part of the layers transation reply. Conveys data about our + // buffer(s) from the compositor. If appropriate we should swap references + // to our buffers. + virtual void SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) {} + + // call before and after painting into this content client + virtual void BeginPaint() {} + virtual void EndPaint(nsTArray<ReadbackProcessor::Update>* aReadbackUpdates = nullptr); +}; + +/** + * A ContentClient for use with OMTC. + */ +class ContentClientRemote : public ContentClient +{ +public: + explicit ContentClientRemote(CompositableForwarder* aForwarder) + : ContentClient(aForwarder) + {} + + virtual void Updated(const nsIntRegion& aRegionToDraw, + const nsIntRegion& aVisibleRegion, + bool aDidSelfCopy) = 0; +}; + +// thin wrapper around RotatedContentBuffer, for on-mtc +class ContentClientBasic final : public ContentClient + , protected RotatedContentBuffer +{ +public: + explicit ContentClientBasic(gfx::BackendType aBackend); + + typedef RotatedContentBuffer::PaintState PaintState; + typedef RotatedContentBuffer::ContentType ContentType; + + virtual void Clear() override { RotatedContentBuffer::Clear(); } + virtual PaintState BeginPaintBuffer(PaintedLayer* aLayer, + uint32_t aFlags) override + { + return RotatedContentBuffer::BeginPaint(aLayer, aFlags); + } + virtual gfx::DrawTarget* BorrowDrawTargetForPainting(PaintState& aPaintState, + RotatedContentBuffer::DrawIterator* aIter = nullptr) override + { + return RotatedContentBuffer::BorrowDrawTargetForPainting(aPaintState, aIter); + } + virtual void ReturnDrawTargetToBuffer(gfx::DrawTarget*& aReturned) override + { + BorrowDrawTarget::ReturnDrawTarget(aReturned); + } + + void DrawTo(PaintedLayer* aLayer, + gfx::DrawTarget* aTarget, + float aOpacity, + gfx::CompositionOp aOp, + gfx::SourceSurface* aMask, + const gfx::Matrix* aMaskTransform) + { + RotatedContentBuffer::DrawTo(aLayer, aTarget, aOpacity, aOp, + aMask, aMaskTransform); + } + + virtual void CreateBuffer(ContentType aType, const gfx::IntRect& aRect, uint32_t aFlags, + RefPtr<gfx::DrawTarget>* aBlackDT, RefPtr<gfx::DrawTarget>* aWhiteDT) override; + + virtual TextureInfo GetTextureInfo() const override + { + MOZ_CRASH("GFX: Should not be called on non-remote ContentClient"); + } + +private: + gfx::BackendType mBackend; +}; + +/** + * A ContentClientRemote backed by a RotatedContentBuffer. + * + * When using a ContentClientRemote, SurfaceDescriptors are created on + * the rendering side and destroyed on the compositing side. They are only + * passed from one side to the other when the TextureClient/Hosts are created. + * *Ownership* of the SurfaceDescriptor moves from the rendering side to the + * compositing side with the create message (send from CreateBuffer) which + * tells the compositor that TextureClients have been created and that the + * compositor should assign the corresponding TextureHosts to our corresponding + * ContentHost. + * + * If the size or type of our buffer(s) change(s), then we simply destroy and + * create them. + */ +// Version using new texture clients +class ContentClientRemoteBuffer : public ContentClientRemote + , protected RotatedContentBuffer +{ + using RotatedContentBuffer::BufferRect; + using RotatedContentBuffer::BufferRotation; +public: + explicit ContentClientRemoteBuffer(CompositableForwarder* aForwarder) + : ContentClientRemote(aForwarder) + , RotatedContentBuffer(ContainsVisibleBounds) + , mIsNewBuffer(false) + , mFrontAndBackBufferDiffer(false) + , mSurfaceFormat(gfx::SurfaceFormat::B8G8R8A8) + {} + + typedef RotatedContentBuffer::PaintState PaintState; + typedef RotatedContentBuffer::ContentType ContentType; + + virtual void Clear() override + { + RotatedContentBuffer::Clear(); + mTextureClient = nullptr; + mTextureClientOnWhite = nullptr; + } + + virtual void Dump(std::stringstream& aStream, + const char* aPrefix="", + bool aDumpHtml=false, + TextureDumpMode aCompress=TextureDumpMode::Compress) override; + + virtual PaintState BeginPaintBuffer(PaintedLayer* aLayer, + uint32_t aFlags) override + { + return RotatedContentBuffer::BeginPaint(aLayer, aFlags); + } + virtual gfx::DrawTarget* BorrowDrawTargetForPainting(PaintState& aPaintState, + RotatedContentBuffer::DrawIterator* aIter = nullptr) override + { + return RotatedContentBuffer::BorrowDrawTargetForPainting(aPaintState, aIter); + } + virtual void ReturnDrawTargetToBuffer(gfx::DrawTarget*& aReturned) override + { + BorrowDrawTarget::ReturnDrawTarget(aReturned); + } + + /** + * Begin/End Paint map a gfxASurface from the texture client + * into the buffer of RotatedBuffer. The surface is only + * valid when the texture client is locked, so is mapped out + * of RotatedContentBuffer when we are done painting. + * None of the underlying buffer attributes (rect, rotation) + * are affected by mapping/unmapping. + */ + virtual void BeginPaint() override; + virtual void EndPaint(nsTArray<ReadbackProcessor::Update>* aReadbackUpdates = nullptr) override; + + virtual void Updated(const nsIntRegion& aRegionToDraw, + const nsIntRegion& aVisibleRegion, + bool aDidSelfCopy) override; + + virtual void SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) override; + + // Expose these protected methods from the superclass. + virtual const gfx::IntRect& BufferRect() const + { + return RotatedContentBuffer::BufferRect(); + } + virtual const nsIntPoint& BufferRotation() const + { + return RotatedContentBuffer::BufferRotation(); + } + + virtual void CreateBuffer(ContentType aType, const gfx::IntRect& aRect, uint32_t aFlags, + RefPtr<gfx::DrawTarget>* aBlackDT, RefPtr<gfx::DrawTarget>* aWhiteDT) override; + + virtual TextureFlags ExtraTextureFlags() const + { + return TextureFlags::NO_FLAGS; + } + +protected: + void DestroyBuffers(); + + virtual nsIntRegion GetUpdatedRegion(const nsIntRegion& aRegionToDraw, + const nsIntRegion& aVisibleRegion, + bool aDidSelfCopy); + + void BuildTextureClients(gfx::SurfaceFormat aFormat, + const gfx::IntRect& aRect, + uint32_t aFlags); + + void CreateBackBuffer(const gfx::IntRect& aBufferRect); + + // Ensure we have a valid back buffer if we have a valid front buffer (i.e. + // if a backbuffer has been created.) + virtual void EnsureBackBufferIfFrontBuffer() {} + + // Create the front buffer for the ContentClient/Host pair if necessary + // and notify the compositor that we have created the buffer(s). + virtual void DestroyFrontBuffer() {} + + virtual void AbortTextureClientCreation() + { + mTextureClient = nullptr; + mTextureClientOnWhite = nullptr; + mIsNewBuffer = false; + } + + RefPtr<TextureClient> mTextureClient; + RefPtr<TextureClient> mTextureClientOnWhite; + // keep a record of texture clients we have created and need to keep around + // (for RotatedBuffer to access), then unlock and remove them when we are done + // painting. + nsTArray<RefPtr<TextureClient> > mOldTextures; + + bool mIsNewBuffer; + bool mFrontAndBackBufferDiffer; + gfx::IntSize mSize; + gfx::SurfaceFormat mSurfaceFormat; +}; + +/** + * A double buffered ContentClient. mTextureClient is the back buffer, which + * we draw into. mFrontClient is the front buffer which we may read from, but + * not write to, when the compositor does not have the 'soft' lock. We can write + * into mTextureClient at any time. + * + * The ContentHost keeps a reference to both corresponding texture hosts, in + * response to our UpdateTextureRegion message, the compositor swaps its + * references. In response to the compositor's reply we swap our references + * (in SwapBuffers). + */ +class ContentClientDoubleBuffered : public ContentClientRemoteBuffer +{ +public: + explicit ContentClientDoubleBuffered(CompositableForwarder* aFwd) + : ContentClientRemoteBuffer(aFwd) + {} + + virtual ~ContentClientDoubleBuffered() {} + + virtual void Clear() override + { + ContentClientRemoteBuffer::Clear(); + mFrontClient = nullptr; + mFrontClientOnWhite = nullptr; + } + + virtual void Updated(const nsIntRegion& aRegionToDraw, + const nsIntRegion& aVisibleRegion, + bool aDidSelfCopy) override; + + virtual void SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) override; + + virtual void BeginPaint() override; + + virtual void FinalizeFrame(const nsIntRegion& aRegionToDraw) override; + + virtual void EnsureBackBufferIfFrontBuffer() override; + + virtual TextureInfo GetTextureInfo() const override + { + return TextureInfo(CompositableType::CONTENT_DOUBLE, mTextureFlags); + } + + virtual void Dump(std::stringstream& aStream, + const char* aPrefix="", + bool aDumpHtml=false, + TextureDumpMode aCompress=TextureDumpMode::Compress) override; +protected: + virtual void DestroyFrontBuffer() override; + +private: + void UpdateDestinationFrom(const RotatedBuffer& aSource, + const nsIntRegion& aUpdateRegion); + + virtual void AbortTextureClientCreation() override + { + mTextureClient = nullptr; + mTextureClientOnWhite = nullptr; + mFrontClient = nullptr; + mFrontClientOnWhite = nullptr; + } + + RefPtr<TextureClient> mFrontClient; + RefPtr<TextureClient> mFrontClientOnWhite; + nsIntRegion mFrontUpdatedRegion; + gfx::IntRect mFrontBufferRect; + nsIntPoint mFrontBufferRotation; +}; + +/** + * A single buffered ContentClient. We have a single TextureClient/Host + * which we update and then send a message to the compositor that we are + * done updating. It is not safe for the compositor to use the corresponding + * TextureHost's memory directly, it must upload it to video memory of some + * kind. We are free to modify the TextureClient once we receive reply from + * the compositor. + */ +class ContentClientSingleBuffered : public ContentClientRemoteBuffer +{ +public: + explicit ContentClientSingleBuffered(CompositableForwarder* aFwd) + : ContentClientRemoteBuffer(aFwd) + { + } + virtual ~ContentClientSingleBuffered() {} + + virtual void FinalizeFrame(const nsIntRegion& aRegionToDraw) override; + + virtual TextureInfo GetTextureInfo() const override + { + return TextureInfo(CompositableType::CONTENT_SINGLE, mTextureFlags | ExtraTextureFlags()); + } + + virtual TextureFlags ExtraTextureFlags() const override + { + return TextureFlags::IMMEDIATE_UPLOAD; + } +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/GPUVideoTextureClient.cpp b/gfx/layers/client/GPUVideoTextureClient.cpp new file mode 100644 index 000000000..10d2bbf38 --- /dev/null +++ b/gfx/layers/client/GPUVideoTextureClient.cpp @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 "mozilla/dom/VideoDecoderManagerChild.h" +#include "mozilla/gfx/2D.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +GPUVideoTextureData::GPUVideoTextureData(dom::VideoDecoderManagerChild* aManager, + const SurfaceDescriptorGPUVideo& aSD, + const gfx::IntSize& aSize) + : mManager(aManager) + , mSD(aSD) + , mSize(aSize) +{} + +GPUVideoTextureData::~GPUVideoTextureData() +{ +} + +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.hasIntermediateBuffer = false; + aInfo.hasSynchronization = false; + aInfo.supportsMoz2D = false; + aInfo.canExposeMappedData = false; +} + +already_AddRefed<SourceSurface> +GPUVideoTextureData::GetAsSourceSurface() +{ + return mManager->Readback(mSD); +} + +void +GPUVideoTextureData::Deallocate(LayersIPCChannel* aAllocator) +{ + mManager->DeallocateSurfaceDescriptorGPUVideo(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 000000000..a445e2a7d --- /dev/null +++ b/gfx/layers/client/GPUVideoTextureClient.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 dom { +class VideoDecoderManagerChild; +} +namespace layers { + +class GPUVideoTextureData : public TextureData +{ +public: + GPUVideoTextureData(dom::VideoDecoderManagerChild* aManager, + const SurfaceDescriptorGPUVideo& aSD, + const gfx::IntSize& aSize); + ~GPUVideoTextureData(); + + virtual void FillInfo(TextureData::Info& aInfo) const override; + + virtual bool Lock(OpenMode) override { return true; }; + + virtual void Unlock() override {}; + + virtual bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + virtual void Deallocate(LayersIPCChannel* aAllocator) override; + + virtual void Forget(LayersIPCChannel* aAllocator) override; + + already_AddRefed<gfx::SourceSurface> GetAsSourceSurface(); + + virtual GPUVideoTextureData* AsGPUVideoTextureData() override + { + return this; + } + +protected: + RefPtr<dom::VideoDecoderManagerChild> mManager; + SurfaceDescriptorGPUVideo mSD; + gfx::IntSize mSize; +}; + +} // 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 000000000..1ccb69502 --- /dev/null +++ b/gfx/layers/client/ImageClient.cpp @@ -0,0 +1,300 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 "ClientLayerManager.h" // for ClientLayer +#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/ShadowLayers.h" // for ShadowLayerForwarder +#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::IMAGE_BRIDGE: + result = new ImageClientBridge(aForwarder, aFlags); + 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() +{ + MOZ_ASSERT(GetForwarder()->GetTextureForwarder()->UsesImageBridge()); + + for (auto& b : mBuffers) { + RemoveTexture(b.mTextureClient); + } + mBuffers.Clear(); +} + +/* static */ already_AddRefed<TextureClient> +ImageClient::CreateTextureClientForImage(Image* aImage, KnowsCompositor* aForwarder) +{ + 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(aForwarder, + data->mYSize, data->mCbCrSize, data->mStereoMode, + data->mYUVColorSpace, + 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; + } + } else if (aImage->GetFormat() == ImageFormat::SURFACE_TEXTURE || + aImage->GetFormat() == ImageFormat::EGLIMAGE) { + gfx::IntSize size = aImage->GetSize(); + + if (aImage->GetFormat() == ImageFormat::EGLIMAGE) { + EGLImageImage* typedImage = aImage->AsEGLImageImage(); + texture = EGLImageTextureData::CreateTextureClient( + typedImage, size, aForwarder->GetTextureForwarder(), TextureFlags::DEFAULT); +#ifdef MOZ_WIDGET_ANDROID + } else if (aImage->GetFormat() == ImageFormat::SURFACE_TEXTURE) { + SurfaceTextureImage* typedImage = aImage->AsSurfaceTextureImage(); + texture = AndroidSurfaceTextureData::CreateTextureClient( + typedImage->GetSurfaceTexture(), size, typedImage->GetOriginPos(), + aForwarder->GetTextureForwarder(), TextureFlags::DEFAULT); +#endif + } else { + MOZ_ASSERT(false, "Bad ImageFormat."); + } + } else { + RefPtr<gfx::SourceSurface> surface = aImage->GetAsSourceSurface(); + MOZ_ASSERT(surface); + texture = TextureClient::CreateForDrawing(aForwarder, 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, uint32_t aContentFlags) +{ + AutoTArray<ImageContainer::OwningImage,4> images; + uint32_t generationCounter; + aContainer->GetCurrentImages(&images, &generationCounter); + + if (mLastUpdateGenerationCounter == generationCounter) { + return true; + } + mLastUpdateGenerationCounter = generationCounter; + + for (int32_t i = images.Length() - 1; i >= 0; --i) { + if (!images[i].mImage->IsValid()) { + // Don't try to update to an invalid image. + images.RemoveElementAt(i); + } + } + 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. + 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 || !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.SwapElements(newBuffers); + + return true; +} + +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) +, mLayer(nullptr) +, mType(aType) +, mLastUpdateGenerationCounter(0) +{} + +ImageClientBridge::ImageClientBridge(CompositableForwarder* aFwd, + TextureFlags aFlags) +: ImageClient(aFwd, aFlags, CompositableType::IMAGE_BRIDGE) +, mAsyncContainerID(0) +{ +} + +bool +ImageClientBridge::UpdateImage(ImageContainer* aContainer, uint32_t aContentFlags) +{ + if (!GetForwarder() || !mLayer) { + return false; + } + if (mAsyncContainerID == aContainer->GetAsyncContainerID()) { + return true; + } + mAsyncContainerID = aContainer->GetAsyncContainerID(); + static_cast<ShadowLayerForwarder*>(GetForwarder())->AttachAsyncCompositable(mAsyncContainerID, mLayer); + return true; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ImageClient.h b/gfx/layers/client/ImageClient.h new file mode 100644 index 000000000..4c6e26400 --- /dev/null +++ b/gfx/layers/client/ImageClient.h @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 ClientLayer; +class CompositableForwarder; +class Image; +class ImageContainer; +class ShadowableLayer; +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() {} + + /** + * 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, uint32_t aContentFlags) = 0; + + void SetLayer(ClientLayer* aLayer) { mLayer = aLayer; } + ClientLayer* GetLayer() const { return mLayer; } + + /** + * 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); + +protected: + ImageClient(CompositableForwarder* aFwd, TextureFlags aFlags, + CompositableType aType); + + ClientLayer* mLayer; + 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); + + virtual bool UpdateImage(ImageContainer* aContainer, uint32_t aContentFlags) override; + + virtual void OnDetach() override; + + virtual bool AddTextureClient(TextureClient* aTexture) override; + + virtual TextureInfo GetTextureInfo() const override; + + virtual void FlushAllImages() override; + + ImageClientSingle* AsImageClientSingle() override { return this; } + +protected: + struct Buffer { + RefPtr<TextureClient> mTextureClient; + int32_t mImageSerial; + }; + nsTArray<Buffer> mBuffers; +}; + +/** + * Image class to be used for async image uploads using the image bridge + * protocol. + * We store the ImageBridge id in the TextureClientIdentifier. + */ +class ImageClientBridge : public ImageClient +{ +public: + ImageClientBridge(CompositableForwarder* aFwd, + TextureFlags aFlags); + + virtual bool UpdateImage(ImageContainer* aContainer, uint32_t aContentFlags) override; + virtual bool Connect(ImageContainer* aImageContainer) override { return false; } + + virtual TextureInfo GetTextureInfo() const override + { + return TextureInfo(mType); + } + +protected: + uint64_t mAsyncContainerID; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/SingleTiledContentClient.cpp b/gfx/layers/client/SingleTiledContentClient.cpp new file mode 100644 index 000000000..bcc8691cf --- /dev/null +++ b/gfx/layers/client/SingleTiledContentClient.cpp @@ -0,0 +1,263 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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/SingleTiledContentClient.h" + +#include "ClientTiledPaintedLayer.h" + +namespace mozilla { +namespace layers { + + +SingleTiledContentClient::SingleTiledContentClient(ClientTiledPaintedLayer& aPaintedLayer, + ClientLayerManager* aManager) + : TiledContentClient(aManager, "Single") +{ + MOZ_COUNT_CTOR(SingleTiledContentClient); + + mTiledBuffer = new ClientSingleTiledLayerBuffer(aPaintedLayer, *this, aManager); +} + +void +SingleTiledContentClient::ClearCachedResources() +{ + CompositableClient::ClearCachedResources(); + mTiledBuffer->DiscardBuffers(); +} + +void +SingleTiledContentClient::UpdatedBuffer(TiledBufferType aType) +{ + mForwarder->UseTiledLayerBuffer(this, mTiledBuffer->GetSurfaceDescriptorTiles()); + mTiledBuffer->ClearPaintedRegion(); +} + +/* static */ bool +SingleTiledContentClient::ClientSupportsLayerSize(const gfx::IntSize& aSize, ClientLayerManager* aManager) +{ + int32_t maxTextureSize = aManager->GetMaxTextureSize(); + return aSize.width <= maxTextureSize && aSize.height <= maxTextureSize; +} + +ClientSingleTiledLayerBuffer::ClientSingleTiledLayerBuffer(ClientTiledPaintedLayer& aPaintedLayer, + CompositableClient& aCompositableClient, + ClientLayerManager* aManager) + : ClientTiledLayerBuffer(aPaintedLayer, aCompositableClient) + , mWasLastPaintProgressive(false) + , mFormat(gfx::SurfaceFormat::UNKNOWN) +{ +} + +void +ClientSingleTiledLayerBuffer::ReleaseTiles() +{ + if (!mTile.IsPlaceholderTile()) { + mTile.DiscardBuffers(); + } + mTile.SetTextureAllocator(nullptr); +} + +void +ClientSingleTiledLayerBuffer::DiscardBuffers() +{ + if (!mTile.IsPlaceholderTile()) { + mTile.DiscardFrontBuffer(); + mTile.DiscardBackBuffer(); + } +} + +SurfaceDescriptorTiles +ClientSingleTiledLayerBuffer::GetSurfaceDescriptorTiles() +{ + InfallibleTArray<TileDescriptor> tiles; + + TileDescriptor tileDesc = mTile.GetTileDescriptor(); + tiles.AppendElement(tileDesc); + mTile.mUpdateRect = gfx::IntRect(); + + return SurfaceDescriptorTiles(mValidRegion, + tiles, + mTilingOrigin, + mSize, + 0, 0, 1, 1, + 1.0, + mFrameResolution.xScale, + mFrameResolution.yScale, + mWasLastPaintProgressive); +} + +already_AddRefed<TextureClient> +ClientSingleTiledLayerBuffer::GetTextureClient() +{ + MOZ_ASSERT(mFormat != gfx::SurfaceFormat::UNKNOWN); + return mCompositableClient.CreateTextureClientForDrawing( + gfx::ImageFormatToSurfaceFormat(mFormat), mSize, BackendSelector::Content, + TextureFlags::DISALLOW_BIGIMAGE | TextureFlags::IMMEDIATE_UPLOAD); +} + +void +ClientSingleTiledLayerBuffer::PaintThebes(const nsIntRegion& aNewValidRegion, + const nsIntRegion& aPaintRegion, + const nsIntRegion& aDirtyRegion, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData, + bool aIsProgressive) +{ + mWasLastPaintProgressive = aIsProgressive; + + // Compare layer valid region size to current backbuffer size, discard if not matching. + gfx::IntSize size = aNewValidRegion.GetBounds().Size(); + gfx::IntPoint origin = aNewValidRegion.GetBounds().TopLeft(); + nsIntRegion paintRegion = aPaintRegion; + + RefPtr<TextureClient> discardedFrontBuffer = nullptr; + RefPtr<TextureClient> discardedFrontBufferOnWhite = nullptr; + nsIntRegion discardedValidRegion; + + if (mSize != size || + mTilingOrigin != origin) { + discardedFrontBuffer = mTile.mFrontBuffer; + discardedFrontBufferOnWhite = mTile.mFrontBufferOnWhite; + discardedValidRegion = mValidRegion; + + TILING_LOG("TILING %p: Single-tile valid region changed. Discarding buffers.\n", &mPaintedLayer) +; + ResetPaintedAndValidState(); + mSize = size; + mTilingOrigin = origin; + paintRegion = aNewValidRegion; + } + + SurfaceMode mode; + gfxContentType content = GetContentType(&mode); + mFormat = gfxPlatform::GetPlatform()->OptimalFormatForContent(content); + + if (mTile.IsPlaceholderTile()) { + mTile.SetTextureAllocator(this); + } + + // The dirty region relative to the top-left of the tile. + nsIntRegion tileDirtyRegion = paintRegion.MovedBy(-mTilingOrigin); + + nsIntRegion extraPainted; + RefPtr<TextureClient> backBufferOnWhite; + RefPtr<TextureClient> backBuffer = + mTile.GetBackBuffer(mCompositableClient, + tileDirtyRegion, + content, mode, + extraPainted, + &backBufferOnWhite); + + mTile.mUpdateRect = tileDirtyRegion.GetBounds().Union(extraPainted.GetBounds()); + + extraPainted.MoveBy(mTilingOrigin); + extraPainted.And(extraPainted, aNewValidRegion); + mPaintedRegion.OrWith(paintRegion); + mPaintedRegion.OrWith(extraPainted); + + if (!backBuffer) { + return; + } + + RefPtr<gfx::DrawTarget> dt = backBuffer->BorrowDrawTarget(); + RefPtr<gfx::DrawTarget> dtOnWhite; + if (backBufferOnWhite) { + dtOnWhite = backBufferOnWhite->BorrowDrawTarget(); + } + + if (mode != SurfaceMode::SURFACE_OPAQUE) { + for (auto iter = tileDirtyRegion.RectIter(); !iter.Done(); iter.Next()) { + const gfx::IntRect& rect = iter.Get(); + if (dtOnWhite) { + dt->FillRect(gfx::Rect(rect.x, rect.y, rect.width, rect.height), + gfx::ColorPattern(gfx::Color(0.0, 0.0, 0.0, 1.0))); + dtOnWhite->FillRect(gfx::Rect(rect.x, rect.y, rect.width, rect.height), + gfx::ColorPattern(gfx::Color(1.0, 1.0, 1.0, 1.0))); + } else { + dt->ClearRect(gfx::Rect(rect.x, rect.y, rect.width, rect.height)); + } + } + } + + // If the old frontbuffer was discarded then attempt to copy what we + // can from it to the new backbuffer. + if (discardedFrontBuffer) { + nsIntRegion copyableRegion; + copyableRegion.And(aNewValidRegion, discardedValidRegion); + copyableRegion.SubOut(aDirtyRegion); + + if (!copyableRegion.IsEmpty()) { + TextureClientAutoLock frontLock(discardedFrontBuffer, + OpenMode::OPEN_READ); + if (frontLock.Succeeded()) { + for (auto iter = copyableRegion.RectIter(); !iter.Done(); iter.Next()) { + const gfx::IntRect rect = iter.Get() - discardedValidRegion.GetBounds().TopLeft(); + const gfx::IntPoint dest = iter.Get().TopLeft() - mTilingOrigin; + discardedFrontBuffer->CopyToTextureClient(backBuffer, &rect, &dest); + } + } + + if (discardedFrontBufferOnWhite && backBufferOnWhite) { + TextureClientAutoLock frontOnWhiteLock(discardedFrontBufferOnWhite, + OpenMode::OPEN_READ); + if (frontOnWhiteLock.Succeeded()) { + for (auto iter = copyableRegion.RectIter(); !iter.Done(); iter.Next()) { + const gfx::IntRect rect = iter.Get() - discardedValidRegion.GetBounds().TopLeft(); + const gfx::IntPoint dest = iter.Get().TopLeft() - mTilingOrigin; + + discardedFrontBufferOnWhite->CopyToTextureClient(backBufferOnWhite, + &rect, &dest); + } + } + } + + TILING_LOG("TILING %p: Region copied from discarded frontbuffer %s\n", &mPaintedLayer, Stringify(copyableRegion).c_str()); + + // We don't need to repaint valid content that was just copied. + paintRegion.SubOut(copyableRegion); + } + } + + if (dtOnWhite) { + dt = gfx::Factory::CreateDualDrawTarget(dt, dtOnWhite); + dtOnWhite = nullptr; + } + + { + RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt); + if (!ctx) { + gfxDevCrash(gfx::LogReason::InvalidContext) << "SingleTiledContextClient context problem " << gfx::hexa(dt); + return; + } + ctx->SetMatrix(ctx->CurrentMatrix().Translate(-mTilingOrigin.x, -mTilingOrigin.y)); + + aCallback(&mPaintedLayer, ctx, paintRegion, paintRegion, DrawRegionClip::DRAW, nsIntRegion(), aCallbackData); + } + + // Mark the area we just drew into the back buffer as invalid in the front buffer as they're + // now out of sync. + mTile.mInvalidFront.OrWith(tileDirtyRegion); + + // The new buffer is now validated, remove the dirty region from it. + mTile.mInvalidBack.SubOut(tileDirtyRegion); + + dt = nullptr; + + mTile.Flip(); + UnlockTile(mTile); + + if (backBuffer->HasIntermediateBuffer()) { + // If our new buffer has an internal buffer, we don't want to keep another + // TextureClient around unnecessarily, so discard the back-buffer. + mTile.DiscardBackBuffer(); + } + + mValidRegion = aNewValidRegion; + mLastPaintSurfaceMode = mode; + mLastPaintContentType = content; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/SingleTiledContentClient.h b/gfx/layers/client/SingleTiledContentClient.h new file mode 100644 index 000000000..e1706bd90 --- /dev/null +++ b/gfx/layers/client/SingleTiledContentClient.h @@ -0,0 +1,135 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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_SINGLETILEDCONTENTCLIENT_H +#define MOZILLA_GFX_SINGLETILEDCONTENTCLIENT_H + +#include "TiledContentClient.h" + +namespace mozilla { +namespace layers { + +class ClientTiledPaintedLayer; +class ClientLayerManager; + +/** + * Provide an instance of TiledLayerBuffer backed by drawable TextureClients. + * This buffer provides an implementation of ValidateTile using a + * thebes callback and can support painting using a single paint buffer. + * Whether a single paint buffer is used is controlled by + * gfxPrefs::PerTileDrawing(). + */ +class ClientSingleTiledLayerBuffer + : public ClientTiledLayerBuffer + , public TextureClientAllocator +{ + virtual ~ClientSingleTiledLayerBuffer() {} +public: + ClientSingleTiledLayerBuffer(ClientTiledPaintedLayer& aPaintedLayer, + CompositableClient& aCompositableClient, + ClientLayerManager* aManager); + + // TextureClientAllocator + already_AddRefed<TextureClient> GetTextureClient() override; + void ReturnTextureClientDeferred(TextureClient* aClient) override {} + void ReportClientLost() override {} + + // ClientTiledLayerBuffer + void PaintThebes(const nsIntRegion& aNewValidRegion, + const nsIntRegion& aPaintRegion, + const nsIntRegion& aDirtyRegion, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData, + bool aIsProgressive = false) override; + + bool SupportsProgressiveUpdate() override { return false; } + bool ProgressiveUpdate(nsIntRegion& aValidRegion, + nsIntRegion& aInvalidRegion, + const nsIntRegion& aOldValidRegion, + BasicTiledLayerPaintData* aPaintData, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData) override + { + MOZ_ASSERT(false, "ProgressiveUpdate not supported!"); + return false; + } + + void ResetPaintedAndValidState() override { + mPaintedRegion.SetEmpty(); + mValidRegion.SetEmpty(); + mTile.DiscardBuffers(); + } + + const nsIntRegion& GetValidRegion() override { + return mValidRegion; + } + + bool IsLowPrecision() const override { + return false; + } + + void ReleaseTiles(); + + void DiscardBuffers(); + + SurfaceDescriptorTiles GetSurfaceDescriptorTiles(); + + void ClearPaintedRegion() { + mPaintedRegion.SetEmpty(); + } + +private: + TileClient mTile; + + nsIntRegion mPaintedRegion; + nsIntRegion mValidRegion; + bool mWasLastPaintProgressive; + + /** + * While we're adding tiles, this is used to keep track of the position of + * the top-left of the top-left-most tile. When we come to wrap the tiles in + * TiledDrawTarget we subtract the value of this member from each tile's + * offset so that all the tiles have a positive offset, then add a + * translation to the TiledDrawTarget to compensate. This is important so + * that the mRect of the TiledDrawTarget is always at a positive x/y + * position, otherwise its GetSize() methods will be broken. + */ + gfx::IntPoint mTilingOrigin; + gfx::IntSize mSize; + gfxImageFormat mFormat; +}; + +class SingleTiledContentClient : public TiledContentClient +{ +public: + SingleTiledContentClient(ClientTiledPaintedLayer& aPaintedLayer, + ClientLayerManager* aManager); + +protected: + ~SingleTiledContentClient() + { + MOZ_COUNT_DTOR(SingleTiledContentClient); + + mTiledBuffer->ReleaseTiles(); + } + +public: + static bool ClientSupportsLayerSize(const gfx::IntSize& aSize, ClientLayerManager* aManager); + + virtual void ClearCachedResources() override; + + virtual void UpdatedBuffer(TiledBufferType aType) override; + + virtual ClientTiledLayerBuffer* GetTiledBuffer() override { return mTiledBuffer; } + virtual ClientTiledLayerBuffer* GetLowPrecisionTiledBuffer() override { return nullptr; } + +private: + RefPtr<ClientSingleTiledLayerBuffer> mTiledBuffer; +}; + +} +} + +#endif diff --git a/gfx/layers/client/TextureClient.cpp b/gfx/layers/client/TextureClient.cpp new file mode 100644 index 000000000..7182731bd --- /dev/null +++ b/gfx/layers/client/TextureClient.cpp @@ -0,0 +1,1707 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 "Layers.h" // for Layer, etc +#include "gfx2DGlue.h" +#include "gfxPlatform.h" // for gfxPlatform +#include "mozilla/Atomics.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/TextureClientRecycleAllocator.h" +#include "mozilla/Mutex.h" +#include "nsDebug.h" // for NS_ASSERTION, NS_WARNING, etc +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "ImageContainer.h" // for PlanarYCbCrData, etc +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" // for gfxDebug +#include "mozilla/layers/TextureClientOGL.h" +#include "mozilla/layers/PTextureChild.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" // for CreateDataSourceSurfaceByCloning +#include "nsPrintfCString.h" // for nsPrintfCString +#include "LayersLogging.h" // for AppendToString +#include "gfxUtils.h" // for gfxUtils::GetAsLZ4Base64Str +#include "IPDLActor.h" +#include "BufferTexture.h" +#include "gfxPrefs.h" +#include "mozilla/layers/ShadowLayers.h" + +#ifdef XP_WIN +#include "DeviceManagerD3D9.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/layers/TextureD3D9.h" +#include "mozilla/layers/TextureD3D11.h" +#include "mozilla/layers/TextureDIB.h" +#include "gfxWindowsPlatform.h" +#include "gfx2DGlue.h" +#endif +#ifdef MOZ_X11 +#include "mozilla/layers/TextureClientX11.h" +#ifdef GL_PROVIDER_GLX +#include "GLXLibrary.h" +#endif +#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 { +namespace 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; + bool workAroundSharedSurfaceOwnershipIssue; +}; + +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) + , mMainThreadOnly(false) + , mIPCOpen(false) + , mOwnsTextureData(false) + , mOwnerCalledDestroy(false) + {} + + bool Recv__delete__() override { return true; } + + LayersIPCChannel* GetAllocator() { return mTextureForwarder; } + + void ActorDestroy(ActorDestroyReason why) override; + + bool IPCOpen() const { return mIPCOpen; } + + void Lock() const { if (mCompositableForwarder && mCompositableForwarder->GetTextureForwarder()->UsesImageBridge()) { mLock.Enter(); } } + + void Unlock() const { if (mCompositableForwarder && mCompositableForwarder->GetTextureForwarder()->UsesImageBridge()) { 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 == true); + 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); + + /// The ugly and slow way to destroy the actor. + /// + /// This will block until the Parent actor has handled the Destroy message, + /// and then start the asynchronous handshake (and destruction will already + /// be done on the parent side, when the async part happens). + void DestroySynchronously(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 mMainThreadOnly; + bool mIPCOpen; + bool mOwnsTextureData; + bool mOwnerCalledDestroy; + + friend class TextureClient; + friend void DeallocateTextureClient(TextureDeallocParams params); +}; + + +static void DestroyTextureData(TextureData* aTextureData, LayersIPCChannel* aAllocator, + bool aDeallocate, bool aMainThreadOnly) +{ + if (!aTextureData) { + return; + } + + if (aMainThreadOnly && !NS_IsMainThread()) { + RefPtr<LayersIPCChannel> allocatorRef = aAllocator; + NS_DispatchToMainThread(NS_NewRunnableFunction([aTextureData, allocatorRef, aDeallocate]() -> void { + DestroyTextureData(aTextureData, allocatorRef, aDeallocate, true); + })); + return; + } + + if (aDeallocate) { + aTextureData->Deallocate(aAllocator); + } else { + aTextureData->Forget(aAllocator); + } + delete aTextureData; +} + +void +TextureChild::ActorDestroy(ActorDestroyReason why) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS); + + if (mTextureData) { + DestroyTextureData(mTextureData, GetAllocator(), mOwnsTextureData, mMainThreadOnly); + mTextureData = nullptr; + } +} + +void +TextureChild::Destroy(const TextureDeallocParams& aParams) +{ + MOZ_ASSERT(!mOwnerCalledDestroy); + if (mOwnerCalledDestroy) { + return; + } + + mOwnerCalledDestroy = true; + + // DestroyTextureData will be called by TextureChild::ActorDestroy + mTextureData = aParams.data; + mOwnsTextureData = aParams.clientDeallocation; + + if (!mCompositableForwarder || + !mCompositableForwarder->DestroyInTransaction(this, false)) + { + this->SendDestroy(); + } +} + +void +TextureChild::DestroySynchronously(const TextureDeallocParams& aParams) +{ + MOZ_PERFORMANCE_WARNING("gfx", "TextureClient/Host pair requires synchronous deallocation"); + + MOZ_ASSERT(!mOwnerCalledDestroy); + if (mOwnerCalledDestroy) { + return; + } + + mOwnerCalledDestroy = true; + + DestroyTextureData( + aParams.data, + aParams.allocator, + aParams.clientDeallocation, + mMainThreadOnly); + + if (!IPCOpen()) { + return; + } + + if (!mCompositableForwarder || + !mCompositableForwarder->DestroyInTransaction(this, true)) + { + this->SendDestroySync(); + this->SendDestroy(); + } +} + +/* static */ Atomic<uint64_t> TextureClient::sSerialCounter(0); + +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; + MessageLoop* ipdlMsgLoop = nullptr; + + if (params.allocator) { + ipdlMsgLoop = params.allocator->GetMessageLoop(); + if (!ipdlMsgLoop) { + // An allocator with no message loop 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 (ipdlMsgLoop && MessageLoop::current() != ipdlMsgLoop) { + if (params.syncDeallocation) { + bool done = false; + ReentrantMonitor barrier("DeallocateTextureClient"); + ReentrantMonitorAutoEnter autoMon(barrier); + ipdlMsgLoop->PostTask(NewRunnableFunction(DeallocateTextureClientSyncProxy, + params, &barrier, &done)); + while (!done) { + barrier.Wait(); + } + } else { + ipdlMsgLoop->PostTask(NewRunnableFunction(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 (!ipdlMsgLoop) { + // If we don't have a message loop 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... + // ..except if the lovely mWorkaroundAnnoyingSharedSurfaceOwnershipIssues member + // is set to true. In this case we are in a special situation where this + // TextureClient is in wrapped into another TextureClient which assumes it owns + // our data. This is specific to the gralloc SharedSurface. + bool shouldDeallocate = !params.workAroundSharedSurfaceOwnershipIssue; + DestroyTextureData(params.data, params.allocator, + shouldDeallocate, + false); // main-thread deallocation + return; + } + + if (params.syncDeallocation || !actor->IPCOpen()) { + actor->DestroySynchronously(params); + } else { + actor->Destroy(params); + } +} + +void TextureClient::Destroy(bool aForceSync) +{ + 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; + if (!mWorkaroundAnnoyingSharedSurfaceLifetimeIssues) { + mData = nullptr; + } + + if (data || actor) { + TextureDeallocParams params; + params.actor = actor; + params.allocator = mAllocator; + params.clientDeallocation = !!(mFlags & TextureFlags::DEALLOCATE_CLIENT); + params.workAroundSharedSurfaceOwnershipIssue = mWorkaroundAnnoyingSharedSurfaceOwnershipIssues; + if (mWorkaroundAnnoyingSharedSurfaceLifetimeIssues) { + params.data = nullptr; + } else { + 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) || aForceSync; + + // 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 +{ + return mReadLock && mReadLock->GetReadCount() > 1; +} + +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 && IsReadLocked()) { + 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 && + 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(); + } + + return mIsLocked; +} + +void +TextureClient::Unlock() +{ + MOZ_ASSERT(IsValid()); + MOZ_ASSERT(mIsLocked); + if (!IsValid() || !mIsLocked) { + return; + } + + if (mBorrowedDrawTarget) { + if (mOpenMode & OpenMode::OPEN_WRITE) { + mBorrowedDrawTarget->Flush(); + if (mReadbackSink && !mData->ReadBack(mReadbackSink)) { + // Fallback implementation for reading back, because mData does not + // have a backend-specific implementation and returned false. + RefPtr<SourceSurface> snapshot = mBorrowedDrawTarget->Snapshot(); + RefPtr<DataSourceSurface> dataSurf = snapshot->GetDataSurface(); + mReadbackSink->ProcessReadback(dataSurf); + } + } + + 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(); +} + +void +TextureClient::EnableReadLock() +{ + if (!mReadLock) { + mReadLock = TextureReadLock::Create(mAllocator); + } +} + +void +TextureClient::SerializeReadLock(ReadLockDescriptor& aDescriptor) +{ + 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(); + mReadLock->Serialize(aDescriptor); + mUpdated = false; + } else { + aDescriptor = null_t(); + } +} + +TextureClient::~TextureClient() +{ + mReadLock = nullptr; + Destroy(false); +} + +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 (!NS_IsMainThread()) { + return nullptr; + } + + if (!mBorrowedDrawTarget) { + mBorrowedDrawTarget = mData->BorrowDrawTarget(); +#ifdef DEBUG + mExpectedDtRefs = mBorrowedDrawTarget ? mBorrowedDrawTarget->refCount() : 0; +#endif + } + + return mBorrowedDrawTarget; +} + +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(); + } +} + +void CancelTextureClientRecycle(uint64_t aTextureId, LayersIPCChannel* aAllocator) +{ + if (!aAllocator) { + return; + } + MessageLoop* msgLoop = nullptr; + msgLoop = aAllocator->GetMessageLoop(); + if (!msgLoop) { + return; + } + if (MessageLoop::current() == msgLoop) { + aAllocator->CancelWaitForRecycle(aTextureId); + } else { + msgLoop->PostTask(NewRunnableFunction(CancelTextureClientRecycle, + aTextureId, aAllocator)); + } +} + +void +TextureClient::CancelWaitForRecycle() +{ + if (GetFlags() & TextureFlags::RECYCLE) { + CancelTextureClientRecycle(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()->GetMessageLoop() == mAllocator->GetMessageLoop()); + 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."; + return false; + } + if (currentFwd && currentFwd->GetCompositorBackendType() != aForwarder->GetCompositorBackendType()) { + gfxCriticalError() << "Attempt to move a texture to different compositor backend."; + return false; + } + mActor->mCompositableForwarder = aForwarder; + } + return true; + } + MOZ_ASSERT(!mActor || mActor->mDestroyed, "Cannot use a texture on several IPC channels."); + + SurfaceDescriptor desc; + if (!ToSurfaceDescriptor(desc)) { + return false; + } + + PTextureChild* actor = aForwarder->GetTextureForwarder()->CreateTexture( + desc, + aForwarder->GetCompositorBackendType(), + GetFlags(), + mSerial); + 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; + mActor->mMainThreadOnly = !!(mFlags & TextureFlags::DEALLOCATE_MAIN_THREAD); + + // 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* aForwarder) +{ + MOZ_ASSERT(aForwarder && aForwarder->GetTextureForwarder()->GetMessageLoop() == mAllocator->GetMessageLoop()); + TextureForwarder* fwd = aForwarder->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; + } + + PTextureChild* actor = fwd->CreateTexture( + desc, + aForwarder->GetCompositorBackendType(), + GetFlags(), + mSerial); + 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->mTextureForwarder = fwd; + mActor->mTextureClient = this; + mActor->mMainThreadOnly = !!(mFlags & TextureFlags::DEALLOCATE_MAIN_THREAD); + + // 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 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 +already_AddRefed<TextureClient> +TextureClient::CreateForDrawing(KnowsCompositor* aAllocator, + gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, + BackendSelector aSelector, + TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags) +{ + LayersBackend layersBackend = aAllocator->GetCompositorBackendType(); + return TextureClient::CreateForDrawing(aAllocator->GetTextureForwarder(), + aFormat, aSize, + layersBackend, + aAllocator->GetMaxTextureSize(), + aSelector, + aTextureFlags, + aAllocFlags); +} + +// static +already_AddRefed<TextureClient> +TextureClient::CreateForDrawing(TextureForwarder* aAllocator, + gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, + LayersBackend aLayersBackend, + int32_t aMaxTextureSize, + BackendSelector aSelector, + TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags) +{ + gfx::BackendType moz2DBackend = BackendTypeForBackendSelector(aLayersBackend, aSelector); + + // also test the validity of aAllocator + if (!aAllocator || !aAllocator->IPCOpen()) { + return nullptr; + } + + if (!gfx::Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + TextureData* data = nullptr; + +#ifdef XP_WIN + if (aLayersBackend == LayersBackend::LAYERS_D3D11 && + (moz2DBackend == gfx::BackendType::DIRECT2D || + moz2DBackend == gfx::BackendType::DIRECT2D1_1 || + (!!(aAllocFlags & ALLOC_FOR_OUT_OF_BAND_CONTENT) && + DeviceManagerDx::Get()->GetContentDevice())) && + aSize.width <= aMaxTextureSize && + aSize.height <= aMaxTextureSize && + !(aAllocFlags & ALLOC_UPDATE_FROM_SURFACE)) + { + data = DXGITextureData::Create(aSize, aFormat, aAllocFlags); + } + if (aLayersBackend == LayersBackend::LAYERS_D3D9 && + moz2DBackend == gfx::BackendType::CAIRO && + aAllocator->IsSameProcess() && + aSize.width <= aMaxTextureSize && + aSize.height <= aMaxTextureSize && + NS_IsMainThread() && + DeviceManagerD3D9::GetDevice()) { + data = D3D9TextureData::Create(aSize, aFormat, aAllocFlags); + } + + if (!data && aFormat == SurfaceFormat::B8G8R8X8 && + moz2DBackend == gfx::BackendType::CAIRO && + NS_IsMainThread()) { + data = DIBTextureData::Create(aSize, aFormat, aAllocator); + } +#endif + +#ifdef MOZ_X11 + gfxSurfaceType type = + gfxPlatform::GetPlatform()->ScreenReferenceSurface()->GetType(); + + if (!data && aLayersBackend == LayersBackend::LAYERS_BASIC && + moz2DBackend == gfx::BackendType::CAIRO && + type == gfxSurfaceType::Xlib) + { + data = X11TextureData::Create(aSize, aFormat, aTextureFlags, aAllocator); + } +#ifdef GL_PROVIDER_GLX + if (!data && aLayersBackend == LayersBackend::LAYERS_OPENGL && + type == gfxSurfaceType::Xlib && + aFormat != SurfaceFormat::A8 && + gl::sGLXLibrary.UseTextureFromPixmap()) + { + data = X11TextureData::Create(aSize, aFormat, aTextureFlags, aAllocator); + } +#endif +#endif + +#ifdef XP_MACOSX + if (!data && gfxPrefs::UseIOSurfaceTextures()) { + data = MacIOSurfaceTextureData::Create(aSize, aFormat, moz2DBackend); + } +#endif + + if (data) { + return MakeAndAddRef<TextureClient>(data, aTextureFlags, aAllocator); + } + + if (moz2DBackend == BackendType::SKIA && 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); + } + + // Can't do any better than a buffer texture client. + return TextureClient::CreateForRawBufferAccess(aAllocator, aFormat, aSize, + moz2DBackend, aLayersBackend, + 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_D3D11 && + (moz2DBackend == gfx::BackendType::DIRECT2D || + moz2DBackend == gfx::BackendType::DIRECT2D1_1 || + (!!(aAllocFlags & ALLOC_FOR_OUT_OF_BAND_CONTENT) && + DeviceManagerDx::Get()->GetContentDevice())) && + 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 (aAllocFlags & ALLOC_DISALLOW_BUFFERTEXTURECLIENT) { + return nullptr; + } + + if (!gfx::Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + // D2D backend does not support CreateDrawTargetForData(). Use CAIRO instead. + if (aMoz2DBackend == gfx::BackendType::DIRECT2D || + aMoz2DBackend == gfx::BackendType::DIRECT2D1_1) { + aMoz2DBackend = gfx::BackendType::CAIRO; + } + + TextureData* texData = BufferTextureData::Create(aSize, aFormat, aMoz2DBackend, + aLayersBackend, aTextureFlags, + aAllocFlags, aAllocator); + if (!texData) { + return nullptr; + } + + return MakeAndAddRef<TextureClient>(texData, aTextureFlags, aAllocator); +} + +// static +already_AddRefed<TextureClient> +TextureClient::CreateForYCbCr(KnowsCompositor* aAllocator, + gfx::IntSize aYSize, + gfx::IntSize aCbCrSize, + StereoMode aStereoMode, + YUVColorSpace aYUVColorSpace, + TextureFlags aTextureFlags) +{ + if (!aAllocator || !aAllocator->GetLayersIPCActor()->IPCOpen()) { + return nullptr; + } + + if (!gfx::Factory::AllowedSurfaceSize(aYSize)) { + return nullptr; + } + + TextureData* data = BufferTextureData::CreateForYCbCr(aAllocator, aYSize, aCbCrSize, + aStereoMode, aYUVColorSpace, + aTextureFlags); + if (!data) { + return nullptr; + } + + return MakeAndAddRef<TextureClient>(data, aTextureFlags, + aAllocator->GetTextureForwarder()); +} + +// static +already_AddRefed<TextureClient> +TextureClient::CreateForYCbCrWithBufferSize(KnowsCompositor* aAllocator, + size_t aSize, + YUVColorSpace aYUVColorSpace, + TextureFlags aTextureFlags) +{ + if (!aAllocator || !aAllocator->GetLayersIPCActor()->IPCOpen()) { + return nullptr; + } + + TextureData* data = + BufferTextureData::CreateForYCbCrWithBufferSize(aAllocator, aSize, aYUVColorSpace, + 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) +, mUpdated(false) +, mAddedToCompositableClient(false) +, mWorkaroundAnnoyingSharedSurfaceLifetimeIssues(false) +, mWorkaroundAnnoyingSharedSurfaceOwnershipIssues(false) +, mFwdTransactionId(0) +, mSerial(++sSerialCounter) +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL +, mPoolTracker(nullptr) +#endif +{ + mData->FillInfo(mInfo); + mFlags |= mData->GetTextureFlags(); +} + +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::PrintInfo(std::stringstream& aStream, const char* aPrefix) +{ + aStream << aPrefix; + aStream << nsPrintfCString("TextureClient (0x%p)", this).get(); + AppendToString(aStream, GetSize(), " [size=", "]"); + AppendToString(aStream, GetFormat(), " [format=", "]"); + AppendToString(aStream, mFlags, " [flags=", "]"); + +#ifdef MOZ_DUMP_PAINTING + if (gfxPrefs::LayersDumpTexture() || profiler_feature_active("layersdump")) { + nsAutoCString pfx(aPrefix); + pfx += " "; + + aStream << "\n" << pfx.get() << "Surface: "; + RefPtr<gfx::DataSourceSurface> dSurf = GetAsSurface(); + if (dSurf) { + aStream << gfxUtils::GetAsLZ4Base64Str(dSurf).get(); + } + } +#endif +} + +class MemoryTextureReadLock : public TextureReadLock { +public: + MemoryTextureReadLock(); + + ~MemoryTextureReadLock(); + + virtual int32_t ReadLock() override; + + virtual int32_t ReadUnlock() override; + + virtual int32_t GetReadCount() override; + + virtual LockType GetType() override { return TYPE_MEMORY; } + + virtual bool IsValid() const override { return true; }; + + virtual bool Serialize(ReadLockDescriptor& aOutput) override; + + 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 TextureReadLock { +public: + struct ShmReadLockInfo { + int32_t readCount; + }; + + explicit ShmemTextureReadLock(LayersIPCChannel* aAllocator); + + ~ShmemTextureReadLock(); + + virtual int32_t ReadLock() override; + + virtual int32_t ReadUnlock() override; + + virtual int32_t GetReadCount() override; + + virtual bool IsValid() const override { return mAllocSuccess; }; + + virtual LockType GetType() override { return TYPE_SHMEM; } + + virtual bool Serialize(ReadLockDescriptor& aOutput) 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; +}; + +// static +already_AddRefed<TextureReadLock> +TextureReadLock::Deserialize(const 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::Tnull_t: { + return nullptr; + } + default: { + // Invalid descriptor. + MOZ_DIAGNOSTIC_ASSERT(false); + } + } + return nullptr; +} +// static +already_AddRefed<TextureReadLock> +TextureReadLock::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) +{ + // 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; +} + +int32_t +MemoryTextureReadLock::ReadLock() +{ + NS_ASSERT_OWNINGTHREAD(MemoryTextureReadLock); + + return PR_ATOMIC_INCREMENT(&mReadCount); +} + +int32_t +MemoryTextureReadLock::ReadUnlock() +{ + int32_t readCount = PR_ATOMIC_DECREMENT(&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); +#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) +{ + aOutput = ReadLockDescriptor(GetShmemSection()); + return true; +} + +int32_t +ShmemTextureReadLock::ReadLock() { + NS_ASSERT_OWNINGTHREAD(ShmemTextureReadLock); + if (!mAllocSuccess) { + return 0; + } + ShmReadLockInfo* info = GetShmReadLockInfoPtr(); + return PR_ATOMIC_INCREMENT(&info->readCount); +} + +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()->DeallocShmemSection(mShmemSection); + } else { + // we are on the compositor process + FixedSizeSmallShmemSectionAllocator::FreeShmemSection(mShmemSection); + } + } + return readCount; +} + +int32_t +ShmemTextureReadLock::GetReadCount() { + NS_ASSERT_OWNINGTHREAD(ShmemTextureReadLock); + if (!mAllocSuccess) { + return 0; + } + ShmReadLockInfo* info = GetShmReadLockInfoPtr(); + return info->readCount; +} + +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; + } + + MappedYCbCrTextureData srcData; + srcData.y.data = aData.mYChannel; + srcData.y.size = aData.mYSize; + srcData.y.stride = aData.mYStride; + srcData.y.skip = aData.mYSkip; + srcData.cb.data = aData.mCbChannel; + srcData.cb.size = aData.mCbCrSize; + srcData.cb.stride = aData.mCbCrStride; + srcData.cb.skip = aData.mCbSkip; + srcData.cr.data = aData.mCrChannel; + srcData.cr.size = aData.mCbCrSize; + srcData.cr.stride = aData.mCbCrStride; + srcData.cr.skip = aData.mCrSkip; + 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<SyncObject> +SyncObject::CreateSyncObject(SyncHandle aHandle) +{ + if (!aHandle) { + return nullptr; + } + +#ifdef XP_WIN + return MakeAndAddRef<SyncObjectD3D11>(aHandle); +#else + MOZ_ASSERT_UNREACHABLE(); + return nullptr; +#endif +} + +already_AddRefed<TextureClient> +TextureClient::CreateWithData(TextureData* aData, TextureFlags aFlags, LayersIPCChannel* aAllocator) +{ + if (!aData) { + return nullptr; + } + return MakeAndAddRef<TextureClient>(aData, aFlags, aAllocator); +} + +bool +MappedYCbCrChannelData::CopyInto(MappedYCbCrChannelData& aDst) +{ + if (!data || !aDst.data || size != aDst.size) { + return false; + } + + if (stride == aDst.stride) { + // 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; + } + + for (int32_t i = 0; i < size.height; ++i) { + if (aDst.skip == 0 && skip == 0) { + // fast-ish path + memcpy(aDst.data + i * aDst.stride, + data + i * stride, + size.width); + } else { + // slow path + uint8_t* src = data + i * stride; + uint8_t* dst = aDst.data + i * aDst.stride; + for (int32_t j = 0; j < size.width; ++j) { + *dst = *src; + src += 1 + skip; + dst += 1 + aDst.skip; + } + } + } + return true; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/TextureClient.h b/gfx/layers/client/TextureClient.h new file mode 100644 index 000000000..d28f37cf5 --- /dev/null +++ b/gfx/layers/client/TextureClient.h @@ -0,0 +1,826 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- +// * 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 "ImageTypes.h" // for StereoMode +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#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/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#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/LayersTypes.h" +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/mozalloc.h" // for operator delete +#include "mozilla/gfx/CriticalSection.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsISupportsImpl.h" // for TextureImage::AddRef, etc +#include "GfxTexturesReporter.h" +#include "pratom.h" +#include "nsThreadUtils.h" + +class gfxImageSurface; + +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 AsyncTransactionWaiter; +class BufferTextureData; +class CompositableForwarder; +class KnowsCompositor; +class LayersIPCChannel; +class CompositableClient; +struct PlanarYCbCrData; +class Image; +class PTextureChild; +class TextureChild; +class TextureData; +class GPUVideoTextureData; +struct RawTextureBuffer; +class RawYCbCrTextureBuffer; +class TextureClient; +class ITextureClientRecycleAllocator; +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL +class TextureClientPool; +#endif +class TextureForwarder; +class KeepAlive; + +/** + * 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, +}; + +#ifdef XP_WIN +typedef void* SyncHandle; +#else +typedef uintptr_t SyncHandle; +#endif // XP_WIN + +class SyncObject : public RefCounted<SyncObject> +{ +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SyncObject) + virtual ~SyncObject() { } + + static already_AddRefed<SyncObject> CreateSyncObject(SyncHandle aHandle); + + enum class SyncType { + D3D11, + }; + + virtual SyncType GetSyncType() = 0; + virtual void FinalizeFrame() = 0; + virtual bool IsSyncObjectValid() = 0; + +protected: + SyncObject() { } +}; + +/** + * This class may be used to asynchronously receive an update when the content + * drawn to this texture client is available for reading in CPU memory. This + * can only be used on texture clients that support draw target creation. + */ +class TextureReadbackSink +{ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TextureReadbackSink) +public: + /** + * Callback function to implement in order to receive a DataSourceSurface + * containing the data read back from the texture client. This will always + * be called on the main thread, and this may not hold on to the + * DataSourceSurface beyond the execution of this function. + */ + virtual void ProcessReadback(gfx::DataSourceSurface *aSourceSurface) = 0; + +protected: + virtual ~TextureReadbackSink() {} +}; + +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; + + 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; + +// 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() {} + +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TextureReadLock) + + virtual int32_t ReadLock() = 0; + virtual int32_t ReadUnlock() = 0; + virtual int32_t GetReadCount() = 0; + virtual bool IsValid() const = 0; + + static already_AddRefed<TextureReadLock> + Create(LayersIPCChannel* aAllocator); + + static already_AddRefed<TextureReadLock> + Deserialize(const ReadLockDescriptor& aDescriptor, ISurfaceAllocator* aAllocator); + + virtual bool Serialize(ReadLockDescriptor& aOutput) = 0; + + enum LockType { + TYPE_MEMORY, + TYPE_SHMEM + }; + virtual LockType GetType() = 0; + +protected: + NS_DECL_OWNINGTHREAD +}; + +#ifdef XP_WIN +class D3D11TextureData; +#endif + +class TextureData { +public: + struct Info { + gfx::IntSize size; + gfx::SurfaceFormat format; + bool hasIntermediateBuffer; + bool hasSynchronization; + bool supportsMoz2D; + bool canExposeMappedData; + + Info() + : format(gfx::SurfaceFormat::UNKNOWN) + , hasIntermediateBuffer(false) + , hasSynchronization(false) + , supportsMoz2D(false) + , canExposeMappedData(false) + {} + }; + + TextureData() { MOZ_COUNT_CTOR(TextureData); } + + virtual ~TextureData() { MOZ_COUNT_DTOR(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; } + + 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 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 bool ReadBack(TextureReadbackSink* aReadbackSink) { return false; } + + virtual void SyncWithObject(SyncObject* aFence) {}; + + virtual TextureFlags GetTextureFlags() const { return TextureFlags::NO_FLAGS; } + +#ifdef XP_WIN + virtual D3D11TextureData* AsD3D11TextureData() { + return nullptr; + } +#endif + + virtual BufferTextureData* AsBufferTextureData() { return nullptr; } + + virtual GPUVideoTextureData* AsGPUVideoTextureData() { return nullptr; } +}; + +/** + * 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 two Texture pairs). + * In order to send several different buffers to the compositor side, use + * several TextureClients. + */ +class TextureClient + : public AtomicRefCountedWithFinalize<TextureClient> +{ +public: + explicit 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, + gfx::IntSize aYSize, + gfx::IntSize aCbCrSize, + StereoMode aStereoMode, + YUVColorSpace aYUVColorSpace, + 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 (can beaccessed through raw + // pointers) with a certain buffer size. It's unfortunate that we need this. + // providing format and sizes could let us do more optimization. + static already_AddRefed<TextureClient> + CreateForYCbCrWithBufferSize(KnowsCompositor* aAllocator, + size_t aSize, + YUVColorSpace aYUVColorSpace, + TextureFlags aTextureFlags); + + // 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; } + + /** + * Indicates whether the TextureClient implementation is backed by an + * in-memory buffer. The consequence of this is that locking the + * TextureClient does not contend with locking the texture on the host side. + */ + bool HasIntermediateBuffer() const { return mInfo.hasIntermediateBuffer; } + + 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(); + + /** + * 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(); + + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix); + + /** + * 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* aForwarder); + + /** + * 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. + * If sync is true, the destruction will be synchronous regardless of the + * texture's flags (bad for performance, use with care). + */ + void Destroy(bool sync = false); + + /** + * 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())); + } + + /** + * This sets the readback sink that this texture is to use. This will + * receive the data for this texture as soon as it becomes available after + * texture unlock. + */ + virtual void SetReadbackSink(TextureReadbackSink* aReadbackSink) { + mReadbackSink = aReadbackSink; + } + + void SyncWithObject(SyncObject* aFence) { mData->SyncWithObject(aFence); } + + 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 CancelWaitForRecycle(); + + /** + * 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; } + + void EnableReadLock(); + + TextureReadLock* GetReadLock() { return mReadLock; } + + bool IsReadLocked() const; + + void SerializeReadLock(ReadLockDescriptor& aDescriptor); + +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, + LayersBackend aLayersBackend, + int32_t aMaxTextureSize, + 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); + + /** + * 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; + // 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; + + bool mWorkaroundAnnoyingSharedSurfaceLifetimeIssues; + bool mWorkaroundAnnoyingSharedSurfaceOwnershipIssues; + + RefPtr<TextureReadbackSink> mReadbackSink; + + uint64_t mFwdTransactionId; + + // Serial id of TextureClient. It is unique in current process. + const uint64_t mSerial; + // 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&); + +#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) + : 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 +{ + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER; + +public: + TextureClientAutoLock(TextureClient* aTexture, OpenMode aMode + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mTexture(aTexture), + mSucceeded(false) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + + 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; +}; + +class KeepAlive +{ +public: + virtual ~KeepAlive() {} +}; + +template<typename T> +class TKeepAlive : public KeepAlive +{ +public: + explicit TKeepAlive(T* aData) : mData(aData) {} +protected: + RefPtr<T> mData; +}; + +/// Convenience function to set the content of ycbcr texture. +bool UpdateYCbCrTextureClient(TextureClient* aTexture, const PlanarYCbCrData& aData); + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/TextureClientPool.cpp b/gfx/layers/client/TextureClientPool.cpp new file mode 100644 index 000000000..c556a791e --- /dev/null +++ b/gfx/layers/client/TextureClientPool.cpp @@ -0,0 +1,339 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- +* 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/layers/TiledContentClient.h" + +#include "gfxPrefs.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(LayersBackend aLayersBackend, + int32_t aMaxTextureSize, + gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, + TextureFlags aFlags, + uint32_t aShrinkTimeoutMsec, + uint32_t aClearTimeoutMsec, + uint32_t aInitialPoolSize, + uint32_t aPoolUnusedSize, + TextureForwarder* aAllocator) + : mBackend(aLayersBackend) + , mMaxTextureSize(aMaxTextureSize) + , 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 = do_CreateInstance("@mozilla.org/timer;1"); + mClearTimer = do_CreateInstance("@mozilla.org/timer;1"); + 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.size()) { + AllocateTextureClient(); + } + + if (!mTextureClients.size()) { + // 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); + + RefPtr<TextureClient> newClient; + if (gfxPrefs::ForceShmemTiles()) { + // gfx::BackendType::NONE means use the content backend + newClient = + TextureClient::CreateForRawBufferAccess(mSurfaceAllocator, + mFormat, mSize, + gfx::BackendType::NONE, + mBackend, + mFlags, ALLOC_DEFAULT); + } else { + newClient = + TextureClient::CreateForDrawing(mSurfaceAllocator, + mFormat, mSize, + mBackend, + mMaxTextureSize, + BackendSelector::Content, + mFlags); + } + + 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->InitWithFuncCallback(ShrinkCallback, this, mShrinkTimeoutMsec, + nsITimer::TYPE_ONE_SHOT); + } + + // Clear pool after a period of inactivity to reduce memory consumption + if (mClearTimeoutMsec) { + TCP_LOG("TexturePool %p scheduling a clear\n", this); + mClearTimer->InitWithFuncCallback(ClearCallback, this, mClearTimeoutMsec, + nsITimer::TYPE_ONE_SHOT); + } +} + +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.size()) { + 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()->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; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/TextureClientPool.h b/gfx/layers/client/TextureClientPool.h new file mode 100644 index 000000000..642c14bf5 --- /dev/null +++ b/gfx/layers/client/TextureClientPool.h @@ -0,0 +1,180 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- +* 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 "TextureClient.h" +#include "nsITimer.h" +#include <stack> +#include <list> + +namespace mozilla { +namespace layers { + +class ISurfaceAllocator; +class TextureForwarder; +class TextureReadLock; + +class TextureClientAllocator +{ +protected: + virtual ~TextureClientAllocator() {} +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 +{ + ~TextureClientPool(); + +public: + TextureClientPool(LayersBackend aBackend, + int32_t aMaxTextureSize, + 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. + */ + virtual void ReportClientLost() override; + + /** + * Calling this will cause the pool to attempt to relinquish any unused + * clients. + */ + void Clear(); + + LayersBackend GetBackend() const { return mBackend; } + int32_t GetMaxTextureSize() const { return mMaxTextureSize; } + 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(); + + /// Backend passed to the TextureClient for buffer creation. + LayersBackend mBackend; + + // Max texture size passed to the TextureClient for buffer creation. + int32_t mMaxTextureSize; + + /// 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; + + // On b2g gonk, std::queue might be a better choice. + // On ICS, fence wait happens implicitly before drawing. + // Since JB, fence wait happens explicitly when fetching a client from the pool. + 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 000000000..6a06d3f68 --- /dev/null +++ b/gfx/layers/client/TextureClientRecycleAllocator.cpp @@ -0,0 +1,279 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- +* 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 +{ + ~TextureClientHolder() {} +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 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* aAllocator) override + { + return mAllocator->Allocate(mFormat, + mSize, + mSelector, + mTextureFlags, + mAllocationFlags); + } + +protected: + TextureClientRecycleAllocator* mAllocator; +}; + +YCbCrTextureClientAllocationHelper::YCbCrTextureClientAllocationHelper(const PlanarYCbCrData& aData, + TextureFlags aTextureFlags) + : ITextureClientAllocationHelper(gfx::SurfaceFormat::YUV, + aData.mYSize, + BackendSelector::Content, + aTextureFlags, + ALLOC_DEFAULT) + , mData(aData) +{ +} + +bool +YCbCrTextureClientAllocationHelper::IsCompatible(TextureClient* aTextureClient) +{ + MOZ_ASSERT(aTextureClient->GetFormat() == gfx::SurfaceFormat::YUV); + + BufferTextureData* bufferData = aTextureClient->GetInternalData()->AsBufferTextureData(); + if (!bufferData || + aTextureClient->GetSize() != mData.mYSize || + bufferData->GetCbCrSize().isNothing() || + bufferData->GetCbCrSize().ref() != mData.mCbCrSize || + bufferData->GetYUVColorSpace().isNothing() || + bufferData->GetYUVColorSpace().ref() != mData.mYUVColorSpace || + bufferData->GetStereoMode().isNothing() || + bufferData->GetStereoMode().ref() != mData.mStereoMode) { + return false; + } + return true; +} + +already_AddRefed<TextureClient> +YCbCrTextureClientAllocationHelper::Allocate(KnowsCompositor* aAllocator) +{ + return TextureClient::CreateForYCbCr(aAllocator, + mData.mYSize, mData.mCbCrSize, + mData.mStereoMode, + mData.mYUVColorSpace, + mTextureFlags); +} + +TextureClientRecycleAllocator::TextureClientRecycleAllocator(KnowsCompositor* aAllocator) + : mSurfaceAllocator(aAllocator) + , 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) { + return nullptr; + } + if (!mPooledClients.empty()) { + textureHolder = mPooledClients.top(); + mPooledClients.pop(); + // If a pooled TextureClient is not compatible, release it. + if (!aHelper.IsCompatible(textureHolder->GetTextureClient())) { + // Release TextureClient. + RefPtr<Runnable> task = new TextureClientReleaseTask(textureHolder->GetTextureClient()); + textureHolder->ClearTextureClient(); + textureHolder = nullptr; + mSurfaceAllocator->GetTextureForwarder()->GetMessageLoop()->PostTask(task.forget()); + } else { + textureHolder->GetTextureClient()->RecycleTexture(aHelper.mTextureFlags); + } + } + } + + if (!textureHolder) { + // Allocate new TextureClient + RefPtr<TextureClient> texture = aHelper.Allocate(mSurfaceAllocator); + 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(mSurfaceAllocator, 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 000000000..23ba78991 --- /dev/null +++ b/gfx/layers/client/TextureClientRecycleAllocator.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- +* 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() {} + +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* aAllocator) = 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 YCbCrTextureClientAllocationHelper : public ITextureClientAllocationHelper +{ +public: + YCbCrTextureClientAllocationHelper(const PlanarYCbCrData& aData, + TextureFlags aTextureFlags); + + bool IsCompatible(TextureClient* aTextureClient) override; + + already_AddRefed<TextureClient> Allocate(KnowsCompositor* aAllocator) override; + +protected: + const PlanarYCbCrData& mData; +}; + + +/** + * 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* aAllocator); + + 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(); + +protected: + virtual already_AddRefed<TextureClient> + Allocate(gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, + BackendSelector aSelector, + TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags); + + RefPtr<KnowsCompositor> mSurfaceAllocator; + + friend class DefaultTextureClientAllocationHelper; + void RecycleTextureClient(TextureClient* aClient) override; + + static const uint32_t kMaxPooledSized = 2; + uint32_t mMaxPooledSize; + + std::map<TextureClient*, RefPtr<TextureClientHolder> > mInUseClients; + + // On b2g gonk, std::queue might be a better choice. + // On ICS, fence wait happens implicitly before drawing. + // Since JB, fence wait happens explicitly when fetching a client from the pool. + // stack is good from Graphics cache usage point of view. + std::stack<RefPtr<TextureClientHolder> > mPooledClients; + Mutex mLock; + 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 000000000..b0cbfec21 --- /dev/null +++ b/gfx/layers/client/TextureClientSharedSurface.cpp @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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" + +using namespace mozilla::gl; + +namespace mozilla { +namespace layers { + + +SharedSurfaceTextureData::SharedSurfaceTextureData(UniquePtr<gl::SharedSurface> surf) + : mSurf(Move(surf)) +{} + +SharedSurfaceTextureData::~SharedSurfaceTextureData() +{} + +void +SharedSurfaceTextureData::Deallocate(LayersIPCChannel*) +{} + +void +SharedSurfaceTextureData::FillInfo(TextureData::Info& aInfo) const +{ + aInfo.size = mSurf->mSize; + aInfo.format = gfx::SurfaceFormat::UNKNOWN; + aInfo.hasIntermediateBuffer = false; + aInfo.hasSynchronization = false; + aInfo.supportsMoz2D = false; + aInfo.canExposeMappedData = false; +} + +bool +SharedSurfaceTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) +{ + return mSurf->ToSurfaceDescriptor(&aOutDescriptor); +} + + +SharedSurfaceTextureClient::SharedSurfaceTextureClient(SharedSurfaceTextureData* aData, + TextureFlags aFlags, + LayersIPCChannel* aAllocator) +: TextureClient(aData, aFlags, aAllocator) +{ + mWorkaroundAnnoyingSharedSurfaceLifetimeIssues = true; +} + +already_AddRefed<SharedSurfaceTextureClient> +SharedSurfaceTextureClient::Create(UniquePtr<gl::SharedSurface> surf, gl::SurfaceFactory* factory, + LayersIPCChannel* aAllocator, TextureFlags aFlags) +{ + if (!surf) { + return nullptr; + } + TextureFlags flags = aFlags | TextureFlags::RECYCLE | surf->GetTextureFlags(); + SharedSurfaceTextureData* data = new SharedSurfaceTextureData(Move(surf)); + return MakeAndAddRef<SharedSurfaceTextureClient>(data, flags, aAllocator); +} + +SharedSurfaceTextureClient::~SharedSurfaceTextureClient() +{ + // XXX - Things break when using the proper destruction handshake with + // SharedSurfaceTextureData because the TextureData outlives its gl + // context. Having a strong reference to the gl context creates a cycle. + // This needs to be fixed in a better way, though, because deleting + // the TextureData here can race with the compositor and cause flashing. + TextureData* data = mData; + mData = nullptr; + + Destroy(); + + if (data) { + // Destroy mData right away without doing the proper deallocation handshake, + // because SharedSurface depends on things that may not outlive the texture's + // destructor so we can't wait until we know the compositor isn't using the + // texture anymore. + // It goes without saying that this is really bad and we should fix the bugs + // that block doing the right thing such as bug 1224199 sooner rather than + // later. + delete data; + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/TextureClientSharedSurface.h b/gfx/layers/client/TextureClientSharedSurface.h new file mode 100644 index 000000000..cfe5085f8 --- /dev/null +++ b/gfx/layers/client/TextureClientSharedSurface.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 +{ +protected: + const UniquePtr<gl::SharedSurface> mSurf; + + friend class SharedSurfaceTextureClient; + + explicit SharedSurfaceTextureData(UniquePtr<gl::SharedSurface> surf); +public: + + ~SharedSurfaceTextureData(); + + virtual bool Lock(OpenMode) override { return false; } + + virtual void Unlock() override {} + + virtual void FillInfo(TextureData::Info& aInfo) const override; + + virtual bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + virtual void Deallocate(LayersIPCChannel*) override; + + gl::SharedSurface* Surf() const { return mSurf.get(); } +}; + +class SharedSurfaceTextureClient : public TextureClient +{ +public: + SharedSurfaceTextureClient(SharedSurfaceTextureData* aData, + TextureFlags aFlags, + LayersIPCChannel* aAllocator); + + ~SharedSurfaceTextureClient(); + + static already_AddRefed<SharedSurfaceTextureClient> + Create(UniquePtr<gl::SharedSurface> surf, gl::SurfaceFactory* factory, + LayersIPCChannel* aAllocator, TextureFlags aFlags); + + gl::SharedSurface* Surf() const { + return static_cast<const SharedSurfaceTextureData*>(GetInternalData())->Surf(); + } +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_TEXTURECLIENT_SHAREDSURFACE_H diff --git a/gfx/layers/client/TiledContentClient.cpp b/gfx/layers/client/TiledContentClient.cpp new file mode 100644 index 000000000..c5677a8a3 --- /dev/null +++ b/gfx/layers/client/TiledContentClient.cpp @@ -0,0 +1,1430 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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/TiledContentClient.h" +#include <math.h> // for ceil, ceilf, floor +#include <algorithm> +#include "ClientTiledPaintedLayer.h" // for ClientTiledPaintedLayer +#include "GeckoProfiler.h" // for PROFILER_LABEL +#include "ClientLayerManager.h" // for ClientLayerManager +#include "gfxContext.h" // for gfxContext, etc +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxPrefs.h" // for gfxPrefs +#include "gfxRect.h" // for gfxRect +#include "mozilla/MathAlgorithms.h" // for Abs +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Tools.h" // for BytesPerPixel +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/CompositorBridgeChild.h" // for CompositorBridgeChild +#include "mozilla/layers/LayerMetricsWrapper.h" +#include "mozilla/layers/ShadowLayers.h" // for ShadowLayerForwarder +#include "TextureClientPool.h" +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for gfxContext::AddRef, etc +#include "nsExpirationTracker.h" // for nsExpirationTracker +#include "nsMathUtils.h" // for NS_lroundf +#include "LayersLogging.h" +#include "UnitTransforms.h" // for TransformTo +#include "mozilla/UniquePtr.h" + +// This is the minimum area that we deem reasonable to copy from the front buffer to the +// back buffer on tile updates. If the valid region is smaller than this, we just +// redraw it and save on the copy (and requisite surface-locking involved). +#define MINIMUM_TILE_COPY_AREA (1.f/16.f) + +#ifdef GFX_TILEDLAYER_DEBUG_OVERLAY +#include "cairo.h" +#include <sstream> +using mozilla::layers::Layer; +static void DrawDebugOverlay(mozilla::gfx::DrawTarget* dt, int x, int y, int width, int height) +{ + gfxContext c(dt); + + // Draw border + c.NewPath(); + c.SetDeviceColor(Color(0.f, 0.f, 0.f)); + c.Rectangle(gfxRect(0, 0, width, height)); + c.Stroke(); + + // Build tile description + std::stringstream ss; + ss << x << ", " << y; + + // Draw text using cairo toy text API + // XXX: this drawing will silently fail if |dt| doesn't have a Cairo backend + cairo_t* cr = gfxFont::RefCairo(dt); + cairo_set_font_size(cr, 25); + cairo_text_extents_t extents; + cairo_text_extents(cr, ss.str().c_str(), &extents); + + int textWidth = extents.width + 6; + + c.NewPath(); + c.SetDeviceColor(Color(0.f, 0.f, 0.f)); + c.Rectangle(gfxRect(gfxPoint(2,2),gfxSize(textWidth, 30))); + c.Fill(); + + c.NewPath(); + c.SetDeviceColor(Color(1.0, 0.0, 0.0)); + c.Rectangle(gfxRect(gfxPoint(2,2),gfxSize(textWidth, 30))); + c.Stroke(); + + c.NewPath(); + cairo_move_to(cr, 4, 28); + cairo_show_text(cr, ss.str().c_str()); + +} + +#endif + +namespace mozilla { + +using namespace gfx; + +namespace layers { + + +MultiTiledContentClient::MultiTiledContentClient(ClientTiledPaintedLayer& aPaintedLayer, + ClientLayerManager* aManager) + : TiledContentClient(aManager, "Multi") + , mTiledBuffer(aPaintedLayer, *this, aManager, &mSharedFrameMetricsHelper) + , mLowPrecisionTiledBuffer(aPaintedLayer, *this, aManager, &mSharedFrameMetricsHelper) +{ + MOZ_COUNT_CTOR(MultiTiledContentClient); + mLowPrecisionTiledBuffer.SetResolution(gfxPrefs::LowPrecisionResolution()); + mHasLowPrecision = gfxPrefs::UseLowPrecisionBuffer(); +} + +void +MultiTiledContentClient::ClearCachedResources() +{ + CompositableClient::ClearCachedResources(); + mTiledBuffer.DiscardBuffers(); + mLowPrecisionTiledBuffer.DiscardBuffers(); +} + +void +MultiTiledContentClient::UpdatedBuffer(TiledBufferType aType) +{ + ClientMultiTiledLayerBuffer* buffer = aType == LOW_PRECISION_TILED_BUFFER + ? &mLowPrecisionTiledBuffer + : &mTiledBuffer; + + MOZ_ASSERT(aType != LOW_PRECISION_TILED_BUFFER || mHasLowPrecision); + + mForwarder->UseTiledLayerBuffer(this, buffer->GetSurfaceDescriptorTiles()); + buffer->ClearPaintedRegion(); +} + +SharedFrameMetricsHelper::SharedFrameMetricsHelper() + : mLastProgressiveUpdateWasLowPrecision(false) + , mProgressiveUpdateWasInDanger(false) +{ + MOZ_COUNT_CTOR(SharedFrameMetricsHelper); +} + +SharedFrameMetricsHelper::~SharedFrameMetricsHelper() +{ + MOZ_COUNT_DTOR(SharedFrameMetricsHelper); +} + +static inline bool +FuzzyEquals(float a, float b) { + return (fabsf(a - b) < 1e-6); +} + +static AsyncTransform +ComputeViewTransform(const FrameMetrics& aContentMetrics, const FrameMetrics& aCompositorMetrics) +{ + // This is basically the same code as AsyncPanZoomController::GetCurrentAsyncTransform + // but with aContentMetrics used in place of mLastContentPaintMetrics, because they + // should be equivalent, modulo race conditions while transactions are inflight. + + ParentLayerPoint translation = (aCompositorMetrics.GetScrollOffset() - aContentMetrics.GetScrollOffset()) + * aCompositorMetrics.GetZoom(); + return AsyncTransform(aCompositorMetrics.GetAsyncZoom(), -translation); +} + +bool +SharedFrameMetricsHelper::UpdateFromCompositorFrameMetrics( + const LayerMetricsWrapper& aLayer, + bool aHasPendingNewThebesContent, + bool aLowPrecision, + AsyncTransform& aViewTransform) +{ + MOZ_ASSERT(aLayer); + + CompositorBridgeChild* compositor = nullptr; + if (aLayer.Manager() && + aLayer.Manager()->AsClientLayerManager()) { + compositor = aLayer.Manager()->AsClientLayerManager()->GetCompositorBridgeChild(); + } + + if (!compositor) { + return false; + } + + const FrameMetrics& contentMetrics = aLayer.Metrics(); + FrameMetrics compositorMetrics; + + if (!compositor->LookupCompositorFrameMetrics(contentMetrics.GetScrollId(), + compositorMetrics)) { + return false; + } + + aViewTransform = ComputeViewTransform(contentMetrics, compositorMetrics); + + // Reset the checkerboard risk flag when switching to low precision + // rendering. + if (aLowPrecision && !mLastProgressiveUpdateWasLowPrecision) { + // Skip low precision rendering until we're at risk of checkerboarding. + if (!mProgressiveUpdateWasInDanger) { + TILING_LOG("TILING: Aborting low-precision rendering because not at risk of checkerboarding\n"); + return true; + } + mProgressiveUpdateWasInDanger = false; + } + mLastProgressiveUpdateWasLowPrecision = aLowPrecision; + + // Always abort updates if the resolution has changed. There's no use + // in drawing at the incorrect resolution. + if (!FuzzyEquals(compositorMetrics.GetZoom().xScale, contentMetrics.GetZoom().xScale) || + !FuzzyEquals(compositorMetrics.GetZoom().yScale, contentMetrics.GetZoom().yScale)) { + TILING_LOG("TILING: Aborting because resolution changed from %s to %s\n", + ToString(contentMetrics.GetZoom()).c_str(), ToString(compositorMetrics.GetZoom()).c_str()); + return true; + } + + // Never abort drawing if we can't be sure we've sent a more recent + // display-port. If we abort updating when we shouldn't, we can end up + // with blank regions on the screen and we open up the risk of entering + // an endless updating cycle. + if (fabsf(contentMetrics.GetScrollOffset().x - compositorMetrics.GetScrollOffset().x) <= 2 && + fabsf(contentMetrics.GetScrollOffset().y - compositorMetrics.GetScrollOffset().y) <= 2 && + fabsf(contentMetrics.GetDisplayPort().x - compositorMetrics.GetDisplayPort().x) <= 2 && + fabsf(contentMetrics.GetDisplayPort().y - compositorMetrics.GetDisplayPort().y) <= 2 && + fabsf(contentMetrics.GetDisplayPort().width - compositorMetrics.GetDisplayPort().width) <= 2 && + fabsf(contentMetrics.GetDisplayPort().height - compositorMetrics.GetDisplayPort().height) <= 2) { + return false; + } + + // When not a low precision pass and the page is in danger of checker boarding + // abort update. + if (!aLowPrecision && !mProgressiveUpdateWasInDanger) { + bool scrollUpdatePending = contentMetrics.GetScrollOffsetUpdated() && + contentMetrics.GetScrollGeneration() != compositorMetrics.GetScrollGeneration(); + // If scrollUpdatePending is true, then that means the content-side + // metrics has a new scroll offset that is going to be forced into the + // compositor but it hasn't gotten there yet. + // Even though right now comparing the metrics might indicate we're + // about to checkerboard (and that's true), the checkerboarding will + // disappear as soon as the new scroll offset update is processed + // on the compositor side. To avoid leaving things in a low-precision + // paint, we need to detect and handle this case (bug 1026756). + if (!scrollUpdatePending && AboutToCheckerboard(contentMetrics, compositorMetrics)) { + mProgressiveUpdateWasInDanger = true; + return true; + } + } + + // Abort drawing stale low-precision content if there's a more recent + // display-port in the pipeline. + if (aLowPrecision && !aHasPendingNewThebesContent) { + TILING_LOG("TILING: Aborting low-precision because of new pending content\n"); + return true; + } + + return false; +} + +bool +SharedFrameMetricsHelper::AboutToCheckerboard(const FrameMetrics& aContentMetrics, + const FrameMetrics& aCompositorMetrics) +{ + // The size of the painted area is originally computed in layer pixels in layout, but then + // converted to app units and then back to CSS pixels before being put in the FrameMetrics. + // This process can introduce some rounding error, so we inflate the rect by one app unit + // to account for that. + CSSRect painted = (aContentMetrics.GetCriticalDisplayPort().IsEmpty() + ? aContentMetrics.GetDisplayPort() + : aContentMetrics.GetCriticalDisplayPort()) + + aContentMetrics.GetScrollOffset(); + painted.Inflate(CSSMargin::FromAppUnits(nsMargin(1, 1, 1, 1))); + + // Inflate the rect by the danger zone. See the description of the danger zone prefs + // in AsyncPanZoomController.cpp for an explanation of this. + CSSRect showing = CSSRect(aCompositorMetrics.GetScrollOffset(), + aCompositorMetrics.CalculateBoundedCompositedSizeInCssPixels()); + showing.Inflate(LayerSize(gfxPrefs::APZDangerZoneX(), gfxPrefs::APZDangerZoneY()) + / aCompositorMetrics.LayersPixelsPerCSSPixel()); + + // Clamp both rects to the scrollable rect, because having either of those + // exceed the scrollable rect doesn't make sense, and could lead to false + // positives. + painted = painted.Intersect(aContentMetrics.GetScrollableRect()); + showing = showing.Intersect(aContentMetrics.GetScrollableRect()); + + if (!painted.Contains(showing)) { + TILING_LOG("TILING: About to checkerboard; content %s\n", Stringify(aContentMetrics).c_str()); + TILING_LOG("TILING: About to checkerboard; painted %s\n", Stringify(painted).c_str()); + TILING_LOG("TILING: About to checkerboard; compositor %s\n", Stringify(aCompositorMetrics).c_str()); + TILING_LOG("TILING: About to checkerboard; showing %s\n", Stringify(showing).c_str()); + return true; + } + return false; +} + +ClientMultiTiledLayerBuffer::ClientMultiTiledLayerBuffer(ClientTiledPaintedLayer& aPaintedLayer, + CompositableClient& aCompositableClient, + ClientLayerManager* aManager, + SharedFrameMetricsHelper* aHelper) + : ClientTiledLayerBuffer(aPaintedLayer, aCompositableClient) + , mManager(aManager) + , mCallback(nullptr) + , mCallbackData(nullptr) + , mSharedFrameMetricsHelper(aHelper) +{ +} + +bool +ClientTiledLayerBuffer::HasFormatChanged() const +{ + SurfaceMode mode; + gfxContentType content = GetContentType(&mode); + return content != mLastPaintContentType || + mode != mLastPaintSurfaceMode; +} + + +gfxContentType +ClientTiledLayerBuffer::GetContentType(SurfaceMode* aMode) const +{ + gfxContentType content = + mPaintedLayer.CanUseOpaqueSurface() ? gfxContentType::COLOR : + gfxContentType::COLOR_ALPHA; + SurfaceMode mode = mPaintedLayer.GetSurfaceMode(); + + if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { +#if defined(MOZ_GFX_OPTIMIZE_MOBILE) + mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; +#else + if (!mPaintedLayer.GetParent() || + !mPaintedLayer.GetParent()->SupportsComponentAlphaChildren()) { + mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; + } else { + content = gfxContentType::COLOR; + } +#endif + } else if (mode == SurfaceMode::SURFACE_OPAQUE) { +#if defined(MOZ_GFX_OPTIMIZE_MOBILE) + if (IsLowPrecision()) { + // If we're in low-res mode, drawing can sample from outside the visible + // region. Make sure that we only sample transparency if that happens. + mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; + content = gfxContentType::COLOR_ALPHA; + } +#else + if (mPaintedLayer.MayResample()) { + mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; + content = gfxContentType::COLOR_ALPHA; + } +#endif + } + + if (aMode) { + *aMode = mode; + } + return content; +} + +class TileExpiry final : public nsExpirationTracker<TileClient, 3> +{ + public: + TileExpiry() : nsExpirationTracker<TileClient, 3>(1000, "TileExpiry") {} + + static void AddTile(TileClient* aTile) + { + if (!sTileExpiry) { + sTileExpiry = MakeUnique<TileExpiry>(); + } + + sTileExpiry->AddObject(aTile); + } + + static void RemoveTile(TileClient* aTile) + { + MOZ_ASSERT(sTileExpiry); + sTileExpiry->RemoveObject(aTile); + } + + static void Shutdown() { + sTileExpiry = nullptr; + } + private: + virtual void NotifyExpired(TileClient* aTile) override + { + aTile->DiscardBackBuffer(); + } + + static UniquePtr<TileExpiry> sTileExpiry; +}; +UniquePtr<TileExpiry> TileExpiry::sTileExpiry; + +void ShutdownTileCache() +{ + TileExpiry::Shutdown(); +} + +void +TileClient::PrivateProtector::Set(TileClient * const aContainer, RefPtr<TextureClient> aNewValue) +{ + if (mBuffer) { + TileExpiry::RemoveTile(aContainer); + } + mBuffer = aNewValue; + if (mBuffer) { + TileExpiry::AddTile(aContainer); + } +} + +void +TileClient::PrivateProtector::Set(TileClient * const aContainer, TextureClient* aNewValue) +{ + Set(aContainer, RefPtr<TextureClient>(aNewValue)); +} + +// Placeholder +TileClient::TileClient() + : mWasPlaceholder(false) +{ +} + +TileClient::~TileClient() +{ + if (mExpirationState.IsTracked()) { + MOZ_ASSERT(mBackBuffer); + TileExpiry::RemoveTile(this); + } +} + +TileClient::TileClient(const TileClient& o) +{ + mBackBuffer.Set(this, o.mBackBuffer); + mBackBufferOnWhite = o.mBackBufferOnWhite; + mFrontBuffer = o.mFrontBuffer; + mFrontBufferOnWhite = o.mFrontBufferOnWhite; + mWasPlaceholder = o.mWasPlaceholder; + mUpdateRect = o.mUpdateRect; +#ifdef GFX_TILEDLAYER_DEBUG_OVERLAY + mLastUpdate = o.mLastUpdate; +#endif + mAllocator = o.mAllocator; + mInvalidFront = o.mInvalidFront; + mInvalidBack = o.mInvalidBack; +} + +TileClient& +TileClient::operator=(const TileClient& o) +{ + if (this == &o) return *this; + mBackBuffer.Set(this, o.mBackBuffer); + mBackBufferOnWhite = o.mBackBufferOnWhite; + mFrontBuffer = o.mFrontBuffer; + mFrontBufferOnWhite = o.mFrontBufferOnWhite; + mWasPlaceholder = o.mWasPlaceholder; + mUpdateRect = o.mUpdateRect; +#ifdef GFX_TILEDLAYER_DEBUG_OVERLAY + mLastUpdate = o.mLastUpdate; +#endif + mAllocator = o.mAllocator; + mInvalidFront = o.mInvalidFront; + mInvalidBack = o.mInvalidBack; + return *this; +} + +void +TileClient::Dump(std::stringstream& aStream) +{ + aStream << "TileClient(bb=" << (TextureClient*)mBackBuffer << " fb=" << mFrontBuffer.get(); + if (mBackBufferOnWhite) { + aStream << " bbow=" << mBackBufferOnWhite.get(); + } + if (mFrontBufferOnWhite) { + aStream << " fbow=" << mFrontBufferOnWhite.get(); + } + aStream << ")"; +} + +void +TileClient::Flip() +{ + RefPtr<TextureClient> frontBuffer = mFrontBuffer; + RefPtr<TextureClient> frontBufferOnWhite = mFrontBufferOnWhite; + mFrontBuffer = mBackBuffer; + mFrontBufferOnWhite = mBackBufferOnWhite; + mBackBuffer.Set(this, frontBuffer); + mBackBufferOnWhite = frontBufferOnWhite; + nsIntRegion invalidFront = mInvalidFront; + mInvalidFront = mInvalidBack; + mInvalidBack = invalidFront; +} + +static bool +CopyFrontToBack(TextureClient* aFront, + TextureClient* aBack, + const gfx::IntRect& aRectToCopy) +{ + TextureClientAutoLock frontLock(aFront, OpenMode::OPEN_READ); + if (!frontLock.Succeeded()) { + gfxCriticalError() << "[Tiling:Client] Failed to lock the tile's front buffer"; + return false; + } + + if (!aBack->Lock(OpenMode::OPEN_READ_WRITE)) { + gfxCriticalError() << "[Tiling:Client] Failed to lock the tile's back buffer"; + return false; + } + + gfx::IntPoint rectToCopyTopLeft = aRectToCopy.TopLeft(); + aFront->CopyToTextureClient(aBack, &aRectToCopy, &rectToCopyTopLeft); + return true; +} + +void +TileClient::ValidateBackBufferFromFront(const nsIntRegion& aDirtyRegion, + nsIntRegion& aAddPaintedRegion) +{ + if (mBackBuffer && mFrontBuffer) { + gfx::IntSize tileSize = mFrontBuffer->GetSize(); + const IntRect tileRect = IntRect(0, 0, tileSize.width, tileSize.height); + + if (aDirtyRegion.Contains(tileRect)) { + // The dirty region means that we no longer need the front buffer, so + // discard it. + DiscardFrontBuffer(); + } else { + // Region that needs copying. + nsIntRegion regionToCopy = mInvalidBack; + + regionToCopy.Sub(regionToCopy, aDirtyRegion); + + aAddPaintedRegion = regionToCopy; + + if (regionToCopy.IsEmpty()) { + // Just redraw it all. + return; + } + + // Copy the bounding rect of regionToCopy. As tiles are quite small, it + // is unlikely that we'd save much by copying each individual rect of the + // region, but we can reevaluate this if it becomes an issue. + const IntRect rectToCopy = regionToCopy.GetBounds(); + gfx::IntRect gfxRectToCopy(rectToCopy.x, rectToCopy.y, rectToCopy.width, rectToCopy.height); + CopyFrontToBack(mFrontBuffer, mBackBuffer, gfxRectToCopy); + + if (mBackBufferOnWhite) { + MOZ_ASSERT(mFrontBufferOnWhite); + CopyFrontToBack(mFrontBufferOnWhite, mBackBufferOnWhite, gfxRectToCopy); + } + + mInvalidBack.SetEmpty(); + } + } +} + +void +TileClient::DiscardFrontBuffer() +{ + if (mFrontBuffer) { + MOZ_ASSERT(mFrontBuffer->GetReadLock()); + + MOZ_ASSERT(mAllocator); + if (mAllocator) { + mAllocator->ReturnTextureClientDeferred(mFrontBuffer); + if (mFrontBufferOnWhite) { + mAllocator->ReturnTextureClientDeferred(mFrontBufferOnWhite); + } + } + + if (mFrontBuffer->IsLocked()) { + mFrontBuffer->Unlock(); + } + if (mFrontBufferOnWhite && mFrontBufferOnWhite->IsLocked()) { + mFrontBufferOnWhite->Unlock(); + } + mFrontBuffer = nullptr; + mFrontBufferOnWhite = nullptr; + } +} + +static void +DiscardTexture(TextureClient* aTexture, TextureClientAllocator* aAllocator) +{ + MOZ_ASSERT(aAllocator); + if (aTexture && aAllocator) { + MOZ_ASSERT(aTexture->GetReadLock()); + if (!aTexture->HasSynchronization() && aTexture->IsReadLocked()) { + // Our current back-buffer is still locked by the compositor. This can occur + // when the client is producing faster than the compositor can consume. In + // this case we just want to drop it and not return it to the pool. + aAllocator->ReportClientLost(); + } else { + aAllocator->ReturnTextureClientDeferred(aTexture); + } + if (aTexture->IsLocked()) { + aTexture->Unlock(); + } + } +} + +void +TileClient::DiscardBackBuffer() +{ + if (mBackBuffer) { + DiscardTexture(mBackBuffer, mAllocator); + mBackBuffer.Set(this, nullptr); + DiscardTexture(mBackBufferOnWhite, mAllocator); + mBackBufferOnWhite = nullptr; + } +} + +static already_AddRefed<TextureClient> +CreateBackBufferTexture(TextureClient* aCurrentTexture, + CompositableClient& aCompositable, + TextureClientAllocator* aAllocator) +{ + if (aCurrentTexture) { + // Our current back-buffer is still locked by the compositor. This can occur + // when the client is producing faster than the compositor can consume. In + // this case we just want to drop it and not return it to the pool. + aAllocator->ReportClientLost(); + } + + RefPtr<TextureClient> texture = aAllocator->GetTextureClient(); + + if (!texture) { + gfxCriticalError() << "[Tiling:Client] Failed to allocate a TextureClient"; + return nullptr; + } + + texture->EnableReadLock(); + + if (!aCompositable.AddTextureClient(texture)) { + gfxCriticalError() << "[Tiling:Client] Failed to connect a TextureClient"; + return nullptr; + } + + return texture.forget(); +} + +TextureClient* +TileClient::GetBackBuffer(CompositableClient& aCompositable, + const nsIntRegion& aDirtyRegion, + gfxContentType aContent, + SurfaceMode aMode, + nsIntRegion& aAddPaintedRegion, + RefPtr<TextureClient>* aBackBufferOnWhite) +{ + if (!mAllocator) { + gfxCriticalError() << "[TileClient] Missing TextureClientAllocator."; + return nullptr; + } + if (aMode != SurfaceMode::SURFACE_COMPONENT_ALPHA) { + // It can happen that a component-alpha layer stops being on component alpha + // on the next frame, just drop the buffers on white if that happens. + if (mBackBufferOnWhite) { + mAllocator->ReportClientLost(); + mBackBufferOnWhite = nullptr; + } + if (mFrontBufferOnWhite) { + mAllocator->ReportClientLost(); + mFrontBufferOnWhite = nullptr; + } + } + + // Try to re-use the front-buffer if possible + if (mFrontBuffer && + mFrontBuffer->HasIntermediateBuffer() && + !mFrontBuffer->IsReadLocked() && + (aMode != SurfaceMode::SURFACE_COMPONENT_ALPHA || ( + mFrontBufferOnWhite && !mFrontBufferOnWhite->IsReadLocked()))) { + // If we had a backbuffer we no longer care about it since we'll + // re-use the front buffer. + DiscardBackBuffer(); + Flip(); + } else { + if (!mBackBuffer || mBackBuffer->IsReadLocked()) { + mBackBuffer.Set(this, + CreateBackBufferTexture(mBackBuffer, aCompositable, mAllocator) + ); + if (!mBackBuffer) { + DiscardBackBuffer(); + DiscardFrontBuffer(); + return nullptr; + } + mInvalidBack = IntRect(IntPoint(), mBackBuffer->GetSize()); + } + + if (aMode == SurfaceMode::SURFACE_COMPONENT_ALPHA + && (!mBackBufferOnWhite || mBackBufferOnWhite->IsReadLocked())) { + mBackBufferOnWhite = CreateBackBufferTexture( + mBackBufferOnWhite, aCompositable, mAllocator + ); + if (!mBackBufferOnWhite) { + DiscardBackBuffer(); + DiscardFrontBuffer(); + return nullptr; + } + mInvalidBack = IntRect(IntPoint(), mBackBufferOnWhite->GetSize()); + } + + ValidateBackBufferFromFront(aDirtyRegion, aAddPaintedRegion); + } + + if (!mBackBuffer->IsLocked()) { + if (!mBackBuffer->Lock(OpenMode::OPEN_READ_WRITE)) { + gfxCriticalError() << "[Tiling:Client] Failed to lock a tile (B)"; + DiscardBackBuffer(); + DiscardFrontBuffer(); + return nullptr; + } + } + + if (mBackBufferOnWhite && !mBackBufferOnWhite->IsLocked()) { + if (!mBackBufferOnWhite->Lock(OpenMode::OPEN_READ_WRITE)) { + gfxCriticalError() << "[Tiling:Client] Failed to lock a tile (W)"; + DiscardBackBuffer(); + DiscardFrontBuffer(); + return nullptr; + } + } + + *aBackBufferOnWhite = mBackBufferOnWhite; + return mBackBuffer; +} + +TileDescriptor +TileClient::GetTileDescriptor() +{ + if (IsPlaceholderTile()) { + mWasPlaceholder = true; + return PlaceholderTileDescriptor(); + } + bool wasPlaceholder = mWasPlaceholder; + mWasPlaceholder = false; + + ReadLockDescriptor lock; + mFrontBuffer->SerializeReadLock(lock); + + ReadLockDescriptor lockOnWhite = null_t(); + if (mFrontBufferOnWhite) { + mFrontBufferOnWhite->SerializeReadLock(lockOnWhite); + } + + return TexturedTileDescriptor(nullptr, mFrontBuffer->GetIPDLActor(), + mFrontBufferOnWhite ? MaybeTexture(mFrontBufferOnWhite->GetIPDLActor()) : MaybeTexture(null_t()), + mUpdateRect, + lock, lockOnWhite, + wasPlaceholder); +} + +void +ClientMultiTiledLayerBuffer::DiscardBuffers() +{ + for (TileClient& tile : mRetainedTiles) { + tile.DiscardBuffers(); + } +} + +SurfaceDescriptorTiles +ClientMultiTiledLayerBuffer::GetSurfaceDescriptorTiles() +{ + InfallibleTArray<TileDescriptor> tiles; + + for (TileClient& tile : mRetainedTiles) { + TileDescriptor tileDesc = tile.GetTileDescriptor(); + tiles.AppendElement(tileDesc); + // Reset the update rect + tile.mUpdateRect = IntRect(); + } + return SurfaceDescriptorTiles(mValidRegion, + tiles, + mTileOrigin, mTileSize, + mTiles.mFirst.x, mTiles.mFirst.y, + mTiles.mSize.width, mTiles.mSize.height, + mResolution, mFrameResolution.xScale, + mFrameResolution.yScale, + mWasLastPaintProgressive); +} + +void +ClientMultiTiledLayerBuffer::PaintThebes(const nsIntRegion& aNewValidRegion, + const nsIntRegion& aPaintRegion, + const nsIntRegion& aDirtyRegion, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData, + bool aIsProgressive) +{ + TILING_LOG("TILING %p: PaintThebes painting region %s\n", &mPaintedLayer, Stringify(aPaintRegion).c_str()); + TILING_LOG("TILING %p: PaintThebes new valid region %s\n", &mPaintedLayer, Stringify(aNewValidRegion).c_str()); + + mCallback = aCallback; + mCallbackData = aCallbackData; + mWasLastPaintProgressive = aIsProgressive; + +#ifdef GFX_TILEDLAYER_PREF_WARNINGS + long start = PR_IntervalNow(); +#endif + +#ifdef GFX_TILEDLAYER_PREF_WARNINGS + if (PR_IntervalNow() - start > 30) { + const IntRect bounds = aPaintRegion.GetBounds(); + printf_stderr("Time to draw %i: %i, %i, %i, %i\n", PR_IntervalNow() - start, bounds.x, bounds.y, bounds.width, bounds.height); + if (aPaintRegion.IsComplex()) { + printf_stderr("Complex region\n"); + for (auto iter = aPaintRegion.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& rect = iter.Get(); + printf_stderr(" rect %i, %i, %i, %i\n", + rect.x, rect.y, rect.width, rect.height); + } + } + } + start = PR_IntervalNow(); +#endif + + PROFILER_LABEL("ClientMultiTiledLayerBuffer", "PaintThebesUpdate", + js::ProfileEntry::Category::GRAPHICS); + + mNewValidRegion = aNewValidRegion; + Update(aNewValidRegion, aPaintRegion, aDirtyRegion); + +#ifdef GFX_TILEDLAYER_PREF_WARNINGS + if (PR_IntervalNow() - start > 10) { + const IntRect bounds = aPaintRegion.GetBounds(); + printf_stderr("Time to tile %i: %i, %i, %i, %i\n", PR_IntervalNow() - start, bounds.x, bounds.y, bounds.width, bounds.height); + } +#endif + + mLastPaintContentType = GetContentType(&mLastPaintSurfaceMode); + mCallback = nullptr; + mCallbackData = nullptr; +} + +void PadDrawTargetOutFromRegion(RefPtr<DrawTarget> drawTarget, nsIntRegion ®ion) +{ + struct LockedBits { + uint8_t *data; + IntSize size; + int32_t stride; + SurfaceFormat format; + static int clamp(int x, int min, int max) + { + if (x < min) + x = min; + if (x > max) + x = max; + return x; + } + + static void ensure_memcpy(uint8_t *dst, uint8_t *src, size_t n, uint8_t *bitmap, int stride, int height) + { + if (src + n > bitmap + stride*height) { + MOZ_CRASH("GFX: long src memcpy"); + } + if (src < bitmap) { + MOZ_CRASH("GFX: short src memcpy"); + } + if (dst + n > bitmap + stride*height) { + MOZ_CRASH("GFX: long dst mempcy"); + } + if (dst < bitmap) { + MOZ_CRASH("GFX: short dst mempcy"); + } + } + + static void visitor(void *closure, VisitSide side, int x1, int y1, int x2, int y2) { + LockedBits *lb = static_cast<LockedBits*>(closure); + uint8_t *bitmap = lb->data; + const int bpp = gfx::BytesPerPixel(lb->format); + const int stride = lb->stride; + const int width = lb->size.width; + const int height = lb->size.height; + + if (side == VisitSide::TOP) { + if (y1 > 0) { + x1 = clamp(x1, 0, width - 1); + x2 = clamp(x2, 0, width - 1); + ensure_memcpy(&bitmap[x1*bpp + (y1-1) * stride], &bitmap[x1*bpp + y1 * stride], (x2 - x1) * bpp, bitmap, stride, height); + memcpy(&bitmap[x1*bpp + (y1-1) * stride], &bitmap[x1*bpp + y1 * stride], (x2 - x1) * bpp); + } + } else if (side == VisitSide::BOTTOM) { + if (y1 < height) { + x1 = clamp(x1, 0, width - 1); + x2 = clamp(x2, 0, width - 1); + ensure_memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[x1*bpp + (y1-1) * stride], (x2 - x1) * bpp, bitmap, stride, height); + memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[x1*bpp + (y1-1) * stride], (x2 - x1) * bpp); + } + } else if (side == VisitSide::LEFT) { + if (x1 > 0) { + while (y1 != y2) { + memcpy(&bitmap[(x1-1)*bpp + y1 * stride], &bitmap[x1*bpp + y1*stride], bpp); + y1++; + } + } + } else if (side == VisitSide::RIGHT) { + if (x1 < width) { + while (y1 != y2) { + memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[(x1-1)*bpp + y1*stride], bpp); + y1++; + } + } + } + + } + } lb; + + if (drawTarget->LockBits(&lb.data, &lb.size, &lb.stride, &lb.format)) { + // we can only pad software targets so if we can't lock the bits don't pad + region.VisitEdges(lb.visitor, &lb); + drawTarget->ReleaseBits(lb.data); + } +} + +void +ClientTiledLayerBuffer::UnlockTile(TileClient& aTile) +{ + // We locked the back buffer, and flipped so we now need to unlock the front + if (aTile.mFrontBuffer && aTile.mFrontBuffer->IsLocked()) { + aTile.mFrontBuffer->Unlock(); + aTile.mFrontBuffer->SyncWithObject(mCompositableClient.GetForwarder()->GetSyncObject()); + } + if (aTile.mFrontBufferOnWhite && aTile.mFrontBufferOnWhite->IsLocked()) { + aTile.mFrontBufferOnWhite->Unlock(); + aTile.mFrontBufferOnWhite->SyncWithObject(mCompositableClient.GetForwarder()->GetSyncObject()); + } + if (aTile.mBackBuffer && aTile.mBackBuffer->IsLocked()) { + aTile.mBackBuffer->Unlock(); + } + if (aTile.mBackBufferOnWhite && aTile.mBackBufferOnWhite->IsLocked()) { + aTile.mBackBufferOnWhite->Unlock(); + } +} + +void ClientMultiTiledLayerBuffer::Update(const nsIntRegion& newValidRegion, + const nsIntRegion& aPaintRegion, + const nsIntRegion& aDirtyRegion) +{ + const IntSize scaledTileSize = GetScaledTileSize(); + const gfx::IntRect newBounds = newValidRegion.GetBounds(); + + const TilesPlacement oldTiles = mTiles; + const TilesPlacement newTiles(floor_div(newBounds.x, scaledTileSize.width), + floor_div(newBounds.y, scaledTileSize.height), + floor_div(GetTileStart(newBounds.x, scaledTileSize.width) + + newBounds.width, scaledTileSize.width) + 1, + floor_div(GetTileStart(newBounds.y, scaledTileSize.height) + + newBounds.height, scaledTileSize.height) + 1); + + const size_t oldTileCount = mRetainedTiles.Length(); + const size_t newTileCount = newTiles.mSize.width * newTiles.mSize.height; + + nsTArray<TileClient> oldRetainedTiles; + mRetainedTiles.SwapElements(oldRetainedTiles); + mRetainedTiles.SetLength(newTileCount); + + for (size_t oldIndex = 0; oldIndex < oldTileCount; oldIndex++) { + const TileIntPoint tilePosition = oldTiles.TilePosition(oldIndex); + const size_t newIndex = newTiles.TileIndex(tilePosition); + // First, get the already existing tiles to the right place in the new array. + // Leave placeholders (default constructor) where there was no tile. + if (newTiles.HasTile(tilePosition)) { + mRetainedTiles[newIndex] = oldRetainedTiles[oldIndex]; + } else { + // release tiles that we are not going to reuse before allocating new ones + // to avoid allocating unnecessarily. + oldRetainedTiles[oldIndex].DiscardBuffers(); + } + } + + oldRetainedTiles.Clear(); + + if (!aPaintRegion.IsEmpty()) { + for (size_t i = 0; i < newTileCount; ++i) { + const TileIntPoint tilePosition = newTiles.TilePosition(i); + + IntPoint tileOffset = GetTileOffset(tilePosition); + nsIntRegion tileDrawRegion = IntRect(tileOffset, scaledTileSize); + tileDrawRegion.AndWith(aPaintRegion); + + if (tileDrawRegion.IsEmpty()) { + continue; + } + + TileClient& tile = mRetainedTiles[i]; + if (!ValidateTile(tile, GetTileOffset(tilePosition), tileDrawRegion)) { + gfxCriticalError() << "ValidateTile failed"; + } + } + + if (mMoz2DTiles.size() > 0) { + gfx::TileSet tileset; + for (size_t i = 0; i < mMoz2DTiles.size(); ++i) { + mMoz2DTiles[i].mTileOrigin -= mTilingOrigin; + } + tileset.mTiles = &mMoz2DTiles[0]; + tileset.mTileCount = mMoz2DTiles.size(); + RefPtr<DrawTarget> drawTarget = gfx::Factory::CreateTiledDrawTarget(tileset); + if (!drawTarget || !drawTarget->IsValid()) { + gfxDevCrash(LogReason::InvalidContext) << "Invalid tiled draw target"; + return; + } + drawTarget->SetTransform(Matrix()); + + RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(drawTarget); + MOZ_ASSERT(ctx); // already checked the draw target above + ctx->SetMatrix( + ctx->CurrentMatrix().Scale(mResolution, mResolution).Translate(ThebesPoint(-mTilingOrigin))); + + mCallback(&mPaintedLayer, ctx, aPaintRegion, aDirtyRegion, + DrawRegionClip::DRAW, nsIntRegion(), mCallbackData); + mMoz2DTiles.clear(); + // Reset: + mTilingOrigin = IntPoint(std::numeric_limits<int32_t>::max(), + std::numeric_limits<int32_t>::max()); + } + + bool edgePaddingEnabled = gfxPrefs::TileEdgePaddingEnabled(); + + for (uint32_t i = 0; i < mRetainedTiles.Length(); ++i) { + TileClient& tile = mRetainedTiles[i]; + + // Only worry about padding when not doing low-res because it simplifies + // the math and the artifacts won't be noticable + // Edge padding prevents sampling artifacts when compositing. + if (edgePaddingEnabled && mResolution == 1 && + tile.mFrontBuffer && tile.mFrontBuffer->IsLocked()) { + + const TileIntPoint tilePosition = newTiles.TilePosition(i); + IntPoint tileOffset = GetTileOffset(tilePosition); + // Strictly speakig we want the unscaled rect here, but it doesn't matter + // because we only run this code when the resolution is equal to 1. + IntRect tileRect = IntRect(tileOffset.x, tileOffset.y, + GetTileSize().width, GetTileSize().height); + + nsIntRegion tileDrawRegion = IntRect(tileOffset, scaledTileSize); + tileDrawRegion.AndWith(aPaintRegion); + + nsIntRegion tileValidRegion = mValidRegion; + tileValidRegion.OrWith(tileDrawRegion); + + // We only need to pad out if the tile has area that's not valid + if (!tileValidRegion.Contains(tileRect)) { + tileValidRegion = tileValidRegion.Intersect(tileRect); + // translate the region into tile space and pad + tileValidRegion.MoveBy(-IntPoint(tileOffset.x, tileOffset.y)); + RefPtr<DrawTarget> drawTarget = tile.mFrontBuffer->BorrowDrawTarget(); + PadDrawTargetOutFromRegion(drawTarget, tileValidRegion); + } + } + UnlockTile(tile); + } + } + + mTiles = newTiles; + mValidRegion = newValidRegion; + mPaintedRegion.OrWith(aPaintRegion); +} + +bool +ClientMultiTiledLayerBuffer::ValidateTile(TileClient& aTile, + const nsIntPoint& aTileOrigin, + const nsIntRegion& aDirtyRegion) +{ + PROFILER_LABEL("ClientMultiTiledLayerBuffer", "ValidateTile", + js::ProfileEntry::Category::GRAPHICS); + +#ifdef GFX_TILEDLAYER_PREF_WARNINGS + if (aDirtyRegion.IsComplex()) { + printf_stderr("Complex region\n"); + } +#endif + + SurfaceMode mode; + gfxContentType content = GetContentType(&mode); + + if (!aTile.mAllocator) { + aTile.SetTextureAllocator(mManager->GetCompositorBridgeChild()->GetTexturePool( + mManager->AsShadowForwarder(), + gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content), + TextureFlags::DISALLOW_BIGIMAGE | TextureFlags::IMMEDIATE_UPLOAD)); + MOZ_ASSERT(aTile.mAllocator); + } + + nsIntRegion offsetScaledDirtyRegion = aDirtyRegion.MovedBy(-aTileOrigin); + offsetScaledDirtyRegion.ScaleRoundOut(mResolution, mResolution); + + nsIntRegion extraPainted; + RefPtr<TextureClient> backBufferOnWhite; + RefPtr<TextureClient> backBuffer = + aTile.GetBackBuffer(mCompositableClient, + offsetScaledDirtyRegion, + content, mode, + extraPainted, + &backBufferOnWhite); + + aTile.mUpdateRect = offsetScaledDirtyRegion.GetBounds().Union(extraPainted.GetBounds()); + + extraPainted.MoveBy(aTileOrigin); + extraPainted.And(extraPainted, mNewValidRegion); + mPaintedRegion.Or(mPaintedRegion, extraPainted); + + if (!backBuffer) { + return false; + } + + gfx::Tile moz2DTile; + RefPtr<DrawTarget> dt = backBuffer->BorrowDrawTarget(); + RefPtr<DrawTarget> dtOnWhite; + if (backBufferOnWhite) { + dtOnWhite = backBufferOnWhite->BorrowDrawTarget(); + moz2DTile.mDrawTarget = Factory::CreateDualDrawTarget(dt, dtOnWhite); + } else { + moz2DTile.mDrawTarget = dt; + } + moz2DTile.mTileOrigin = gfx::IntPoint(aTileOrigin.x, aTileOrigin.y); + if (!dt || (backBufferOnWhite && !dtOnWhite)) { + aTile.DiscardBuffers(); + return false; + } + + mMoz2DTiles.push_back(moz2DTile); + mTilingOrigin.x = std::min(mTilingOrigin.x, moz2DTile.mTileOrigin.x); + mTilingOrigin.y = std::min(mTilingOrigin.y, moz2DTile.mTileOrigin.y); + + for (auto iter = aDirtyRegion.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& dirtyRect = iter.Get(); + gfx::Rect drawRect(dirtyRect.x - aTileOrigin.x, + dirtyRect.y - aTileOrigin.y, + dirtyRect.width, + dirtyRect.height); + drawRect.Scale(mResolution); + + // Mark the newly updated area as invalid in the front buffer + aTile.mInvalidFront.Or(aTile.mInvalidFront, IntRect::RoundOut(drawRect)); + + if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { + dt->FillRect(drawRect, ColorPattern(Color(0.0, 0.0, 0.0, 1.0))); + dtOnWhite->FillRect(drawRect, ColorPattern(Color(1.0, 1.0, 1.0, 1.0))); + } else if (content == gfxContentType::COLOR_ALPHA) { + dt->ClearRect(drawRect); + } + } + + // The new buffer is now validated, remove the dirty region from it. + aTile.mInvalidBack.SubOut(offsetScaledDirtyRegion); + + aTile.Flip(); + + return true; +} + +/** + * This function takes the transform stored in aTransformToCompBounds + * (which was generated in GetTransformToAncestorsParentLayer), and + * modifies it with the ViewTransform from the compositor side so that + * it reflects what the compositor is actually rendering. This operation + * basically adds in the layer's async transform. + * This function then returns the scroll ancestor's composition bounds, + * transformed into the painted layer's LayerPixel coordinates, accounting + * for the compositor state. + */ +static Maybe<LayerRect> +GetCompositorSideCompositionBounds(const LayerMetricsWrapper& aScrollAncestor, + const LayerToParentLayerMatrix4x4& aTransformToCompBounds, + const AsyncTransform& aAPZTransform, + const LayerRect& aClip) +{ + LayerToParentLayerMatrix4x4 transform = aTransformToCompBounds * + AsyncTransformComponentMatrix(aAPZTransform); + + return UntransformBy(transform.Inverse(), + aScrollAncestor.Metrics().GetCompositionBounds(), aClip); +} + +bool +ClientMultiTiledLayerBuffer::ComputeProgressiveUpdateRegion(const nsIntRegion& aInvalidRegion, + const nsIntRegion& aOldValidRegion, + nsIntRegion& aRegionToPaint, + BasicTiledLayerPaintData* aPaintData, + bool aIsRepeated) +{ + aRegionToPaint = aInvalidRegion; + + // If the composition bounds rect is empty, we can't make any sensible + // decision about how to update coherently. In this case, just update + // everything in one transaction. + if (aPaintData->mCompositionBounds.IsEmpty()) { + aPaintData->mPaintFinished = true; + return false; + } + + // If this is a low precision buffer, we force progressive updates. The + // assumption is that the contents is less important, so visual coherency + // is lower priority than speed. + bool drawingLowPrecision = IsLowPrecision(); + + // Find out if we have any non-stale content to update. + nsIntRegion staleRegion; + staleRegion.And(aInvalidRegion, aOldValidRegion); + + TILING_LOG("TILING %p: Progressive update stale region %s\n", &mPaintedLayer, Stringify(staleRegion).c_str()); + + LayerMetricsWrapper scrollAncestor; + mPaintedLayer.GetAncestorLayers(&scrollAncestor, nullptr, nullptr); + + // Find out the current view transform to determine which tiles to draw + // first, and see if we should just abort this paint. Aborting is usually + // caused by there being an incoming, more relevant paint. + AsyncTransform viewTransform; + MOZ_ASSERT(mSharedFrameMetricsHelper); + + bool abortPaint = + mSharedFrameMetricsHelper->UpdateFromCompositorFrameMetrics( + scrollAncestor, + !staleRegion.Contains(aInvalidRegion), + drawingLowPrecision, + viewTransform); + + TILING_LOG("TILING %p: Progressive update view transform %s zoom %f abort %d\n", + &mPaintedLayer, ToString(viewTransform.mTranslation).c_str(), viewTransform.mScale.scale, abortPaint); + + if (abortPaint) { + // We ignore if front-end wants to abort if this is the first, + // non-low-precision paint, as in that situation, we're about to override + // front-end's page/viewport metrics. + if (!aPaintData->mFirstPaint || drawingLowPrecision) { + PROFILER_LABEL("ClientMultiTiledLayerBuffer", "ComputeProgressiveUpdateRegion", + js::ProfileEntry::Category::GRAPHICS); + + aRegionToPaint.SetEmpty(); + return aIsRepeated; + } + } + + Maybe<LayerRect> transformedCompositionBounds = + GetCompositorSideCompositionBounds(scrollAncestor, + aPaintData->mTransformToCompBounds, + viewTransform, + ViewAs<LayerPixel>(Rect(mPaintedLayer.GetLayerBounds()))); + + if (!transformedCompositionBounds) { + aPaintData->mPaintFinished = true; + return false; + } + + TILING_LOG("TILING %p: Progressive update transformed compositor bounds %s\n", &mPaintedLayer, Stringify(*transformedCompositionBounds).c_str()); + + // Compute a "coherent update rect" that we should paint all at once in a + // single transaction. This is to avoid rendering glitches on animated + // page content, and when layers change size/shape. + // On Fennec uploads are more expensive because we're not using gralloc, so + // we use a coherent update rect that is intersected with the screen at the + // time of issuing the draw command. This will paint faster but also potentially + // make the progressive paint more visible to the user while scrolling. + // On B2G uploads are cheaper and we value coherency more, especially outside + // the browser, so we always use the entire user-visible area. + IntRect coherentUpdateRect(RoundedOut( +#ifdef MOZ_WIDGET_ANDROID + transformedCompositionBounds->Intersect(aPaintData->mCompositionBounds) +#else + *transformedCompositionBounds +#endif + ).ToUnknownRect()); + + TILING_LOG("TILING %p: Progressive update final coherency rect %s\n", &mPaintedLayer, Stringify(coherentUpdateRect).c_str()); + + aRegionToPaint.And(aInvalidRegion, coherentUpdateRect); + aRegionToPaint.Or(aRegionToPaint, staleRegion); + bool drawingStale = !aRegionToPaint.IsEmpty(); + if (!drawingStale) { + aRegionToPaint = aInvalidRegion; + } + + // Prioritise tiles that are currently visible on the screen. + bool paintingVisible = false; + if (aRegionToPaint.Intersects(coherentUpdateRect)) { + aRegionToPaint.And(aRegionToPaint, coherentUpdateRect); + paintingVisible = true; + } + + TILING_LOG("TILING %p: Progressive update final paint region %s\n", &mPaintedLayer, Stringify(aRegionToPaint).c_str()); + + // Paint area that's visible and overlaps previously valid content to avoid + // visible glitches in animated elements, such as gifs. + bool paintInSingleTransaction = paintingVisible && (drawingStale || aPaintData->mFirstPaint); + + TILING_LOG("TILING %p: paintingVisible %d drawingStale %d firstPaint %d singleTransaction %d\n", + &mPaintedLayer, paintingVisible, drawingStale, aPaintData->mFirstPaint, paintInSingleTransaction); + + // The following code decides what order to draw tiles in, based on the + // current scroll direction of the primary scrollable layer. + NS_ASSERTION(!aRegionToPaint.IsEmpty(), "Unexpectedly empty paint region!"); + IntRect paintBounds = aRegionToPaint.GetBounds(); + + int startX, incX, startY, incY; + gfx::IntSize scaledTileSize = GetScaledTileSize(); + if (aPaintData->mScrollOffset.x >= aPaintData->mLastScrollOffset.x) { + startX = RoundDownToTileEdge(paintBounds.x, scaledTileSize.width); + incX = scaledTileSize.width; + } else { + startX = RoundDownToTileEdge(paintBounds.XMost() - 1, scaledTileSize.width); + incX = -scaledTileSize.width; + } + + if (aPaintData->mScrollOffset.y >= aPaintData->mLastScrollOffset.y) { + startY = RoundDownToTileEdge(paintBounds.y, scaledTileSize.height); + incY = scaledTileSize.height; + } else { + startY = RoundDownToTileEdge(paintBounds.YMost() - 1, scaledTileSize.height); + incY = -scaledTileSize.height; + } + + // Find a tile to draw. + IntRect tileBounds(startX, startY, scaledTileSize.width, scaledTileSize.height); + int32_t scrollDiffX = aPaintData->mScrollOffset.x - aPaintData->mLastScrollOffset.x; + int32_t scrollDiffY = aPaintData->mScrollOffset.y - aPaintData->mLastScrollOffset.y; + // This loop will always terminate, as there is at least one tile area + // along the first/last row/column intersecting with regionToPaint, or its + // bounds would have been smaller. + while (true) { + aRegionToPaint.And(aInvalidRegion, tileBounds); + if (!aRegionToPaint.IsEmpty()) { + if (mResolution != 1) { + // Paint the entire tile for low-res. This is aimed to fixing low-res resampling + // and to avoid doing costly region accurate painting for a small area. + aRegionToPaint = tileBounds; + } + break; + } + if (Abs(scrollDiffY) >= Abs(scrollDiffX)) { + tileBounds.x += incX; + } else { + tileBounds.y += incY; + } + } + + if (!aRegionToPaint.Contains(aInvalidRegion)) { + // The region needed to paint is larger then our progressive chunk size + // therefore update what we want to paint and ask for a new paint transaction. + + // If we need to draw more than one tile to maintain coherency, make + // sure it happens in the same transaction by requesting this work be + // repeated immediately. + // If this is unnecessary, the remaining work will be done tile-by-tile in + // subsequent transactions. The caller code is responsible for scheduling + // the subsequent transactions as long as we don't set the mPaintFinished + // flag to true. + return (!drawingLowPrecision && paintInSingleTransaction); + } + + // We're not repeating painting and we've not requested a repeat transaction, + // so the paint is finished. If there's still a separate low precision + // paint to do, it will get marked as unfinished later. + aPaintData->mPaintFinished = true; + return false; +} + +bool +ClientMultiTiledLayerBuffer::ProgressiveUpdate(nsIntRegion& aValidRegion, + nsIntRegion& aInvalidRegion, + const nsIntRegion& aOldValidRegion, + BasicTiledLayerPaintData* aPaintData, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData) +{ + TILING_LOG("TILING %p: Progressive update valid region %s\n", &mPaintedLayer, Stringify(aValidRegion).c_str()); + TILING_LOG("TILING %p: Progressive update invalid region %s\n", &mPaintedLayer, Stringify(aInvalidRegion).c_str()); + TILING_LOG("TILING %p: Progressive update old valid region %s\n", &mPaintedLayer, Stringify(aOldValidRegion).c_str()); + + bool repeat = false; + bool isBufferChanged = false; + do { + // Compute the region that should be updated. Repeat as many times as + // is required. + nsIntRegion regionToPaint; + repeat = ComputeProgressiveUpdateRegion(aInvalidRegion, + aOldValidRegion, + regionToPaint, + aPaintData, + repeat); + + TILING_LOG("TILING %p: Progressive update computed paint region %s repeat %d\n", &mPaintedLayer, Stringify(regionToPaint).c_str(), repeat); + + // There's no further work to be done. + if (regionToPaint.IsEmpty()) { + break; + } + + isBufferChanged = true; + + // Keep track of what we're about to refresh. + aValidRegion.Or(aValidRegion, regionToPaint); + + // aValidRegion may have been altered by InvalidateRegion, but we still + // want to display stale content until it gets progressively updated. + // Create a region that includes stale content. + nsIntRegion validOrStale; + validOrStale.Or(aValidRegion, aOldValidRegion); + + // Paint the computed region and subtract it from the invalid region. + PaintThebes(validOrStale, regionToPaint, aInvalidRegion, + aCallback, aCallbackData, true); + aInvalidRegion.Sub(aInvalidRegion, regionToPaint); + } while (repeat); + + TILING_LOG("TILING %p: Progressive update final valid region %s buffer changed %d\n", &mPaintedLayer, Stringify(aValidRegion).c_str(), isBufferChanged); + TILING_LOG("TILING %p: Progressive update final invalid region %s\n", &mPaintedLayer, Stringify(aInvalidRegion).c_str()); + + // Return false if nothing has been drawn, or give what has been drawn + // to the shadow layer to upload. + return isBufferChanged; +} + +void +TiledContentClient::PrintInfo(std::stringstream& aStream, const char* aPrefix) +{ + aStream << aPrefix; + aStream << nsPrintfCString("%sTiledContentClient (0x%p)", mName, this).get(); + + if (profiler_feature_active("displaylistdump")) { + nsAutoCString pfx(aPrefix); + pfx += " "; + + Dump(aStream, pfx.get(), false); + } +} + +void +TiledContentClient::Dump(std::stringstream& aStream, + const char* aPrefix, + bool aDumpHtml, + TextureDumpMode aCompress) +{ + GetTiledBuffer()->Dump(aStream, aPrefix, aDumpHtml, aCompress); +} + +void +BasicTiledLayerPaintData::ResetPaintData() +{ + mLowPrecisionPaintCount = 0; + mPaintFinished = false; + mHasTransformAnimation = false; + mCompositionBounds.SetEmpty(); + mCriticalDisplayPort = Nothing(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/TiledContentClient.h b/gfx/layers/client/TiledContentClient.h new file mode 100644 index 000000000..cf55450f8 --- /dev/null +++ b/gfx/layers/client/TiledContentClient.h @@ -0,0 +1,545 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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_TILEDCONTENTCLIENT_H +#define MOZILLA_GFX_TILEDCONTENTCLIENT_H + +#include <stddef.h> // for size_t +#include <stdint.h> // for uint16_t +#include <algorithm> // for swap +#include <limits> +#include "Layers.h" // for LayerManager, etc +#include "TiledLayerBuffer.h" // for TiledLayerBuffer +#include "Units.h" // for CSSPoint +#include "gfxTypes.h" +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/ipc/Shmem.h" // for Shmem +#include "mozilla/ipc/SharedMemory.h" // for SharedMemory +#include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform +#include "mozilla/layers/CompositableClient.h" // for CompositableClient +#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc +#include "mozilla/layers/LayersMessages.h" // for TileDescriptor +#include "mozilla/layers/LayersTypes.h" // for TextureDumpMode +#include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/TextureClientPool.h" +#include "ClientLayerManager.h" +#include "mozilla/mozalloc.h" // for operator delete +#include "nsISupportsImpl.h" // for MOZ_COUNT_DTOR +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc +#include "nsExpirationTracker.h" +#include "mozilla/layers/ISurfaceAllocator.h" + +namespace mozilla { +namespace layers { + +class ClientTiledPaintedLayer; +class ClientLayerManager; + +/** + * Represent a single tile in tiled buffer. The buffer keeps tiles, + * each tile keeps a reference to a texture client and a read-lock. This + * read-lock is used to help implement a copy-on-write mechanism. The tile + * should be locked before being sent to the compositor. The compositor should + * unlock the read-lock as soon as it has finished with the buffer in the + * TextureHost to prevent more textures being created than is necessary. + * Ideal place to store per tile debug information. + */ +struct TileClient +{ + // Placeholder + TileClient(); + ~TileClient(); + + TileClient(const TileClient& o); + + TileClient& operator=(const TileClient& o); + + bool operator== (const TileClient& o) const + { + return mFrontBuffer == o.mFrontBuffer; + } + + bool operator!= (const TileClient& o) const + { + return mFrontBuffer != o.mFrontBuffer; + } + + void SetTextureAllocator(TextureClientAllocator* aAllocator) + { + mAllocator = aAllocator; + } + + bool IsPlaceholderTile() const + { + return mBackBuffer == nullptr && mFrontBuffer == nullptr; + } + + void DiscardBuffers() + { + DiscardFrontBuffer(); + DiscardBackBuffer(); + } + + nsExpirationState *GetExpirationState() { return &mExpirationState; } + + TileDescriptor GetTileDescriptor(); + + /** + * For debugging. + */ + void Dump(std::stringstream& aStream); + + /** + * Swaps the front and back buffers. + */ + void Flip(); + + void DumpTexture(std::stringstream& aStream, TextureDumpMode aCompress) { + // TODO We should combine the OnWhite/OnBlack here an just output a single image. + CompositableClient::DumpTextureClient(aStream, mFrontBuffer, aCompress); + } + + /** + * Returns an unlocked TextureClient that can be used for writing new + * data to the tile. This may flip the front-buffer to the back-buffer if + * the front-buffer is still locked by the host, or does not have an + * internal buffer (and so will always be locked). + * + * If getting the back buffer required copying pixels from the front buffer + * then the copied region is stored in aAddPaintedRegion so the host side + * knows to upload it. + * + * If nullptr is returned, aTextureClientOnWhite is undefined. + */ + TextureClient* GetBackBuffer(CompositableClient&, + const nsIntRegion& aDirtyRegion, + gfxContentType aContent, SurfaceMode aMode, + nsIntRegion& aAddPaintedRegion, + RefPtr<TextureClient>* aTextureClientOnWhite); + + void DiscardFrontBuffer(); + + void DiscardBackBuffer(); + + /* We wrap the back buffer in a class that disallows assignment + * so that we can track when ever it changes so that we can update + * the expiry tracker for expiring the back buffers */ + class PrivateProtector { + public: + void Set(TileClient * container, RefPtr<TextureClient>); + void Set(TileClient * container, TextureClient*); + // Implicitly convert to TextureClient* because we can't chain + // implicit conversion that would happen on RefPtr<TextureClient> + operator TextureClient*() const { return mBuffer; } + RefPtr<TextureClient> operator ->() { return mBuffer; } + private: + PrivateProtector& operator=(const PrivateProtector &); + RefPtr<TextureClient> mBuffer; + } mBackBuffer; + RefPtr<TextureClient> mBackBufferOnWhite; + RefPtr<TextureClient> mFrontBuffer; + RefPtr<TextureClient> mFrontBufferOnWhite; + RefPtr<TextureClientAllocator> mAllocator; + gfx::IntRect mUpdateRect; + bool mWasPlaceholder; +#ifdef GFX_TILEDLAYER_DEBUG_OVERLAY + TimeStamp mLastUpdate; +#endif + nsIntRegion mInvalidFront; + nsIntRegion mInvalidBack; + nsExpirationState mExpirationState; +private: + // Copies dirty pixels from the front buffer into the back buffer, + // and records the copied region in aAddPaintedRegion. + void ValidateBackBufferFromFront(const nsIntRegion &aDirtyRegion, + nsIntRegion& aAddPaintedRegion); +}; + +/** + * This struct stores all the data necessary to perform a paint so that it + * doesn't need to be recalculated on every repeated transaction. + */ +struct BasicTiledLayerPaintData { + /* + * The scroll offset of the content from the nearest ancestor layer that + * represents scrollable content with a display port set. + */ + ParentLayerPoint mScrollOffset; + + /* + * The scroll offset of the content from the nearest ancestor layer that + * represents scrollable content with a display port set, for the last + * layer update transaction. + */ + ParentLayerPoint mLastScrollOffset; + + /* + * The transform matrix to go from this layer's Layer units to + * the scroll ancestor's ParentLayer units. The "scroll ancestor" is + * the closest ancestor layer which scrolls, and is used to obtain + * the composition bounds that are relevant for this layer. + */ + LayerToParentLayerMatrix4x4 mTransformToCompBounds; + + /* + * The critical displayport of the content from the nearest ancestor layer + * that represents scrollable content with a display port set. isNothing() + * if a critical displayport is not set. + */ + Maybe<LayerIntRect> mCriticalDisplayPort; + + /* + * The render resolution of the document that the content this layer + * represents is in. + */ + CSSToParentLayerScale2D mResolution; + + /* + * The composition bounds of the layer, in Layer coordinates. This is + * used to make sure that tiled updates to regions that are visible to the + * user are grouped coherently. + */ + LayerRect mCompositionBounds; + + /* + * Low precision updates are always executed a tile at a time in repeated + * transactions. This counter is set to 1 on the first transaction of a low + * precision update, and incremented for each subsequent transaction. + */ + uint16_t mLowPrecisionPaintCount; + + /* + * Whether this is the first time this layer is painting + */ + bool mFirstPaint : 1; + + /* + * Whether there is further work to complete this paint. This is used to + * determine whether or not to repeat the transaction when painting + * progressively. + */ + bool mPaintFinished : 1; + + /* + * Whether or not there is an async transform animation active + */ + bool mHasTransformAnimation : 1; + + /* + * Initializes/clears data to prepare for paint action. + */ + void ResetPaintData(); +}; + +class SharedFrameMetricsHelper +{ +public: + SharedFrameMetricsHelper(); + ~SharedFrameMetricsHelper(); + + /** + * This is called by the BasicTileLayer to determine if it is still interested + * in the update of this display-port to continue. We can return true here + * to abort the current update and continue with any subsequent ones. This + * is useful for slow-to-render pages when the display-port starts lagging + * behind enough that continuing to draw it is wasted effort. + */ + bool UpdateFromCompositorFrameMetrics(const LayerMetricsWrapper& aLayer, + bool aHasPendingNewThebesContent, + bool aLowPrecision, + AsyncTransform& aViewTransform); + + /** + * Determines if the compositor's upcoming composition bounds has fallen + * outside of the contents display port. If it has then the compositor + * will start to checker board. Checker boarding is when the compositor + * tries to composite a tile and it is not available. Historically + * a tile with a checker board pattern was used. Now a blank tile is used. + */ + bool AboutToCheckerboard(const FrameMetrics& aContentMetrics, + const FrameMetrics& aCompositorMetrics); +private: + bool mLastProgressiveUpdateWasLowPrecision; + bool mProgressiveUpdateWasInDanger; +}; + +/** + * Provide an instance of TiledLayerBuffer backed by drawable TextureClients. + * This buffer provides an implementation of ValidateTile using a + * thebes callback and can support painting using a single paint buffer. + * Whether a single paint buffer is used is controlled by + * gfxPrefs::PerTileDrawing(). + */ +class ClientTiledLayerBuffer +{ +public: + ClientTiledLayerBuffer(ClientTiledPaintedLayer& aPaintedLayer, + CompositableClient& aCompositableClient) + : mPaintedLayer(aPaintedLayer) + , mCompositableClient(aCompositableClient) + , mLastPaintContentType(gfxContentType::COLOR) + , mLastPaintSurfaceMode(SurfaceMode::SURFACE_OPAQUE) + , mWasLastPaintProgressive(false) + {} + + virtual void PaintThebes(const nsIntRegion& aNewValidRegion, + const nsIntRegion& aPaintRegion, + const nsIntRegion& aDirtyRegion, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData, + bool aIsProgressive = false) = 0; + + virtual bool SupportsProgressiveUpdate() = 0; + virtual bool ProgressiveUpdate(nsIntRegion& aValidRegion, + nsIntRegion& aInvalidRegion, + const nsIntRegion& aOldValidRegion, + BasicTiledLayerPaintData* aPaintData, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData) = 0; + virtual void ResetPaintedAndValidState() = 0; + + virtual const nsIntRegion& GetValidRegion() = 0; + + virtual bool IsLowPrecision() const = 0; + + virtual void Dump(std::stringstream& aStream, + const char* aPrefix, + bool aDumpHtml, + TextureDumpMode aCompress) {} + + const CSSToParentLayerScale2D& GetFrameResolution() { return mFrameResolution; } + void SetFrameResolution(const CSSToParentLayerScale2D& aResolution) { mFrameResolution = aResolution; } + + bool HasFormatChanged() const; + +protected: + void UnlockTile(TileClient& aTile); + gfxContentType GetContentType(SurfaceMode* aMode = nullptr) const; + + ClientTiledPaintedLayer& mPaintedLayer; + CompositableClient& mCompositableClient; + + gfxContentType mLastPaintContentType; + SurfaceMode mLastPaintSurfaceMode; + CSSToParentLayerScale2D mFrameResolution; + + bool mWasLastPaintProgressive; +}; + +class ClientMultiTiledLayerBuffer + : public TiledLayerBuffer<ClientMultiTiledLayerBuffer, TileClient> + , public ClientTiledLayerBuffer +{ + friend class TiledLayerBuffer<ClientMultiTiledLayerBuffer, TileClient>; +public: + ClientMultiTiledLayerBuffer(ClientTiledPaintedLayer& aPaintedLayer, + CompositableClient& aCompositableClient, + ClientLayerManager* aManager, + SharedFrameMetricsHelper* aHelper); + + void PaintThebes(const nsIntRegion& aNewValidRegion, + const nsIntRegion& aPaintRegion, + const nsIntRegion& aDirtyRegion, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData, + bool aIsProgressive = false) override; + + virtual bool SupportsProgressiveUpdate() override { return true; } + /** + * Performs a progressive update of a given tiled buffer. + * See ComputeProgressiveUpdateRegion below for parameter documentation. + */ + bool ProgressiveUpdate(nsIntRegion& aValidRegion, + nsIntRegion& aInvalidRegion, + const nsIntRegion& aOldValidRegion, + BasicTiledLayerPaintData* aPaintData, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData) override; + + void ResetPaintedAndValidState() override { + mPaintedRegion.SetEmpty(); + mValidRegion.SetEmpty(); + mTiles.mSize.width = 0; + mTiles.mSize.height = 0; + DiscardBuffers(); + mRetainedTiles.Clear(); + } + + + const nsIntRegion& GetValidRegion() override { + return TiledLayerBuffer::GetValidRegion(); + } + + bool IsLowPrecision() const override { + return TiledLayerBuffer::IsLowPrecision(); + } + + void Dump(std::stringstream& aStream, + const char* aPrefix, + bool aDumpHtml, + TextureDumpMode aCompress) override { + TiledLayerBuffer::Dump(aStream, aPrefix, aDumpHtml, aCompress); + } + + void ReadLock(); + + void Release(); + + void DiscardBuffers(); + + SurfaceDescriptorTiles GetSurfaceDescriptorTiles(); + + void SetResolution(float aResolution) { + if (mResolution == aResolution) { + return; + } + + Update(nsIntRegion(), nsIntRegion(), nsIntRegion()); + mResolution = aResolution; + } + +protected: + bool ValidateTile(TileClient& aTile, + const nsIntPoint& aTileRect, + const nsIntRegion& dirtyRect); + + void Update(const nsIntRegion& aNewValidRegion, + const nsIntRegion& aPaintRegion, + const nsIntRegion& aDirtyRegion); + + TileClient GetPlaceholderTile() const { return TileClient(); } + +private: + RefPtr<ClientLayerManager> mManager; + LayerManager::DrawPaintedLayerCallback mCallback; + void* mCallbackData; + + // The region that will be made valid during Update(). Once Update() is + // completed then this is identical to mValidRegion. + nsIntRegion mNewValidRegion; + + SharedFrameMetricsHelper* mSharedFrameMetricsHelper; + // When using Moz2D's CreateTiledDrawTarget we maintain a list of gfx::Tiles + std::vector<gfx::Tile> mMoz2DTiles; + /** + * While we're adding tiles, this is used to keep track of the position of + * the top-left of the top-left-most tile. When we come to wrap the tiles in + * TiledDrawTarget we subtract the value of this member from each tile's + * offset so that all the tiles have a positive offset, then add a + * translation to the TiledDrawTarget to compensate. This is important so + * that the mRect of the TiledDrawTarget is always at a positive x/y + * position, otherwise its GetSize() methods will be broken. + */ + gfx::IntPoint mTilingOrigin; + /** + * Calculates the region to update in a single progressive update transaction. + * This employs some heuristics to update the most 'sensible' region to + * update at this point in time, and how large an update should be performed + * at once to maintain visual coherency. + * + * aInvalidRegion is the current invalid region. + * aOldValidRegion is the valid region of mTiledBuffer at the beginning of the + * current transaction. + * aRegionToPaint will be filled with the region to update. This may be empty, + * which indicates that there is no more work to do. + * aIsRepeated should be true if this function has already been called during + * this transaction. + * + * Returns true if it should be called again, false otherwise. In the case + * that aRegionToPaint is empty, this will return aIsRepeated for convenience. + */ + bool ComputeProgressiveUpdateRegion(const nsIntRegion& aInvalidRegion, + const nsIntRegion& aOldValidRegion, + nsIntRegion& aRegionToPaint, + BasicTiledLayerPaintData* aPaintData, + bool aIsRepeated); +}; + +class TiledContentClient : public CompositableClient +{ +public: + TiledContentClient(ClientLayerManager* aManager, + const char* aName = "") + : CompositableClient(aManager->AsShadowForwarder()) + , mName(aName) + {} + +protected: + ~TiledContentClient() + {} + +public: + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix); + + virtual void Dump(std::stringstream& aStream, + const char* aPrefix="", + bool aDumpHtml=false, + TextureDumpMode aCompress=TextureDumpMode::Compress) override; + + virtual TextureInfo GetTextureInfo() const override + { + return TextureInfo(CompositableType::CONTENT_TILED); + } + + + virtual ClientTiledLayerBuffer* GetTiledBuffer() = 0; + virtual ClientTiledLayerBuffer* GetLowPrecisionTiledBuffer() = 0; + + enum TiledBufferType { + TILED_BUFFER, + LOW_PRECISION_TILED_BUFFER + }; + virtual void UpdatedBuffer(TiledBufferType aType) = 0; + +private: + const char* mName; +}; + +/** + * An implementation of TiledContentClient that supports + * multiple tiles and a low precision buffer. + */ +class MultiTiledContentClient : public TiledContentClient +{ +public: + MultiTiledContentClient(ClientTiledPaintedLayer& aPaintedLayer, + ClientLayerManager* aManager); + +protected: + ~MultiTiledContentClient() + { + MOZ_COUNT_DTOR(MultiTiledContentClient); + + mTiledBuffer.DiscardBuffers(); + mLowPrecisionTiledBuffer.DiscardBuffers(); + } + +public: + void ClearCachedResources() override; + void UpdatedBuffer(TiledBufferType aType) override; + + ClientTiledLayerBuffer* GetTiledBuffer() override { return &mTiledBuffer; } + ClientTiledLayerBuffer* GetLowPrecisionTiledBuffer() override { + if (mHasLowPrecision) { + return &mLowPrecisionTiledBuffer; + } + return nullptr; + } + +private: + SharedFrameMetricsHelper mSharedFrameMetricsHelper; + ClientMultiTiledLayerBuffer mTiledBuffer; + ClientMultiTiledLayerBuffer mLowPrecisionTiledBuffer; + bool mHasLowPrecision; +}; + +} // namespace layers +} // namespace mozilla + +#endif |