/* 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 "VideoProcessorD3D11.h"

#include <d3d11.h>
#include <d3d11_1.h>

#include "gfxPlatform.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/Types.h"
#include "mozilla/layers/TextureD3D11.h"
#include "mozilla/Maybe.h"

namespace mozilla {
namespace layers {

// TODO: De-duplicate this, it also exists in DCLayerTree.cpp.
static Maybe<DXGI_COLOR_SPACE_TYPE> GetSourceDXGIColorSpace(
    const gfx::YUVColorSpace aYUVColorSpace, const gfx::ColorRange aColorRange,
    const gfx::TransferFunction aTransferFunction) {
  switch (aYUVColorSpace) {
    case gfx::YUVColorSpace::BT601:
      // https://en.wikipedia.org/wiki/Rec._601 - this is the NTSC and SECAM/PAL
      // color spaces
      if (aTransferFunction != gfx::TransferFunction::BT709) {
        gfxCriticalNoteOnce
            << "GetSourceDXGIColorSpace: Unhandled transfer function "
            << static_cast<int>(aTransferFunction)
            << " for BT601, treating as BT709 transfer function";
      }
      switch (aColorRange) {
        case gfx::ColorRange::FULL:
          return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601);
        case gfx::ColorRange::LIMITED:
          return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601);
      }
      gfxCriticalNoteOnce << "GetSourceDXGIColorSpace: Unhandled color range "
                          << static_cast<int>(aColorRange) << " for BT601";
      return Nothing();
    case gfx::YUVColorSpace::Identity:
      gfxCriticalNoteOnce
          << "GetSourceDXGIColorSpace: Unhandled YUV color space "
          << static_cast<int>(aYUVColorSpace)
          << ", treating as BT709 color space";
      FMT_FALLTHROUGH;
    case gfx::YUVColorSpace::BT709:
      // https://en.wikipedia.org/wiki/Rec._709 - this is the HDTV color space
      if (aTransferFunction != gfx::TransferFunction::BT709) {
        gfxCriticalNoteOnce
            << "GetSourceDXGIColorSpace: Unhandled transfer function "
            << static_cast<int>(aTransferFunction)
            << " for BT709, treating as BT709 transfer function";
      }
      switch (aColorRange) {
        case gfx::ColorRange::FULL:
          return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709);
        case gfx::ColorRange::LIMITED:
          return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709);
      }
      gfxCriticalNoteOnce << "GetSourceDXGIColorSpace: Unhandled color range "
                          << static_cast<int>(aColorRange) << " for BT709";
      return Nothing();
    case gfx::YUVColorSpace::BT2020:
      // https://en.wikipedia.org/wiki/Rec._2020 - this is the UHDTV color space
      if (!gfxPlatform::UseHDR()) {
        // This pref being off mimics legacy behavior, it's wrong but it's
        // precisely what we did before, looks washed out if it's PQ.
        switch (aColorRange) {
          case gfx::ColorRange::FULL:
            return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020);
          case gfx::ColorRange::LIMITED:
            return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020);
        }
        gfxCriticalNoteOnce << "GetSourceDXGIColorSpace: Unhandled color range "
                            << static_cast<int>(aColorRange) << " for BT2020";
        return Nothing();
      }
      switch (aTransferFunction) {
        case gfx::TransferFunction::SRGB:
        case gfx::TransferFunction::LINEAR:
          // Almost certainly never used, but cover all switch cases to support
          // the compiler warning if any are added later.
          gfxCriticalNoteOnce
              << "GetSourceDXGIColorSpace: DXGI has no support for "
              << static_cast<int>(aTransferFunction)
              << " transfer function for YCBCR content, treating as BT2020 "
                 "transfer function";
          FMT_FALLTHROUGH;
        case gfx::TransferFunction::BT709:
          // BT2020 defines a transfer function that is almost identical to
          // BT709 + BT1886, so this refers to BT2020 transfer function.
          // https://en.wikipedia.org/wiki/Rec._2020
          switch (aColorRange) {
            case gfx::ColorRange::FULL:
              return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020);
            case gfx::ColorRange::LIMITED:
              return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020);
          }
          gfxCriticalNoteOnce
              << "GetSourceDXGIColorSpace: Unhandled color range "
              << static_cast<int>(aColorRange) << " for BT2020";
          return Nothing();
        case gfx::TransferFunction::PQ:
          // This is an HDR video transfer function, needs 10bit (HDR10) to
          // avoid being lower quality than BT709 over the SDR range.
          // https://en.wikipedia.org/wiki/Perceptual_quantizer
          switch (aColorRange) {
            case gfx::ColorRange::FULL:
              gfxCriticalNoteOnce
                  << "GetSourceDXGIColorSpace: DXGI has no support for PQ "
                     "transfer function with full color range for BT2020 "
                     "content, treating as studio range";
              return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020);
            case gfx::ColorRange::LIMITED:
              return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020);
          }
          gfxCriticalNoteOnce
              << "GetSourceDXGIColorSpace: Unhandled color range "
              << static_cast<int>(aColorRange) << " for BT2020";
          return Nothing();
        case gfx::TransferFunction::HLG:
          // This is an HDR video transfer function, does not strictly require
          // 10bit but certainly benefits from it.
          // https://en.wikipedia.org/wiki/Hybrid_log%E2%80%93gamma
          switch (aColorRange) {
            case gfx::ColorRange::FULL:
              return Some(DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020);
            case gfx::ColorRange::LIMITED:
              return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020);
          }
          gfxCriticalNoteOnce
              << "GetSourceDXGIColorSpace: Unhandled color range "
              << static_cast<int>(aColorRange) << " for BT2020";
          return Nothing();
      }
      gfxCriticalNoteOnce
          << "GetSourceDXGIColorSpace: Unhandled transfer function "
          << static_cast<int>(aTransferFunction) << " for BT2020";
      return Nothing();
  }

  return Nothing();
}

static Maybe<DXGI_COLOR_SPACE_TYPE> GetSourceDXGIColorSpace(
    const gfx::YUVRangedColorSpace aYUVColorSpace) {
  const auto info = FromYUVRangedColorSpace(aYUVColorSpace);
  return GetSourceDXGIColorSpace(info.space, info.range, info.transferFunction);
}

/* static */
RefPtr<VideoProcessorD3D11> VideoProcessorD3D11::Create(ID3D11Device* aDevice) {
  MOZ_ASSERT(aDevice);

  if (!aDevice) {
    return nullptr;
  }

  RefPtr<ID3D11DeviceContext> context;
  aDevice->GetImmediateContext(getter_AddRefs(context));
  if (!context) {
    return nullptr;
  }

  HRESULT hr;
  RefPtr<ID3D11VideoDevice> videoDevice;
  hr =
      aDevice->QueryInterface((ID3D11VideoDevice**)getter_AddRefs(videoDevice));
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "Failed to get D3D11VideoDevice: " << gfx::hexa(hr);
    return nullptr;
  }

  RefPtr<ID3D11VideoContext> videoContext;
  hr = context->QueryInterface(
      (ID3D11VideoContext**)getter_AddRefs(videoContext));
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "Failed to get D3D11VideoContext: " << gfx::hexa(hr);
    return nullptr;
  }

  RefPtr<ID3D11VideoContext1> videoContext1;
  hr = videoContext->QueryInterface(
      (ID3D11VideoContext1**)getter_AddRefs(videoContext1));
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "Failed to get D3D11VideoContext1: "
                        << gfx::hexa(hr);
    return nullptr;
  }

  RefPtr<VideoProcessorD3D11> videoProcessor = new VideoProcessorD3D11(
      aDevice, context, videoDevice, videoContext, videoContext1);

  return videoProcessor;
}

VideoProcessorD3D11::VideoProcessorD3D11(ID3D11Device* aDevice,
                                         ID3D11DeviceContext* aDeviceContext,
                                         ID3D11VideoDevice* aVideoDevice,
                                         ID3D11VideoContext* aVideoContext,
                                         ID3D11VideoContext1* aVideoContext1)
    : mDevice(aDevice),
      mDeviceContext(aDeviceContext),
      mVideoDevice(aVideoDevice),
      mVideoContext(aVideoContext),
      mVideoContext1(aVideoContext1) {}

VideoProcessorD3D11::~VideoProcessorD3D11() {}

HRESULT VideoProcessorD3D11::Init(const gfx::IntSize& aSize) {
  if (mSize == aSize) {
    return S_OK;
  }

  mVideoProcessorEnumerator = nullptr;
  mVideoProcessor = nullptr;
  mSize = gfx::IntSize();

  D3D11_VIDEO_PROCESSOR_CONTENT_DESC desc;
  desc.InputFrameFormat = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE;
  desc.InputFrameRate.Numerator = 60;
  desc.InputFrameRate.Denominator = 1;
  desc.InputWidth = aSize.width;
  desc.InputHeight = aSize.height;
  desc.OutputFrameRate.Numerator = 60;
  desc.OutputFrameRate.Denominator = 1;
  desc.OutputWidth = aSize.width;
  desc.OutputHeight = aSize.height;
  desc.Usage = D3D11_VIDEO_USAGE_PLAYBACK_NORMAL;

  HRESULT hr = mVideoDevice->CreateVideoProcessorEnumerator(
      &desc, getter_AddRefs(mVideoProcessorEnumerator));
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "Failed to create VideoProcessorEnumerator: "
                        << gfx::hexa(hr);
    return hr;
  }

  hr = mVideoDevice->CreateVideoProcessor(mVideoProcessorEnumerator, 0,
                                          getter_AddRefs(mVideoProcessor));
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "Failed to create VideoProcessor: " << gfx::hexa(hr);
    return hr;
  }

  // Turn off auto stream processing (the default) that will hurt power
  // consumption.
  mVideoContext->VideoProcessorSetStreamAutoProcessingMode(mVideoProcessor, 0,
                                                           FALSE);

  mSize = aSize;

  return S_OK;
}

bool VideoProcessorD3D11::CallVideoProcessorBlt(
    InputTextureInfo& aTextureInfo, ID3D11Texture2D* aOutputTexture) {
  MOZ_ASSERT(mVideoProcessorEnumerator);
  MOZ_ASSERT(mVideoProcessor);
  MOZ_ASSERT(aTextureInfo.mTexture);
  MOZ_ASSERT(aOutputTexture);

  HRESULT hr;

  auto yuvRangedColorSpace = gfx::ToYUVRangedColorSpace(
      gfx::ToYUVColorSpace(aTextureInfo.mColorSpace), aTextureInfo.mColorRange,
      aTextureInfo.mTransferFunction);
  auto sourceColorSpace = GetSourceDXGIColorSpace(yuvRangedColorSpace);
  if (sourceColorSpace.isNothing()) {
    gfxCriticalNoteOnce << "Unsupported color space";
    return false;
  }

  DXGI_COLOR_SPACE_TYPE inputColorSpace = sourceColorSpace.ref();
  mVideoContext1->VideoProcessorSetStreamColorSpace1(mVideoProcessor, 0,
                                                     inputColorSpace);

  const DXGI_COLOR_SPACE_TYPE outputColorSpace =
      DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;

  mVideoContext1->VideoProcessorSetOutputColorSpace1(mVideoProcessor,
                                                     outputColorSpace);

  D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC inputDesc = {};
  inputDesc.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D;
  inputDesc.Texture2D.ArraySlice = aTextureInfo.mIndex;

  RefPtr<ID3D11VideoProcessorInputView> inputView;
  hr = mVideoDevice->CreateVideoProcessorInputView(
      aTextureInfo.mTexture, mVideoProcessorEnumerator, &inputDesc,
      getter_AddRefs(inputView));
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "ID3D11VideoProcessorInputView creation failed: "
                        << gfx::hexa(hr);
    return false;
  }

  D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC outputDesc = {};
  outputDesc.ViewDimension = D3D11_VPOV_DIMENSION_TEXTURE2D;
  outputDesc.Texture2D.MipSlice = 0;

  RefPtr<ID3D11VideoProcessorOutputView> outputView;
  hr = mVideoDevice->CreateVideoProcessorOutputView(
      aOutputTexture, mVideoProcessorEnumerator, &outputDesc,
      getter_AddRefs(outputView));
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "ID3D11VideoProcessorOutputView creation failed: "
                        << gfx::hexa(hr);
    return false;
  }

  D3D11_VIDEO_PROCESSOR_STREAM stream = {};
  stream.Enable = true;
  stream.pInputSurface = inputView.get();

  hr = mVideoContext->VideoProcessorBlt(mVideoProcessor, outputView, 0, 1,
                                        &stream);
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "VideoProcessorBlt failed: " << gfx::hexa(hr);
    return false;
  }

  return true;
}

}  // namespace layers
}  // namespace mozilla
