/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "SharedRGBImage.h"
#include "ImageTypes.h"         // for ImageFormat::SHARED_RGB, etc
#include "mozilla/ipc/Shmem.h"  // for Shmem
#include "gfx2DGlue.h"          // for ImageFormatToSurfaceFormat, etc
#include "gfxPlatform.h"        // for gfxPlatform, gfxImageFormat
#include "mozilla/gfx/Point.h"  // for IntSIze
#include "mozilla/layers/BufferTexture.h"
#include "mozilla/layers/ISurfaceAllocator.h"  // for ISurfaceAllocator, etc
#include "mozilla/layers/ImageClient.h"        // for ImageClient
#include "mozilla/layers/LayersSurfaces.h"     // for SurfaceDescriptor, etc
#include "mozilla/layers/TextureClient.h"      // for BufferTextureClient, etc
#include "mozilla/layers/TextureClientRecycleAllocator.h"  // for ITextureClientAllocationHelper
#include "mozilla/layers/ImageBridgeChild.h"  // for ImageBridgeChild
#include "mozilla/mozalloc.h"                 // for operator delete, etc
#include "nsDebug.h"                          // for NS_WARNING, NS_ASSERTION
#include "nsISupportsImpl.h"                  // for Image::AddRef, etc
#include "nsProxyRelease.h"
#include "nsRect.h"  // for mozilla::gfx::IntRect

// Just big enough for a 1080p RGBA32 frame
#define MAX_FRAME_SIZE (16 * 1024 * 1024)

namespace mozilla {
namespace layers {

class TextureClientForRawBufferAccessAllocationHelper
    : public ITextureClientAllocationHelper {
 public:
  TextureClientForRawBufferAccessAllocationHelper(gfx::SurfaceFormat aFormat,
                                                  gfx::IntSize aSize,
                                                  TextureFlags aTextureFlags)
      : ITextureClientAllocationHelper(aFormat, aSize, BackendSelector::Content,
                                       aTextureFlags, ALLOC_DEFAULT) {}

  bool IsCompatible(TextureClient* aTextureClient) override {
    bool ret = aTextureClient->GetFormat() == mFormat &&
               aTextureClient->GetSize() == mSize;
    return ret;
  }

  already_AddRefed<TextureClient> Allocate(
      KnowsCompositor* aAllocator) override {
    return TextureClient::CreateForRawBufferAccess(
        aAllocator, mFormat, mSize, gfx::BackendType::NONE, mTextureFlags);
  }
};

SharedRGBImage::SharedRGBImage(ImageClient* aCompositable)
    : Image(nullptr, ImageFormat::SHARED_RGB), mCompositable(aCompositable) {
  MOZ_COUNT_CTOR(SharedRGBImage);
}

SharedRGBImage::SharedRGBImage(TextureClientRecycleAllocator* aRecycleAllocator)
    : Image(nullptr, ImageFormat::SHARED_RGB),
      mRecycleAllocator(aRecycleAllocator) {
  MOZ_COUNT_CTOR(SharedRGBImage);
}

SharedRGBImage::~SharedRGBImage() {
  MOZ_COUNT_DTOR(SharedRGBImage);
  NS_ReleaseOnMainThread("SharedRGBImage::mSourceSurface",
                         mSourceSurface.forget());
}

TextureClientRecycleAllocator* SharedRGBImage::RecycleAllocator() {
  static const uint32_t MAX_POOLED_VIDEO_COUNT = 5;

  if (!mRecycleAllocator && mCompositable) {
    if (!mCompositable->HasTextureClientRecycler()) {
      // Initialize TextureClientRecycler
      mCompositable->GetTextureClientRecycler()->SetMaxPoolSize(
          MAX_POOLED_VIDEO_COUNT);
    }
    mRecycleAllocator = mCompositable->GetTextureClientRecycler();
  }
  return mRecycleAllocator;
}

bool SharedRGBImage::Allocate(gfx::IntSize aSize, gfx::SurfaceFormat aFormat) {
  mSize = aSize;

  TextureFlags flags =
      mCompositable ? mCompositable->GetTextureFlags() : TextureFlags::DEFAULT;
  {
    TextureClientForRawBufferAccessAllocationHelper helper(aFormat, aSize,
                                                           flags);
    mTextureClient =
        RecycleAllocator()->CreateOrRecycle(helper).unwrapOr(nullptr);
  }

  return !!mTextureClient;
}

gfx::IntSize SharedRGBImage::GetSize() const { return mSize; }

TextureClient* SharedRGBImage::GetTextureClient(
    KnowsCompositor* aKnowsCompositor) {
  return mTextureClient.get();
}

static void ReleaseTextureClient(void* aData) {
  RELEASE_MANUALLY(static_cast<TextureClient*>(aData));
}

static gfx::UserDataKey sTextureClientKey;

already_AddRefed<gfx::SourceSurface> SharedRGBImage::GetAsSourceSurface() {
  NS_ASSERTION(NS_IsMainThread(), "Must be main thread");

  if (mSourceSurface) {
    RefPtr<gfx::SourceSurface> surface(mSourceSurface);
    return surface.forget();
  }

  RefPtr<gfx::SourceSurface> surface;
  {
    // We are 'borrowing' the DrawTarget and retaining a permanent reference to
    // the underlying data (via the surface). It is in this instance since we
    // know that the TextureClient is always wrapping a BufferTextureData and
    // therefore it won't go away underneath us.
    BufferTextureData* decoded_buffer =
        mTextureClient->GetInternalData()->AsBufferTextureData();
    RefPtr<gfx::DrawTarget> drawTarget = decoded_buffer->BorrowDrawTarget();

    if (!drawTarget) {
      return nullptr;
    }

    surface = drawTarget->Snapshot();
    if (!surface) {
      return nullptr;
    }

    // The surface may outlive the owning TextureClient. So, we need to ensure
    // that the surface keeps the TextureClient alive via a reference held in
    // user data. The TextureClient's DrawTarget only has a weak reference to
    // the surface, so we won't create any cycles by just referencing the
    // TextureClient.
    if (!surface->GetUserData(&sTextureClientKey)) {
      surface->AddUserData(&sTextureClientKey, mTextureClient,
                           ReleaseTextureClient);
      ADDREF_MANUALLY(mTextureClient);
    }
  }

  mSourceSurface = surface;
  return surface.forget();
}

}  // namespace layers
}  // namespace mozilla
