/* -*- 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 "SourceSurfaceD2D.h"
#include "DrawTargetD2D.h"
#include "Logging.h"
#include "Tools.h"

namespace mozilla {
namespace gfx {

SourceSurfaceD2D::SourceSurfaceD2D()
{
}

SourceSurfaceD2D::~SourceSurfaceD2D()
{
  if (mBitmap) {
    DrawTargetD2D::mVRAMUsageSS -= GetByteSize();
  }
}

IntSize
SourceSurfaceD2D::GetSize() const
{
  return mSize;
}

SurfaceFormat
SourceSurfaceD2D::GetFormat() const
{
  return mFormat;
}

bool
SourceSurfaceD2D::IsValid() const
{
  return mDevice == Factory::GetDirect3D10Device();
}

TemporaryRef<DataSourceSurface>
SourceSurfaceD2D::GetDataSurface()
{
  RefPtr<DataSourceSurfaceD2D> result = new DataSourceSurfaceD2D(this);
  if (result->IsValid()) {
    return result;
  }
  return nullptr;
}

bool
SourceSurfaceD2D::InitFromData(unsigned char *aData,
                               const IntSize &aSize,
                               int32_t aStride,
                               SurfaceFormat aFormat,
                               ID2D1RenderTarget *aRT)
{
  HRESULT hr;

  mFormat = aFormat;
  mSize = aSize;

  if ((uint32_t)aSize.width > aRT->GetMaximumBitmapSize() ||
      (uint32_t)aSize.height > aRT->GetMaximumBitmapSize()) {
    gfxDebug() << "Bitmap does not fit in texture.";
    return false;
  }

  D2D1_BITMAP_PROPERTIES props = D2D1::BitmapProperties(D2DPixelFormat(aFormat));
  hr = aRT->CreateBitmap(D2DIntSize(aSize), aData, aStride, props, byRef(mBitmap));

  if (FAILED(hr)) {
    gfxWarning() << "Failed to create D2D Bitmap for data. Code: " << hr;
    return false;
  }

  DrawTargetD2D::mVRAMUsageSS += GetByteSize();
  mDevice = Factory::GetDirect3D10Device();

  return true;
}

bool
SourceSurfaceD2D::InitFromTexture(ID3D10Texture2D *aTexture,
                                  SurfaceFormat aFormat,
                                  ID2D1RenderTarget *aRT)
{
  HRESULT hr;

  RefPtr<IDXGISurface> surf;

  hr = aTexture->QueryInterface((IDXGISurface**)&surf);

  if (FAILED(hr)) {
    gfxWarning() << "Failed to QI texture to surface. Code: " << hr;
    return false;
  }

  D3D10_TEXTURE2D_DESC desc;
  aTexture->GetDesc(&desc);

  mSize = IntSize(desc.Width, desc.Height);
  mFormat = aFormat;

  D2D1_BITMAP_PROPERTIES props = D2D1::BitmapProperties(D2DPixelFormat(aFormat));
  hr = aRT->CreateSharedBitmap(IID_IDXGISurface, surf, &props, byRef(mBitmap));

  if (FAILED(hr)) {
    gfxWarning() << "Failed to create SharedBitmap. Code: " << hr;
    return false;
  }

  aTexture->GetDevice(byRef(mDevice));
  DrawTargetD2D::mVRAMUsageSS += GetByteSize();

  return true;
}

uint32_t
SourceSurfaceD2D::GetByteSize() const
{
  return mSize.width * mSize.height * BytesPerPixel(mFormat);
}

DataSourceSurfaceD2D::DataSourceSurfaceD2D(SourceSurfaceD2D* aSourceSurface)
  : mTexture(nullptr)
  , mFormat(aSourceSurface->mFormat)
  , mSize(aSourceSurface->mSize)
  , mMapped(false)
{
  // We allocate ourselves a regular D3D surface (sourceTexture) and paint the
  // D2D bitmap into it via a DXGI render target. Then we need to copy
  // sourceTexture into a staging texture (mTexture), which we will lazily map
  // to get the data.

  CD3D10_TEXTURE2D_DESC desc(DXGIFormat(mFormat), mSize.width, mSize.height);
  desc.MipLevels = 1;
  desc.Usage = D3D10_USAGE_DEFAULT;
  desc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE;
  RefPtr<ID3D10Texture2D> sourceTexture;
  HRESULT hr = aSourceSurface->mDevice->CreateTexture2D(&desc, nullptr,
                                                        byRef(sourceTexture));
  if (FAILED(hr)) {
    gfxWarning() << "Failed to create texture. Code: " << hr;
    return;
  }

  RefPtr<IDXGISurface> dxgiSurface;
  hr = sourceTexture->QueryInterface((IDXGISurface**)byRef(dxgiSurface));
  if (FAILED(hr)) {
    gfxWarning() << "Failed to create DXGI surface. Code: " << hr;
    return;
  }

  D2D1_RENDER_TARGET_PROPERTIES rtProps = D2D1::RenderTargetProperties(
            D2D1_RENDER_TARGET_TYPE_DEFAULT,
            D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED));

  RefPtr<ID2D1RenderTarget> renderTarget;
  hr = DrawTargetD2D::factory()->CreateDxgiSurfaceRenderTarget(dxgiSurface,
                                                               &rtProps,
                                                               byRef(renderTarget));
  if (FAILED(hr)) {
    gfxWarning() << "Failed to create render target. Code: " << hr;
    return;
  }

  renderTarget->BeginDraw();
  renderTarget->Clear(D2D1::ColorF(0, 0.0f));
  renderTarget->DrawBitmap(aSourceSurface->mBitmap,
                           D2D1::RectF(0, 0,
                                       Float(mSize.width),
                                       Float(mSize.height)));
  hr = renderTarget->EndDraw();
  if (FAILED(hr)) {
    gfxWarning() << "Failed to draw bitmap. Code: " << hr;
    return;
  }

  desc.CPUAccessFlags = D3D10_CPU_ACCESS_READ | D3D10_CPU_ACCESS_WRITE;
  desc.Usage = D3D10_USAGE_STAGING;
  desc.BindFlags = 0;
  hr = aSourceSurface->mDevice->CreateTexture2D(&desc, nullptr, byRef(mTexture));
  if (FAILED(hr)) {
    gfxWarning() << "Failed to create staging texture. Code: " << hr;
    mTexture = nullptr;
    return;
  }

  aSourceSurface->mDevice->CopyResource(mTexture, sourceTexture);
}

DataSourceSurfaceD2D::~DataSourceSurfaceD2D()
{
  if (mMapped) {
    mTexture->Unmap(0);
  }
}

unsigned char*
DataSourceSurfaceD2D::GetData()
{
  EnsureMappedTexture();
  if (!mMapped) {
    return nullptr;
  }

  return reinterpret_cast<unsigned char*>(mData.pData);
}

int32_t
DataSourceSurfaceD2D::Stride()
{
  EnsureMappedTexture();
  if (!mMapped) {
    return 0;
  }

  return mData.RowPitch;
}

IntSize
DataSourceSurfaceD2D::GetSize() const
{
  return mSize;
}

SurfaceFormat
DataSourceSurfaceD2D::GetFormat() const
{
  return mFormat;
}

bool
DataSourceSurfaceD2D::Map(MapType aMapType, MappedSurface *aMappedSurface)
{
  // DataSourceSurfaces used with the new Map API should not be used with GetData!!
  MOZ_ASSERT(!mMapped);
  MOZ_ASSERT(!mIsMapped);

  if (!mTexture) {
    return false;
  }

  D3D10_MAP mapType;

  if (aMapType == MapType::READ) {
    mapType = D3D10_MAP_READ;
  } else if (aMapType == MapType::WRITE) {
    mapType = D3D10_MAP_WRITE;
  } else {
    mapType = D3D10_MAP_READ_WRITE;
  }

  D3D10_MAPPED_TEXTURE2D map;

  HRESULT hr = mTexture->Map(0, mapType, 0, &map);

  if (FAILED(hr)) {
    gfxWarning() << "Texture map failed with code: " << hr;
    return false;
  }

  aMappedSurface->mData = (uint8_t*)map.pData;
  aMappedSurface->mStride = map.RowPitch;
  mIsMapped = true;

  return true;
}

void
DataSourceSurfaceD2D::Unmap()
{
  MOZ_ASSERT(mIsMapped);

  mIsMapped = false;
  mTexture->Unmap(0);
}

void
DataSourceSurfaceD2D::EnsureMappedTexture()
{
  // Do not use GetData() after having used Map!
  MOZ_ASSERT(!mIsMapped);

  if (mMapped ||
      !mTexture) {
    return;
  }

  HRESULT hr = mTexture->Map(0, D3D10_MAP_READ, 0, &mData);
  if (FAILED(hr)) {
    gfxWarning() << "Failed to map texture. Code: " << hr;
    mTexture = nullptr;
  } else {
    mMapped = true;
  }
}

}
}
