/* -*- 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 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 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 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 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 TextureClientRecycleAllocator::CreateOrRecycle(ITextureClientAllocationHelper& aHelper) { MOZ_ASSERT(aHelper.mTextureFlags & TextureFlags::RECYCLE); RefPtr 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 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 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 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 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 >::iterator it; for (it = mInUseClients.begin(); it != mInUseClients.end(); it++) { RefPtr 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 kungFuDeathGrip(this); aClient->SetRecycleAllocator(nullptr); RefPtr 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