gfx/layers/client/TextureClientRecycleAllocator.cpp
author Sotaro Ikeda <sikeda@mozilla.com>
Tue, 16 Dec 2014 07:11:48 -0800
changeset 219924 473ecad73b44b88f88c858727c9431f351d76820
parent 215405 92295f515d2de1f859a82c710155df30f74412c8
child 234863 ac4464790ec4896a5188fa50cfc69ae0ffeddc08
permissions -rw-r--r--
Bug 1043558 - Use gralloc for WebRTC camera preview r=jesup,nical

/* -*- 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 <map>
#include <stack>

#include "gfxPlatform.h"
#include "mozilla/layers/GrallocTextureClient.h"
#include "mozilla/layers/ISurfaceAllocator.h"
#include "mozilla/Mutex.h"

#include "TextureClientRecycleAllocator.h"

namespace mozilla {
namespace layers {

class TextureClientRecycleAllocatorImp : public ISurfaceAllocator
{
  ~TextureClientRecycleAllocatorImp();

public:
  explicit TextureClientRecycleAllocatorImp(ISurfaceAllocator* aAllocator);

  void SetMaxPoolSize(uint32_t aMax)
  {
    if (aMax > 0) {
      mMaxPooledSize = aMax;
    }
  }

  // Creates and allocates a TextureClient.
  TemporaryRef<TextureClient>
  CreateOrRecycleForDrawing(gfx::SurfaceFormat aFormat,
                            gfx::IntSize aSize,
                            gfx::BackendType aMoz2dBackend,
                            TextureFlags aTextureFlags,
                            TextureAllocationFlags flags);

  void Destroy();

  void RecycleCallbackImp(TextureClient* aClient);

  static void RecycleCallback(TextureClient* aClient, void* aClosure);

  // ISurfaceAllocator
  virtual LayersBackend GetCompositorBackendType() const MOZ_OVERRIDE
  {
    return mSurfaceAllocator->GetCompositorBackendType();
  }

  virtual bool AllocShmem(size_t aSize,
                          mozilla::ipc::SharedMemory::SharedMemoryType aType,
                          mozilla::ipc::Shmem* aShmem) MOZ_OVERRIDE
  {
    return mSurfaceAllocator->AllocShmem(aSize, aType, aShmem);
  }

  virtual bool AllocUnsafeShmem(size_t aSize,
                                mozilla::ipc::SharedMemory::SharedMemoryType aType,
                                mozilla::ipc::Shmem* aShmem) MOZ_OVERRIDE
  {
    return mSurfaceAllocator->AllocUnsafeShmem(aSize, aType, aShmem);
  }

  virtual void DeallocShmem(mozilla::ipc::Shmem& aShmem) MOZ_OVERRIDE
  {
    mSurfaceAllocator->DeallocShmem(aShmem);
  }

  virtual bool IsSameProcess() const MOZ_OVERRIDE
  {
    return mSurfaceAllocator->IsSameProcess();
  }

protected:
  // ISurfaceAllocator
  virtual bool IsOnCompositorSide() const MOZ_OVERRIDE
  {
    return false;
  }

private:
  static const uint32_t kMaxPooledSized = 2;

  // 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)
    {}

    TextureClient* GetTextureClient()
    {
      return mTextureClient;
    }
    void ClearTextureClient() { mTextureClient = nullptr; }
  protected:
    RefPtr<TextureClient> mTextureClient;
  };

  bool mDestroyed;
  uint32_t mMaxPooledSize;
  RefPtr<ISurfaceAllocator> mSurfaceAllocator;
  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;
};

TextureClientRecycleAllocatorImp::TextureClientRecycleAllocatorImp(ISurfaceAllocator *aAllocator)
  : mDestroyed(false)
  , mMaxPooledSize(kMaxPooledSized)
  , mSurfaceAllocator(aAllocator)
  , mLock("TextureClientRecycleAllocatorImp.mLock")
{
}

TextureClientRecycleAllocatorImp::~TextureClientRecycleAllocatorImp()
{
  MOZ_ASSERT(mDestroyed);
  MOZ_ASSERT(mPooledClients.empty());
  MOZ_ASSERT(mInUseClients.empty());
}

TemporaryRef<TextureClient>
TextureClientRecycleAllocatorImp::CreateOrRecycleForDrawing(
                                             gfx::SurfaceFormat aFormat,
                                             gfx::IntSize aSize,
                                             gfx::BackendType aMoz2DBackend,
                                             TextureFlags aTextureFlags,
                                             TextureAllocationFlags aAllocFlags)
{
  // TextureAllocationFlags is actually used only by ContentClient.
  // This class does not handle ConteClient's TextureClient allocation.
  MOZ_ASSERT(aAllocFlags == TextureAllocationFlags::ALLOC_DEFAULT ||
             aAllocFlags == TextureAllocationFlags::ALLOC_DISALLOW_BUFFERTEXTURECLIENT);
  MOZ_ASSERT(!(aTextureFlags & TextureFlags::RECYCLE));
  aTextureFlags = aTextureFlags | TextureFlags::RECYCLE; // Set recycle flag

  RefPtr<TextureClientHolder> textureHolder;

  if (aMoz2DBackend == gfx::BackendType::NONE) {
    aMoz2DBackend = gfxPlatform::GetPlatform()->GetContentBackend();
  }

  {
    MutexAutoLock lock(mLock);
    if (mDestroyed) {
      return nullptr;
    } else if (!mPooledClients.empty()) {
      textureHolder = mPooledClients.top();
      mPooledClients.pop();
      // If a pooled TextureClient is not compatible, release it.
      if (textureHolder->GetTextureClient()->GetFormat() != aFormat ||
          textureHolder->GetTextureClient()->GetSize() != aSize)
      {
        TextureClientReleaseTask* task = new TextureClientReleaseTask(textureHolder->GetTextureClient());
        textureHolder->ClearTextureClient();
        textureHolder = nullptr;
        // Release TextureClient.
        mSurfaceAllocator->GetMessageLoop()->PostTask(FROM_HERE, task);
      } else {
        textureHolder->GetTextureClient()->RecycleTexture(aTextureFlags);
      }
    }
  }

  if (!textureHolder) {
    // Allocate new TextureClient
    RefPtr<TextureClient> texture;
    texture = TextureClient::CreateForDrawing(this, aFormat, aSize, aMoz2DBackend,
                                              aTextureFlags, aAllocFlags);
    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;
  }
  textureHolder->GetTextureClient()->SetRecycleCallback(TextureClientRecycleAllocatorImp::RecycleCallback, this);
  return textureHolder->GetTextureClient();
}

void
TextureClientRecycleAllocatorImp::Destroy()
{
  MutexAutoLock lock(mLock);
  if (mDestroyed) {
    return;
  }
  mDestroyed = true;
  while (!mPooledClients.empty()) {
    mPooledClients.pop();
  }
}

void
TextureClientRecycleAllocatorImp::RecycleCallbackImp(TextureClient* aClient)
{
  RefPtr<TextureClientHolder> textureHolder;
  aClient->ClearRecycleCallback();
  {
    MutexAutoLock lock(mLock);
    if (mInUseClients.find(aClient) != mInUseClients.end()) {
      textureHolder = mInUseClients[aClient]; // Keep reference count of TextureClientHolder within lock.
      if (!mDestroyed && mPooledClients.size() < mMaxPooledSize) {
        mPooledClients.push(textureHolder);
      }
      mInUseClients.erase(aClient);
    }
  }
}

/* static */ void
TextureClientRecycleAllocatorImp::RecycleCallback(TextureClient* aClient, void* aClosure)
{
  TextureClientRecycleAllocatorImp* recycleAllocator = static_cast<TextureClientRecycleAllocatorImp*>(aClosure);
  recycleAllocator->RecycleCallbackImp(aClient);
}

TextureClientRecycleAllocator::TextureClientRecycleAllocator(ISurfaceAllocator *aAllocator)
{
  mAllocator = new TextureClientRecycleAllocatorImp(aAllocator);
}

TextureClientRecycleAllocator::~TextureClientRecycleAllocator()
{
  mAllocator->Destroy();
  mAllocator = nullptr;
}

void
TextureClientRecycleAllocator::SetMaxPoolSize(uint32_t aMax)
{
  mAllocator->SetMaxPoolSize(aMax);
}

TemporaryRef<TextureClient>
TextureClientRecycleAllocator::CreateOrRecycleForDrawing(
                                            gfx::SurfaceFormat aFormat,
                                            gfx::IntSize aSize,
                                            gfx::BackendType aMoz2DBackend,
                                            TextureFlags aTextureFlags,
                                            TextureAllocationFlags aAllocFlags)
{
  return mAllocator->CreateOrRecycleForDrawing(aFormat,
                                               aSize,
                                               aMoz2DBackend,
                                               aTextureFlags,
                                               aAllocFlags);
}

}
}