/* -*- 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 "BufferTexture.h"
#include "mozilla/layers/ImageDataSerializer.h"
#include "mozilla/layers/ISurfaceAllocator.h"
#include "mozilla/layers/CompositableForwarder.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/fallible.h"
#include "libyuv.h"

#ifdef MOZ_WIDGET_GTK
#include "gfxPlatformGtk.h"
#endif

namespace mozilla {
namespace layers {

class MemoryTextureData : public BufferTextureData
{
public:
  static MemoryTextureData* Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
                                   gfx::BackendType aMoz2DBackend,
                                   LayersBackend aLayersBackend,
                                   TextureFlags aFlags,
                                   TextureAllocationFlags aAllocFlags,
                                   LayersIPCChannel* aAllocator);

  virtual TextureData*
  CreateSimilar(LayersIPCChannel* aAllocator,
                LayersBackend aLayersBackend,
                TextureFlags aFlags = TextureFlags::DEFAULT,
                TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT) const override;

  virtual bool Serialize(SurfaceDescriptor& aOutDescriptor) override;

  virtual void Deallocate(LayersIPCChannel*) override;

  MemoryTextureData(const BufferDescriptor& aDesc,
                    gfx::BackendType aMoz2DBackend,
                    uint8_t* aBuffer, size_t aBufferSize)
  : BufferTextureData(aDesc, aMoz2DBackend)
  , mBuffer(aBuffer)
  , mBufferSize(aBufferSize)
  {
    MOZ_ASSERT(aBuffer);
    MOZ_ASSERT(aBufferSize);
  }

  virtual uint8_t* GetBuffer() override { return mBuffer; }

  virtual size_t GetBufferSize() override { return mBufferSize; }

protected:
  uint8_t* mBuffer;
  size_t mBufferSize;
};

class ShmemTextureData : public BufferTextureData
{
public:
  static ShmemTextureData* Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
                                  gfx::BackendType aMoz2DBackend,
                                  LayersBackend aLayersBackend,
                                  TextureFlags aFlags,
                                  TextureAllocationFlags aAllocFlags,
                                  LayersIPCChannel* aAllocator);

  virtual TextureData*
  CreateSimilar(LayersIPCChannel* aAllocator,
                LayersBackend aLayersBackend,
                TextureFlags aFlags = TextureFlags::DEFAULT,
                TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT) const override;

  virtual bool Serialize(SurfaceDescriptor& aOutDescriptor) override;

  virtual void Deallocate(LayersIPCChannel* aAllocator) override;

  ShmemTextureData(const BufferDescriptor& aDesc,
                   gfx::BackendType aMoz2DBackend, mozilla::ipc::Shmem aShmem)
  : BufferTextureData(aDesc, aMoz2DBackend)
  , mShmem(aShmem)
  {
    MOZ_ASSERT(mShmem.Size<uint8_t>());
  }

  virtual uint8_t* GetBuffer() override { return mShmem.get<uint8_t>(); }

  virtual size_t GetBufferSize() override { return mShmem.Size<uint8_t>(); }

protected:
  mozilla::ipc::Shmem mShmem;
};

static bool UsingX11Compositor()
{
#ifdef MOZ_WIDGET_GTK
  return gfx::gfxVars::UseXRender();
#endif
  return false;
}

bool ComputeHasIntermediateBuffer(gfx::SurfaceFormat aFormat,
                                  LayersBackend aLayersBackend)
{
  return aLayersBackend != LayersBackend::LAYERS_BASIC
      || UsingX11Compositor()
      || aFormat == gfx::SurfaceFormat::UNKNOWN;
}

BufferTextureData*
BufferTextureData::Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
                          gfx::BackendType aMoz2DBackend,
                          LayersBackend aLayersBackend, TextureFlags aFlags,
                          TextureAllocationFlags aAllocFlags,
                          LayersIPCChannel* aAllocator)
{
  if (!aAllocator || aAllocator->IsSameProcess()) {
    return MemoryTextureData::Create(aSize, aFormat, aMoz2DBackend,
                                     aLayersBackend, aFlags,
                                     aAllocFlags, aAllocator);
  } else {
    return ShmemTextureData::Create(aSize, aFormat, aMoz2DBackend,
                                    aLayersBackend, aFlags,
                                    aAllocFlags, aAllocator);
  }
}

BufferTextureData*
BufferTextureData::CreateInternal(LayersIPCChannel* aAllocator,
                                  const BufferDescriptor& aDesc,
                                  gfx::BackendType aMoz2DBackend,
                                  int32_t aBufferSize,
                                  TextureFlags aTextureFlags)
{
  if (!aAllocator || aAllocator->IsSameProcess()) {
    uint8_t* buffer = new (fallible) uint8_t[aBufferSize];
    if (!buffer) {
      return nullptr;
    }

    GfxMemoryImageReporter::DidAlloc(buffer);

    return new MemoryTextureData(aDesc, aMoz2DBackend, buffer, aBufferSize);
  } else {
    ipc::Shmem shm;
    if (!aAllocator->AllocUnsafeShmem(aBufferSize, OptimalShmemType(), &shm)) {
      return nullptr;
    }

    return new ShmemTextureData(aDesc, aMoz2DBackend, shm);
  }
}

BufferTextureData*
BufferTextureData::CreateForYCbCrWithBufferSize(KnowsCompositor* aAllocator,
                                                int32_t aBufferSize,
                                                YUVColorSpace aYUVColorSpace,
                                                TextureFlags aTextureFlags)
{
  if (aBufferSize == 0 || !gfx::Factory::CheckBufferSize(aBufferSize)) {
    return nullptr;
  }

  bool hasIntermediateBuffer = aAllocator ? ComputeHasIntermediateBuffer(gfx::SurfaceFormat::YUV,
                                                                         aAllocator->GetCompositorBackendType())
                                          : true;

  // Initialize the metadata with something, even if it will have to be rewritten
  // afterwards since we don't know the dimensions of the texture at this point.
  BufferDescriptor desc = YCbCrDescriptor(gfx::IntSize(), gfx::IntSize(),
                                          0, 0, 0, StereoMode::MONO,
                                          aYUVColorSpace,
                                          hasIntermediateBuffer);

  return CreateInternal(aAllocator ? aAllocator->GetTextureForwarder() : nullptr,
                       desc, gfx::BackendType::NONE, aBufferSize, aTextureFlags);
}

BufferTextureData*
BufferTextureData::CreateForYCbCr(KnowsCompositor* aAllocator,
                                  gfx::IntSize aYSize,
                                  gfx::IntSize aCbCrSize,
                                  StereoMode aStereoMode,
                                  YUVColorSpace aYUVColorSpace,
                                  TextureFlags aTextureFlags)
{
  uint32_t bufSize = ImageDataSerializer::ComputeYCbCrBufferSize(aYSize, aCbCrSize);
  if (bufSize == 0) {
    return nullptr;
  }

  uint32_t yOffset;
  uint32_t cbOffset;
  uint32_t crOffset;
  ImageDataSerializer::ComputeYCbCrOffsets(aYSize.width, aYSize.height,
                                          aCbCrSize.width, aCbCrSize.height,
                                          yOffset, cbOffset, crOffset);

  bool hasIntermediateBuffer = aAllocator ? ComputeHasIntermediateBuffer(gfx::SurfaceFormat::YUV,
                                                                         aAllocator->GetCompositorBackendType())
                                          : true;

  YCbCrDescriptor descriptor = YCbCrDescriptor(aYSize, aCbCrSize, yOffset, cbOffset,
                                               crOffset, aStereoMode, aYUVColorSpace,
                                               hasIntermediateBuffer);

 return CreateInternal(aAllocator ? aAllocator->GetTextureForwarder() : nullptr, descriptor,
                       gfx::BackendType::NONE, bufSize, aTextureFlags);
}

void
BufferTextureData::FillInfo(TextureData::Info& aInfo) const
{
  aInfo.size = GetSize();
  aInfo.format = GetFormat();
  aInfo.hasSynchronization = false;
  aInfo.canExposeMappedData = true;

  if (mDescriptor.type() == BufferDescriptor::TYCbCrDescriptor) {
    aInfo.hasIntermediateBuffer = mDescriptor.get_YCbCrDescriptor().hasIntermediateBuffer();
  } else {
    aInfo.hasIntermediateBuffer = mDescriptor.get_RGBDescriptor().hasIntermediateBuffer();
  }

  switch (aInfo.format) {
    case gfx::SurfaceFormat::YUV:
    case gfx::SurfaceFormat::UNKNOWN:
      aInfo.supportsMoz2D = false;
      break;
    default:
      aInfo.supportsMoz2D = true;
  }
}

gfx::IntSize
BufferTextureData::GetSize() const
{
  return ImageDataSerializer::SizeFromBufferDescriptor(mDescriptor);
}

Maybe<gfx::IntSize>
BufferTextureData::GetCbCrSize() const
{
  return ImageDataSerializer::CbCrSizeFromBufferDescriptor(mDescriptor);
}

Maybe<YUVColorSpace>
BufferTextureData::GetYUVColorSpace() const
{
  return ImageDataSerializer::YUVColorSpaceFromBufferDescriptor(mDescriptor);
}

Maybe<StereoMode>
BufferTextureData::GetStereoMode() const
{
  return ImageDataSerializer::StereoModeFromBufferDescriptor(mDescriptor);
}

gfx::SurfaceFormat
BufferTextureData::GetFormat() const
{
  return ImageDataSerializer::FormatFromBufferDescriptor(mDescriptor);
}

already_AddRefed<gfx::DrawTarget>
BufferTextureData::BorrowDrawTarget()
{
  if (mDrawTarget) {
    mDrawTarget->SetTransform(gfx::Matrix());
    RefPtr<gfx::DrawTarget> dt = mDrawTarget;
    return dt.forget();
  }

  if (mDescriptor.type() != BufferDescriptor::TRGBDescriptor) {
    return nullptr;
  }

  const RGBDescriptor& rgb = mDescriptor.get_RGBDescriptor();

  uint32_t stride = ImageDataSerializer::GetRGBStride(rgb);
  mDrawTarget = gfx::Factory::CreateDrawTargetForData(mMoz2DBackend,
                                                      GetBuffer(), rgb.size(),
                                                      stride, rgb.format(), true);

  if (mDrawTarget) {
    RefPtr<gfx::DrawTarget> dt = mDrawTarget;
    return dt.forget();
  }

  // TODO - should we warn? should we really fallback to cairo? perhaps
  // at least update mMoz2DBackend...
  if (mMoz2DBackend != gfx::BackendType::CAIRO) {
    mDrawTarget = gfx::Factory::CreateDrawTargetForData(gfx::BackendType::CAIRO,
                                                        GetBuffer(), rgb.size(),
                                                        stride, rgb.format(), true);
  }

  if (!mDrawTarget) {
    gfxCriticalNote << "BorrowDrawTarget failure, original backend " << (int)mMoz2DBackend;
  }

  RefPtr<gfx::DrawTarget> dt = mDrawTarget;
  return dt.forget();
}

bool
BufferTextureData::BorrowMappedData(MappedTextureData& aData)
{
  if (GetFormat() == gfx::SurfaceFormat::YUV) {
    return false;
  }

  gfx::IntSize size = GetSize();

  aData.data = GetBuffer();
  aData.size = size;
  aData.format = GetFormat();
  aData.stride = ImageDataSerializer::ComputeRGBStride(aData.format, size.width);

  return true;
}

bool
BufferTextureData::BorrowMappedYCbCrData(MappedYCbCrTextureData& aMap)
{
  if (mDescriptor.type() != BufferDescriptor::TYCbCrDescriptor) {
    return false;
  }

  const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor();

  uint8_t* data = GetBuffer();
  auto ySize = desc.ySize();
  auto cbCrSize = desc.cbCrSize();

  aMap.stereoMode = desc.stereoMode();
  aMap.metadata = nullptr;

  aMap.y.data = data + desc.yOffset();
  aMap.y.size = ySize;
  aMap.y.stride = ySize.width;
  aMap.y.skip = 0;

  aMap.cb.data = data + desc.cbOffset();
  aMap.cb.size = cbCrSize;
  aMap.cb.stride = cbCrSize.width;
  aMap.cb.skip = 0;

  aMap.cr.data = data + desc.crOffset();
  aMap.cr.size = cbCrSize;
  aMap.cr.stride = cbCrSize.width;
  aMap.cr.skip = 0;

  return true;
}

bool
BufferTextureData::UpdateFromSurface(gfx::SourceSurface* aSurface)
{
  if (mDescriptor.type() != BufferDescriptor::TRGBDescriptor) {
    return false;
  }
  const RGBDescriptor& rgb = mDescriptor.get_RGBDescriptor();

  uint32_t stride = ImageDataSerializer::GetRGBStride(rgb);
  RefPtr<gfx::DataSourceSurface> surface =
    gfx::Factory::CreateWrappingDataSourceSurface(GetBuffer(), stride,
                                                  rgb.size(), rgb.format());

  if (!surface) {
    gfxCriticalError() << "Failed to get serializer as surface!";
    return false;
  }

  RefPtr<gfx::DataSourceSurface> srcSurf = aSurface->GetDataSurface();

  if (!srcSurf) {
    gfxCriticalError() << "Failed to GetDataSurface in UpdateFromSurface (BT).";
    return false;
  }

  if (surface->GetSize() != srcSurf->GetSize() || surface->GetFormat() != srcSurf->GetFormat()) {
    gfxCriticalError() << "Attempt to update texture client from a surface with a different size or format (BT)! This: " << surface->GetSize() << " " << surface->GetFormat() << " Other: " << aSurface->GetSize() << " " << aSurface->GetFormat();
    return false;
  }

  gfx::DataSourceSurface::MappedSurface sourceMap;
  gfx::DataSourceSurface::MappedSurface destMap;
  if (!srcSurf->Map(gfx::DataSourceSurface::READ, &sourceMap)) {
    gfxCriticalError() << "Failed to map source surface for UpdateFromSurface (BT).";
    return false;
  }

  if (!surface->Map(gfx::DataSourceSurface::WRITE, &destMap)) {
    srcSurf->Unmap();
    gfxCriticalError() << "Failed to map destination surface for UpdateFromSurface.";
    return false;
  }


  for (int y = 0; y < srcSurf->GetSize().height; y++) {
    memcpy(destMap.mData + destMap.mStride * y,
           sourceMap.mData + sourceMap.mStride * y,
           srcSurf->GetSize().width * BytesPerPixel(srcSurf->GetFormat()));
  }

  srcSurf->Unmap();
  surface->Unmap();

  return true;
}

void
BufferTextureData::SetDesciptor(const BufferDescriptor& aDescriptor)
{
  MOZ_ASSERT(mDescriptor.type() == BufferDescriptor::TYCbCrDescriptor);
  MOZ_ASSERT(mDescriptor.get_YCbCrDescriptor().ySize() == gfx::IntSize());
  mDescriptor = aDescriptor;
}

bool
MemoryTextureData::Serialize(SurfaceDescriptor& aOutDescriptor)
{
  MOZ_ASSERT(GetFormat() != gfx::SurfaceFormat::UNKNOWN);
  if (GetFormat() == gfx::SurfaceFormat::UNKNOWN) {
    return false;
  }

  uintptr_t ptr = reinterpret_cast<uintptr_t>(mBuffer);
  aOutDescriptor = SurfaceDescriptorBuffer(mDescriptor, MemoryOrShmem(ptr));

  return true;
}

static bool
InitBuffer(uint8_t* buf, size_t bufSize,
           gfx::SurfaceFormat aFormat, TextureAllocationFlags aAllocFlags,
           bool aAlreadyZero)
{
  if (!buf) {
    gfxDebug() << "BufferTextureData: Failed to allocate " << bufSize << " bytes";
    return false;
  }

  if ((aAllocFlags & ALLOC_CLEAR_BUFFER) ||
      (aAllocFlags & ALLOC_CLEAR_BUFFER_BLACK)) {
    if (aFormat == gfx::SurfaceFormat::B8G8R8X8) {
      // Even though BGRX was requested, XRGB_UINT32 is what is meant,
      // so use 0xFF000000 to put alpha in the right place.
      libyuv::ARGBRect(buf, bufSize, 0, 0, bufSize / sizeof(uint32_t), 1, 0xFF000000);
    } else if (!aAlreadyZero) {
      memset(buf, 0, bufSize);
    }
  }

  if (aAllocFlags & ALLOC_CLEAR_BUFFER_WHITE) {
    memset(buf, 0xFF, bufSize);
  }

  return true;
}

MemoryTextureData*
MemoryTextureData::Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
                          gfx::BackendType aMoz2DBackend,
                          LayersBackend aLayersBackend, TextureFlags aFlags,
                          TextureAllocationFlags aAllocFlags,
                          LayersIPCChannel* aAllocator)
{
  // Should have used CreateForYCbCr.
  MOZ_ASSERT(aFormat != gfx::SurfaceFormat::YUV);

  if (aSize.width <= 0 || aSize.height <= 0) {
    gfxDebug() << "Asking for buffer of invalid size " << aSize.width << "x" << aSize.height;
    return nullptr;
  }

  uint32_t bufSize = ImageDataSerializer::ComputeRGBBufferSize(aSize, aFormat);
  if (!bufSize) {
    return nullptr;
  }

  uint8_t* buf = new (fallible) uint8_t[bufSize];
  if (!InitBuffer(buf, bufSize, aFormat, aAllocFlags, false)) {
    return nullptr;
  }

  bool hasIntermediateBuffer = ComputeHasIntermediateBuffer(aFormat, aLayersBackend);

  GfxMemoryImageReporter::DidAlloc(buf);

  BufferDescriptor descriptor = RGBDescriptor(aSize, aFormat, hasIntermediateBuffer);

  return new MemoryTextureData(descriptor, aMoz2DBackend, buf, bufSize);
}

void
MemoryTextureData::Deallocate(LayersIPCChannel*)
{
  MOZ_ASSERT(mBuffer);
  GfxMemoryImageReporter::WillFree(mBuffer);
  delete [] mBuffer;
  mBuffer = nullptr;
}

TextureData*
MemoryTextureData::CreateSimilar(LayersIPCChannel* aAllocator,
                                 LayersBackend aLayersBackend,
                                 TextureFlags aFlags,
                                 TextureAllocationFlags aAllocFlags) const
{
  return MemoryTextureData::Create(GetSize(), GetFormat(), mMoz2DBackend,
                                   aLayersBackend, aFlags, aAllocFlags, aAllocator);
}

bool
ShmemTextureData::Serialize(SurfaceDescriptor& aOutDescriptor)
{
  MOZ_ASSERT(GetFormat() != gfx::SurfaceFormat::UNKNOWN);
  if (GetFormat() == gfx::SurfaceFormat::UNKNOWN) {
    return false;
  }

  aOutDescriptor = SurfaceDescriptorBuffer(mDescriptor, MemoryOrShmem(mShmem));

  return true;
}

ShmemTextureData*
ShmemTextureData::Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
                         gfx::BackendType aMoz2DBackend,
                         LayersBackend aLayersBackend, TextureFlags aFlags,
                         TextureAllocationFlags aAllocFlags,
                         LayersIPCChannel* aAllocator)
{
  MOZ_ASSERT(aAllocator);
  // Should have used CreateForYCbCr.
  MOZ_ASSERT(aFormat != gfx::SurfaceFormat::YUV);

  if (!aAllocator) {
    return nullptr;
  }

  if (aSize.width <= 0 || aSize.height <= 0) {
    gfxDebug() << "Asking for buffer of invalid size " << aSize.width << "x" << aSize.height;
    return nullptr;
  }

  uint32_t bufSize = ImageDataSerializer::ComputeRGBBufferSize(aSize, aFormat);
  if (!bufSize) {
    return nullptr;
  }

  mozilla::ipc::Shmem shm;
  if (!aAllocator->AllocUnsafeShmem(bufSize, OptimalShmemType(), &shm)) {
    return nullptr;
  }

  uint8_t* buf = shm.get<uint8_t>();
  if (!InitBuffer(buf, bufSize, aFormat, aAllocFlags, true)) {
    return nullptr;
  }

  bool hasIntermediateBuffer = ComputeHasIntermediateBuffer(aFormat, aLayersBackend);

  BufferDescriptor descriptor = RGBDescriptor(aSize, aFormat, hasIntermediateBuffer);

  return new ShmemTextureData(descriptor, aMoz2DBackend, shm);

  return nullptr;
}

TextureData*
ShmemTextureData::CreateSimilar(LayersIPCChannel* aAllocator,
                                LayersBackend aLayersBackend,
                                TextureFlags aFlags,
                                TextureAllocationFlags aAllocFlags) const
{
  return ShmemTextureData::Create(GetSize(), GetFormat(), mMoz2DBackend,
                                  aLayersBackend, aFlags, aAllocFlags, aAllocator);
}

void
ShmemTextureData::Deallocate(LayersIPCChannel* aAllocator)
{
  aAllocator->DeallocShmem(mShmem);
}

} // namespace
} // namespace