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

// -

#include <d3d11.h>
#include <dcomp.h>
#include <d3d11_1.h>
#include <d3d11_4.h>
#include <dxgi1_2.h>
#include <d3dcompiler.h>

// -

#include "gfxWindowsPlatform.h"
#include "GLContext.h"
#include "GLContextEGL.h"
#include "mozilla/gfx/DeviceManagerDx.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/GPUParent.h"
#include "mozilla/gfx/Matrix.h"
#include "mozilla/gfx/StackArray.h"
#include "mozilla/layers/CompositeProcessD3D11FencesHolderMap.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/webrender/RenderD3D11TextureHost.h"
#include "mozilla/webrender/RenderDcompSurfaceTextureHost.h"
#include "mozilla/webrender/RenderTextureHost.h"
#include "mozilla/webrender/RenderThread.h"
#include "mozilla/WindowsVersion.h"
#include "mozilla/glean/GfxMetrics.h"
#include "nsPrintfCString.h"
#include "WinUtils.h"

// -

namespace mozilla {
namespace wr {

extern LazyLogModule gRenderThreadLog;
#define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__))

#define LOG_H(msg, ...)                   \
  MOZ_LOG(gDcompSurface, LogLevel::Debug, \
          ("DCSurfaceHandle=%p, " msg, this, ##__VA_ARGS__))

static UINT GetVendorId(ID3D11VideoDevice* const aVideoDevice) {
  RefPtr<IDXGIDevice> dxgiDevice;
  RefPtr<IDXGIAdapter> adapter;
  aVideoDevice->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice));
  dxgiDevice->GetAdapter(getter_AddRefs(adapter));

  DXGI_ADAPTER_DESC adapterDesc;
  adapter->GetDesc(&adapterDesc);

  return adapterDesc.VendorId;
}

// Undocumented NVIDIA VSR data
struct NvidiaVSRGetData_v1 {
  UINT vsrGPUisVSRCapable : 1;   // 01/32, 1: GPU is VSR capable
  UINT vsrOtherFieldsValid : 1;  // 02/32, 1: Other status fields are valid
  // remaining fields are valid if vsrOtherFieldsValid is set - requires
  // previous execution of VPBlt with SetStreamExtension for VSR enabled.
  UINT vsrEnabled : 1;           // 03/32, 1: VSR is enabled
  UINT vsrIsInUseForThisVP : 1;  // 04/32, 1: VSR is in use by this Video
                                 // Processor
  UINT vsrLevel : 3;             // 05-07/32, 0-4 current level
  UINT vsrReserved : 21;         // 32-07
};

static Result<NvidiaVSRGetData_v1, HRESULT> GetNvidiaVpSuperResolutionInfo(
    ID3D11VideoContext* aVideoContext, ID3D11VideoProcessor* aVideoProcessor) {
  MOZ_ASSERT(aVideoContext);
  MOZ_ASSERT(aVideoProcessor);

  // Undocumented NVIDIA driver constants
  constexpr GUID nvGUID = {0xD43CE1B3,
                           0x1F4B,
                           0x48AC,
                           {0xBA, 0xEE, 0xC3, 0xC2, 0x53, 0x75, 0xE6, 0xF7}};

  NvidiaVSRGetData_v1 data{};
  HRESULT hr = aVideoContext->VideoProcessorGetStreamExtension(
      aVideoProcessor, 0, &nvGUID, sizeof(data), &data);

  if (FAILED(hr)) {
    return Err(hr);
  }
  return data;
}

static void AddProfileMarkerForNvidiaVpSuperResolutionInfo(
    ID3D11VideoContext* aVideoContext, ID3D11VideoProcessor* aVideoProcessor) {
  MOZ_ASSERT(profiler_thread_is_being_profiled_for_markers());

  auto res = GetNvidiaVpSuperResolutionInfo(aVideoContext, aVideoProcessor);
  if (res.isErr()) {
    return;
  }

  auto data = res.unwrap();

  nsPrintfCString str(
      "SuperResolution VP Capable %u OtherFieldsValid %u Enabled %u InUse %u "
      "Level %u",
      data.vsrGPUisVSRCapable, data.vsrOtherFieldsValid, data.vsrEnabled,
      data.vsrIsInUseForThisVP, data.vsrLevel);
  PROFILER_MARKER_TEXT("DCSurfaceVideo", GRAPHICS, {}, str);
}

static HRESULT SetNvidiaVpSuperResolution(ID3D11VideoContext* aVideoContext,
                                          ID3D11VideoProcessor* aVideoProcessor,
                                          bool aEnable) {
  LOG("SetNvidiaVpSuperResolution() aEnable=%d", aEnable);

  // Undocumented NVIDIA driver constants
  constexpr GUID nvGUID = {0xD43CE1B3,
                           0x1F4B,
                           0x48AC,
                           {0xBA, 0xEE, 0xC3, 0xC2, 0x53, 0x75, 0xE6, 0xF7}};

  constexpr UINT nvExtensionVersion = 0x1;
  constexpr UINT nvExtensionMethodSuperResolution = 0x2;
  struct {
    UINT version;
    UINT method;
    UINT enable;
  } streamExtensionInfo = {nvExtensionVersion, nvExtensionMethodSuperResolution,
                           aEnable ? 1u : 0};

  HRESULT hr;
  hr = aVideoContext->VideoProcessorSetStreamExtension(
      aVideoProcessor, 0, &nvGUID, sizeof(streamExtensionInfo),
      &streamExtensionInfo);
  return hr;
}

static HRESULT SetVpSuperResolution(UINT aGpuVendorId,
                                    ID3D11VideoContext* aVideoContext,
                                    ID3D11VideoProcessor* aVideoProcessor,
                                    bool aEnable) {
  MOZ_ASSERT(aVideoContext);
  MOZ_ASSERT(aVideoProcessor);

  if (aGpuVendorId == 0x10DE) {
    return SetNvidiaVpSuperResolution(aVideoContext, aVideoProcessor, aEnable);
  }
  return E_NOTIMPL;
}

static bool GetNvidiaRTXVideoTrueHDRSupported(
    ID3D11VideoContext* aVideoContext, ID3D11VideoProcessor* aVideoProcessor) {
  const GUID kNvidiaTrueHDRInterfaceGUID = {
      0xfdd62bb4,
      0x620b,
      0x4fd7,
      {0x9a, 0xb3, 0x1e, 0x59, 0xd0, 0xd5, 0x44, 0xb3}};
  UINT available = 0;
  HRESULT hr = aVideoContext->VideoProcessorGetStreamExtension(
      aVideoProcessor, 0, &kNvidiaTrueHDRInterfaceGUID, sizeof(available),
      &available);
  if (FAILED(hr)) {
    return false;
  }

  bool driverSupportsTrueHdr = (available == 1);
  return driverSupportsTrueHdr;
}

static HRESULT SetNvidiaRTXVideoTrueHDR(ID3D11VideoContext* aVideoContext,
                                        ID3D11VideoProcessor* aVideoProcessor,
                                        bool aEnable) {
  constexpr GUID kNvidiaTrueHDRInterfaceGUID = {
      0xfdd62bb4,
      0x620b,
      0x4fd7,
      {0x9a, 0xb3, 0x1e, 0x59, 0xd0, 0xd5, 0x44, 0xb3}};
  constexpr UINT kStreamExtensionMethodTrueHDR = 0x3;
  const UINT TrueHDRVersion4 = 4;
  struct {
    UINT version;
    UINT method;
    UINT enable : 1;
    UINT reserved : 31;
  } streamExtensionInfo = {TrueHDRVersion4, kStreamExtensionMethodTrueHDR,
                           aEnable ? 1u : 0u, 0u};
  HRESULT hr = aVideoContext->VideoProcessorSetStreamExtension(
      aVideoProcessor, 0, &kNvidiaTrueHDRInterfaceGUID,
      sizeof(streamExtensionInfo), &streamExtensionInfo);
  return hr;
}

static bool GetVpAutoHDRSupported(UINT aGpuVendorId,
                                  ID3D11VideoContext* aVideoContext,
                                  ID3D11VideoProcessor* aVideoProcessor) {
  MOZ_ASSERT(aVideoContext);
  MOZ_ASSERT(aVideoProcessor);

  if (aGpuVendorId == 0x10DE) {
    return GetNvidiaRTXVideoTrueHDRSupported(aVideoContext, aVideoProcessor);
  }
  return false;
}

static HRESULT SetVpAutoHDR(UINT aGpuVendorId,
                            ID3D11VideoContext* aVideoContext,
                            ID3D11VideoProcessor* aVideoProcessor,
                            bool aEnable) {
  MOZ_ASSERT(aVideoContext);
  MOZ_ASSERT(aVideoProcessor);

  if (aGpuVendorId == 0x10DE) {
    return SetNvidiaRTXVideoTrueHDR(aVideoContext, aVideoProcessor, aEnable);
  }
  MOZ_ASSERT_UNREACHABLE("Unexpected to be called");
  return E_NOTIMPL;
}

StaticAutoPtr<GpuOverlayInfo> DCLayerTree::sGpuOverlayInfo;

/* static */
UniquePtr<DCLayerTree> DCLayerTree::Create(gl::GLContext* aGL,
                                           EGLConfig aEGLConfig,
                                           ID3D11Device* aDevice,
                                           ID3D11DeviceContext* aCtx,
                                           HWND aHwnd, nsACString& aError) {
  RefPtr<IDCompositionDevice2> dCompDevice =
      gfx::DeviceManagerDx::Get()->GetDirectCompositionDevice();
  if (!dCompDevice) {
    aError.Assign("DCLayerTree(no device)"_ns);
    return nullptr;
  }

  auto layerTree = MakeUnique<DCLayerTree>(aGL, aEGLConfig, aDevice, aCtx,
                                           aHwnd, dCompDevice);
  if (!layerTree->Initialize(aHwnd, aError)) {
    return nullptr;
  }

  return layerTree;
}

void DCLayerTree::Shutdown() { DCLayerTree::sGpuOverlayInfo = nullptr; }

DCLayerTree::DCLayerTree(gl::GLContext* aGL, EGLConfig aEGLConfig,
                         ID3D11Device* aDevice, ID3D11DeviceContext* aCtx,
                         HWND aHwnd, IDCompositionDevice2* aCompositionDevice)
    : mGL(aGL),
      mEGLConfig(aEGLConfig),
      mDevice(aDevice),
      mCtx(aCtx),
      mHwnd(aHwnd),
      mCompositionDevice(aCompositionDevice),
      mDebugCounter(false),
      mDebugVisualRedrawRegions(false),
      mEGLImage(EGL_NO_IMAGE),
      mColorRBO(0),
      mPendingCommit(false) {
  LOG("DCLayerTree::DCLayerTree()");

  if (gfx::gfxVars::UseWebRenderCompositor()) {
    MOZ_ASSERT(StaticPrefs::gfx_webrender_layer_compositor());
    mCompositorKind = Some(WebRenderOsCompositorKind::LayerCompositor);
  }
}

DCLayerTree::~DCLayerTree() {
  LOG("DCLayerTree::~DCLayerTree()");

  ReleaseNativeCompositorResources();
}

void DCLayerTree::ReleaseNativeCompositorResources() {
  const auto gl = GetGLContext();

  DestroyEGLSurface();

  // Delete any cached FBO objects
  for (auto it = mFrameBuffers.begin(); it != mFrameBuffers.end(); ++it) {
    gl->fDeleteRenderbuffers(1, &it->depthRboId);
    gl->fDeleteFramebuffers(1, &it->fboId);
  }
}

bool DCLayerTree::Initialize(HWND aHwnd, nsACString& aError) {
  HRESULT hr;

  RefPtr<IDCompositionDesktopDevice> desktopDevice;
  hr = mCompositionDevice->QueryInterface(
      (IDCompositionDesktopDevice**)getter_AddRefs(desktopDevice));
  if (FAILED(hr)) {
    aError.Assign(nsPrintfCString(
        "DCLayerTree(get IDCompositionDesktopDevice failed %lx)", hr));
    return false;
  }

  hr = desktopDevice->CreateTargetForHwnd(aHwnd, TRUE,
                                          getter_AddRefs(mCompositionTarget));
  if (FAILED(hr)) {
    aError.Assign(nsPrintfCString(
        "DCLayerTree(create DCompositionTarget failed %lx)", hr));
    return false;
  }

  hr = mCompositionDevice->CreateVisual(getter_AddRefs(mRootVisual));
  if (FAILED(hr)) {
    aError.Assign(nsPrintfCString(
        "DCLayerTree(create root DCompositionVisual failed %lx)", hr));
    return false;
  }

  hr =
      mCompositionDevice->CreateVisual(getter_AddRefs(mDefaultSwapChainVisual));
  if (FAILED(hr)) {
    aError.Assign(nsPrintfCString(
        "DCLayerTree(create swap chain DCompositionVisual failed %lx)", hr));
    return false;
  }

  if (gfx::gfxVars::UseWebRenderDCompVideoHwOverlayWin() ||
      gfx::gfxVars::UseWebRenderDCompVideoSwOverlayWin()) {
    if (!InitializeVideoOverlaySupport()) {
      RenderThread::Get()->HandleWebRenderError(WebRenderError::VIDEO_OVERLAY);
    }
  }
  if (!sGpuOverlayInfo) {
    // Set default if sGpuOverlayInfo was not set.
    sGpuOverlayInfo = new GpuOverlayInfo();
  }

  // Initialize SwapChainInfo
  SupportsSwapChainTearing();

  mCompositionTarget->SetRoot(mRootVisual);
  // Set interporation mode to nearest, to ensure 1:1 sampling.
  // By default, a visual inherits the interpolation mode of the parent visual.
  // If no visuals set the interpolation mode, the default for the entire visual
  // tree is nearest neighbor interpolation.
  mRootVisual->SetBitmapInterpolationMode(
      DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
  return true;
}

bool FlagsSupportsOverlays(UINT flags) {
  return (flags & (DXGI_OVERLAY_SUPPORT_FLAG_DIRECT |
                   DXGI_OVERLAY_SUPPORT_FLAG_SCALING));
}

// A warpper of IDXGIOutput4::CheckOverlayColorSpaceSupport()
bool CheckOverlayColorSpaceSupport(DXGI_FORMAT aDxgiFormat,
                                   DXGI_COLOR_SPACE_TYPE aDxgiColorSpace,
                                   RefPtr<IDXGIOutput> aOutput,
                                   RefPtr<ID3D11Device> aD3d11Device) {
  UINT colorSpaceSupportFlags = 0;
  RefPtr<IDXGIOutput4> output4;

  if (FAILED(aOutput->QueryInterface(__uuidof(IDXGIOutput4),
                                     getter_AddRefs(output4)))) {
    return false;
  }

  if (FAILED(output4->CheckOverlayColorSpaceSupport(
          aDxgiFormat, aDxgiColorSpace, aD3d11Device,
          &colorSpaceSupportFlags))) {
    return false;
  }

  return (colorSpaceSupportFlags &
          DXGI_OVERLAY_COLOR_SPACE_SUPPORT_FLAG_PRESENT);
}

bool DCLayerTree::InitializeVideoOverlaySupport() {
  MOZ_ASSERT(IsWin10AnniversaryUpdateOrLater());

  HRESULT hr;

  hr = mDevice->QueryInterface(
      (ID3D11VideoDevice**)getter_AddRefs(mVideoDevice));
  if (FAILED(hr)) {
    gfxCriticalNote << "Failed to get D3D11VideoDevice: " << gfx::hexa(hr);
    return false;
  }

  hr =
      mCtx->QueryInterface((ID3D11VideoContext**)getter_AddRefs(mVideoContext));
  if (FAILED(hr)) {
    gfxCriticalNote << "Failed to get D3D11VideoContext: " << gfx::hexa(hr);
    return false;
  }

  if (sGpuOverlayInfo) {
    return true;
  }

  UniquePtr<GpuOverlayInfo> info = MakeUnique<GpuOverlayInfo>();

  RefPtr<IDXGIDevice> dxgiDevice;
  RefPtr<IDXGIAdapter> adapter;
  mDevice->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice));
  dxgiDevice->GetAdapter(getter_AddRefs(adapter));

  unsigned int i = 0;
  while (true) {
    RefPtr<IDXGIOutput> output;
    if (FAILED(adapter->EnumOutputs(i++, getter_AddRefs(output)))) {
      break;
    }
    RefPtr<IDXGIOutput3> output3;
    if (FAILED(output->QueryInterface(__uuidof(IDXGIOutput3),
                                      getter_AddRefs(output3)))) {
      break;
    }

    output3->CheckOverlaySupport(DXGI_FORMAT_NV12, mDevice,
                                 &info->mNv12OverlaySupportFlags);
    output3->CheckOverlaySupport(DXGI_FORMAT_YUY2, mDevice,
                                 &info->mYuy2OverlaySupportFlags);
    output3->CheckOverlaySupport(DXGI_FORMAT_B8G8R8A8_UNORM, mDevice,
                                 &info->mBgra8OverlaySupportFlags);
    output3->CheckOverlaySupport(DXGI_FORMAT_R10G10B10A2_UNORM, mDevice,
                                 &info->mRgb10a2OverlaySupportFlags);
    output3->CheckOverlaySupport(DXGI_FORMAT_R16G16B16A16_FLOAT, mDevice,
                                 &info->mRgba16fOverlaySupportFlags);

    if (FlagsSupportsOverlays(info->mRgb10a2OverlaySupportFlags)) {
      info->mSupportsHDR = true;
      info->mSupportsHardwareOverlayRGB10A2 = true;
    }

    if (FlagsSupportsOverlays(info->mRgba16fOverlaySupportFlags)) {
      info->mSupportsHDR = true;
      info->mSupportsHardwareOverlayRGBA16F = true;
    }

    if (!info->mSupportsHardwareOverlays &&
        FlagsSupportsOverlays(info->mNv12OverlaySupportFlags)) {
      // NV12 format is preferred if it's supported.
      info->mOverlayFormatUsed = DXGI_FORMAT_NV12;
      info->mSupportsHardwareOverlays = true;
    }

    if (!info->mSupportsHardwareOverlays &&
        FlagsSupportsOverlays(info->mYuy2OverlaySupportFlags)) {
      // If NV12 isn't supported, fallback to YUY2 if it's supported.
      info->mOverlayFormatUsed = DXGI_FORMAT_YUY2;
      info->mSupportsHardwareOverlays = true;
    }

    // Early out after the first output that reports overlay support. All
    // outputs are expected to report the same overlay support according to
    // Microsoft's WDDM documentation:
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/display/multiplane-overlay-hardware-requirements
    if (info->mSupportsHardwareOverlays) {
      break;
    }
  }

  if (!StaticPrefs::gfx_webrender_dcomp_video_yuv_overlay_win_AtStartup()) {
    info->mOverlayFormatUsed = DXGI_FORMAT_B8G8R8A8_UNORM;
    info->mSupportsHardwareOverlays = false;
  }

  info->mSupportsOverlays = info->mSupportsHardwareOverlays;

  // Check VpSuperResolution and VpAutoHDR support.
  const auto size = gfx::IntSize(100, 100);
  if (EnsureVideoProcessor(size, size)) {
    const UINT vendorId = GetVendorId(mVideoDevice);
    if (vendorId == 0x10DE) {
      auto res = GetNvidiaVpSuperResolutionInfo(mVideoContext, mVideoProcessor);
      if (res.isOk() && res.unwrap().vsrGPUisVSRCapable) {
        info->mSupportsVpSuperResolution = true;
      }
    }

    const bool driverSupportVpAutoHDR =
        GetVpAutoHDRSupported(vendorId, mVideoContext, mVideoProcessor);
    if (driverSupportVpAutoHDR) {
      info->mSupportsVpAutoHDR = true;
    }
  }

  // Note: "UniquePtr::release" here is saying "release your ownership stake
  // on your pointer, so that our StaticAutoPtr can take over ownership".
  // (StaticAutoPtr doesn't have a move constructor that could directly steal
  // the contents of a UniquePtr via std::move().)
  sGpuOverlayInfo = info.release();

  if (auto* gpuParent = gfx::GPUParent::GetSingleton()) {
    gpuParent->NotifyOverlayInfo(GetOverlayInfo());
  }

  return true;
}

DCSurface* DCLayerTree::GetSurface(wr::NativeSurfaceId aId) const {
  auto surface_it = mDCSurfaces.find(aId);
  MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
  return surface_it->second.get();
}

void DCLayerTree::SetDefaultSwapChain(IDXGISwapChain1* aSwapChain) {
  LOG("DCLayerTree::SetDefaultSwapChain()");

  mRootVisual->AddVisual(mDefaultSwapChainVisual, TRUE, nullptr);
  mDefaultSwapChainVisual->SetContent(aSwapChain);
  // Default SwapChain's visual does not need linear interporation.
  mDefaultSwapChainVisual->SetBitmapInterpolationMode(
      DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
  mPendingCommit = true;
}

void DCLayerTree::MaybeUpdateDebug() {
  bool updated = false;
  updated |= MaybeUpdateDebugCounter();
  updated |= MaybeUpdateDebugVisualRedrawRegions();
  if (updated) {
    mPendingCommit = true;
  }
}

void DCLayerTree::MaybeCommit() {
  if (!mPendingCommit) {
    return;
  }
  mCompositionDevice->Commit();
  mPendingCommit = false;
}

void DCLayerTree::WaitForCommitCompletion() {
  // To ensure that swapchain layers have presented to the screen
  // for capture, call present twice. This is less than ideal, but
  // I'm not sure if there is a better way to ensure this syncs
  // correctly that works on both Win10/11. Even though this can
  // be slower than necessary, it's only used by the reftest
  // screenshotting code, so isn't particularly perf sensitive.
  bool needsWait = false;
  for (auto it = mDCSurfaces.begin(); it != mDCSurfaces.end(); it++) {
    auto* surface = it->second->AsDCSwapChain();
    if (surface) {
      needsWait = true;
    }
  }

  if (needsWait) {
    RefPtr<IDXGIDevice2> dxgiDevice2;
    mDevice->QueryInterface((IDXGIDevice2**)getter_AddRefs(dxgiDevice2));
    MOZ_ASSERT(dxgiDevice2);

    HANDLE event = ::CreateEvent(nullptr, false, false, nullptr);
    HRESULT hr = dxgiDevice2->EnqueueSetEvent(event);
    if (SUCCEEDED(hr)) {
      DebugOnly<DWORD> result = ::WaitForSingleObject(event, INFINITE);
      MOZ_ASSERT(result == WAIT_OBJECT_0);
    } else {
      gfxCriticalNoteOnce << "EnqueueSetEvent failed: " << gfx::hexa(hr);
    }
    ::CloseHandle(event);
  }

  mCompositionDevice->WaitForCommitCompletion();
}

bool DCLayerTree::UseCompositor() const { return mCompositorKind.isSome(); }

bool DCLayerTree::UseLayerCompositor() const {
  return mCompositorKind.isSome() &&
         mCompositorKind.ref() == WebRenderOsCompositorKind::LayerCompositor;
}

void DCLayerTree::DisableNativeCompositor() {
  MOZ_ASSERT(mCurrentLayers.empty());

  mCompositorKind = Nothing();
  ReleaseNativeCompositorResources();
  mPrevLayers.clear();
  mRootVisual->RemoveAllVisuals();
}

bool DCLayerTree::EnableAsyncScreenshot() {
  MOZ_ASSERT(UseLayerCompositor());
  if (!UseLayerCompositor()) {
    MOZ_ASSERT_UNREACHABLE("unexpected to be called");
    return false;
  }

  mAsyncScreenshotLastFrameUsed = mCurrentFrame;

  if (!mEnableAsyncScreenshot) {
    mEnableAsyncScreenshotInNextFrame = true;
    return false;
  }

  return true;
}

bool DCLayerTree::MaybeUpdateDebugCounter() {
  bool debugCounter = StaticPrefs::gfx_webrender_debug_dcomp_counter();
  if (mDebugCounter == debugCounter) {
    return false;
  }

  RefPtr<IDCompositionDeviceDebug> debugDevice;
  HRESULT hr = mCompositionDevice->QueryInterface(
      (IDCompositionDeviceDebug**)getter_AddRefs(debugDevice));
  if (FAILED(hr)) {
    return false;
  }

  if (debugCounter) {
    debugDevice->EnableDebugCounters();
  } else {
    debugDevice->DisableDebugCounters();
  }

  mDebugCounter = debugCounter;
  return true;
}

bool DCLayerTree::MaybeUpdateDebugVisualRedrawRegions() {
  bool debugVisualRedrawRegions =
      StaticPrefs::gfx_webrender_debug_dcomp_redraw_regions();
  if (mDebugVisualRedrawRegions == debugVisualRedrawRegions) {
    return false;
  }

  RefPtr<IDCompositionVisualDebug> visualDebug;
  HRESULT hr = mRootVisual->QueryInterface(
      (IDCompositionVisualDebug**)getter_AddRefs(visualDebug));
  if (FAILED(hr)) {
    return false;
  }

  if (debugVisualRedrawRegions) {
    visualDebug->EnableRedrawRegions();
  } else {
    visualDebug->DisableRedrawRegions();
  }

  mDebugVisualRedrawRegions = debugVisualRedrawRegions;
  return true;
}

void DCLayerTree::CompositorBeginFrame() {
  mCurrentFrame++;
  mUsedOverlayTypesInFrame = DCompOverlayTypes::NO_OVERLAY;
  if (mEnableAsyncScreenshotInNextFrame) {
    mEnableAsyncScreenshot = true;
    mEnableAsyncScreenshotInNextFrame = false;
  }
}

void DCLayerTree::CompositorEndFrame() {
  auto start = TimeStamp::Now();
  // Check if the visual tree of surfaces is the same as last frame.
  const bool same = mPrevLayers == mCurrentLayers;

  if (!same) {
    // If not, we need to rebuild the visual tree.
    mRootVisual->RemoveAllVisuals();
  }

  for (auto it = mCurrentLayers.begin(); it != mCurrentLayers.end(); ++it) {
    auto surface_it = mDCSurfaces.find(*it);
    MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
    const auto surface = surface_it->second.get();
    if (!same) {
      const auto visual = surface->GetRootVisual();
      if (UseLayerCompositor()) {
        // Layer compositor expects front to back.
        mRootVisual->AddVisual(visual, true, nullptr);
      } else {
        // Native compositor expects back to front.
        mRootVisual->AddVisual(visual, false, nullptr);
      }
    }
  }

  mPrevLayers.swap(mCurrentLayers);
  mCurrentLayers.clear();

  if (!same || !UseLayerCompositor()) {
    mPendingCommit = true;
  }

  MaybeCommit();

  auto end = TimeStamp::Now();
  mozilla::glean::gfx::composite_swap_time.AccumulateSingleSample(
      (end - start).ToMilliseconds() * 10.);

  // Remove any framebuffers that haven't been
  // used in the last 60 frames.
  //
  // This should use nsTArray::RemoveElementsBy once
  // CachedFrameBuffer is able to properly destroy
  // itself in the destructor.
  const auto gl = GetGLContext();
  for (uint32_t i = 0, len = mFrameBuffers.Length(); i < len; ++i) {
    auto& fb = mFrameBuffers[i];
    if ((mCurrentFrame - fb.lastFrameUsed) > 60) {
      gl->fDeleteRenderbuffers(1, &fb.depthRboId);
      gl->fDeleteFramebuffers(1, &fb.fboId);
      mFrameBuffers.UnorderedRemoveElementAt(i);
      --i;  // Examine the element again, if necessary.
      --len;
    }
  }

  if (mEnableAsyncScreenshot &&
      (mCurrentFrame - mAsyncScreenshotLastFrameUsed) > 1) {
    mEnableAsyncScreenshot = false;
  }

  if (!StaticPrefs::gfx_webrender_dcomp_video_check_slow_present()) {
    return;
  }

  // Disable video overlay if mCompositionDevice->Commit() with video overlay is
  // too slow. It drops fps.

  const auto commitDurationMs =
      static_cast<uint32_t>((end - start).ToMilliseconds());

  nsPrintfCString marker("CommitWait overlay %u %ums ",
                         (uint8_t)mUsedOverlayTypesInFrame, commitDurationMs);
  PROFILER_MARKER_TEXT("CommitWait", GRAPHICS, {}, marker);

  for (auto it = mDCSurfaces.begin(); it != mDCSurfaces.end(); it++) {
    auto* surfaceVideo = it->second->AsDCSurfaceVideo();
    if (surfaceVideo) {
      surfaceVideo->OnCompositorEndFrame(mCurrentFrame, commitDurationMs);
    }
  }
}

void DCLayerTree::BindSwapChain(wr::NativeSurfaceId aId,
                                const wr::DeviceIntRect* aDirtyRects,
                                size_t aNumDirtyRects) {
  auto surface = GetSurface(aId);
  surface->AsDCLayerSurface()->Bind(aDirtyRects, aNumDirtyRects);
}

void DCLayerTree::PresentSwapChain(wr::NativeSurfaceId aId,
                                   const wr::DeviceIntRect* aDirtyRects,
                                   size_t aNumDirtyRects) {
  auto surface = GetSurface(aId);
  surface->AsDCLayerSurface()->Present(aDirtyRects, aNumDirtyRects);
  if (surface->AsDCLayerDCompositionTexture()) {
    mPendingCommit = true;
  }
}

void DCLayerTree::Bind(wr::NativeTileId aId, wr::DeviceIntPoint* aOffset,
                       uint32_t* aFboId, wr::DeviceIntRect aDirtyRect,
                       wr::DeviceIntRect aValidRect) {
  MOZ_ASSERT_UNREACHABLE("Unexpected to be called!");
}

void DCLayerTree::Unbind() {
  MOZ_ASSERT_UNREACHABLE("Unexpected to be called!");
}

void DCLayerTree::CreateSurface(wr::NativeSurfaceId aId,
                                wr::DeviceIntPoint aVirtualOffset,
                                wr::DeviceIntSize aTileSize, bool aIsOpaque) {
  MOZ_ASSERT_UNREACHABLE("Unexpected to be called!");
}

void DCLayerTree::CreateSwapChainSurface(wr::NativeSurfaceId aId,
                                         wr::DeviceIntSize aSize,
                                         bool aIsOpaque,
                                         bool aNeedsSyncDcompCommit) {
  MOZ_ASSERT_IF(mEnableAsyncScreenshot, !aNeedsSyncDcompCommit);

  auto it = mDCSurfaces.find(aId);
  MOZ_RELEASE_ASSERT(it == mDCSurfaces.end());

  UniquePtr<DCSurface> surface;
  if (UseDCLayerDCompositionTexture()) {
    surface = MakeUnique<DCLayerDCompositionTexture>(aSize, aIsOpaque, this);
    if (!surface->Initialize()) {
      gfxCriticalNote << "Failed to initialize DCLayerDCompositionTexture: "
                      << wr::AsUint64(aId);
      RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE);
    }
  } else if (
      !mEnableAsyncScreenshot &&
      (aNeedsSyncDcompCommit ||
       StaticPrefs::
           gfx_webrender_layer_compositor_force_composition_surface_AtStartup())) {
    surface = MakeUnique<DCLayerCompositionSurface>(aSize, aIsOpaque, this);
    if (!surface->Initialize()) {
      gfxCriticalNote << "Failed to initialize DCLayerSurface: "
                      << wr::AsUint64(aId);
      RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE);
    }
  } else {
    surface = MakeUnique<DCSwapChain>(aSize, aIsOpaque, this);
    if (!surface->Initialize()) {
      gfxCriticalNote << "Failed to initialize DCSwapChain: "
                      << wr::AsUint64(aId);
      RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE);
    }
  }

  MOZ_ASSERT_IF(mEnableAsyncScreenshot, mDCSurfaces.empty());

  mDCSurfaces[aId] = std::move(surface);
}

void DCLayerTree::ResizeSwapChainSurface(wr::NativeSurfaceId aId,
                                         wr::DeviceIntSize aSize) {
  auto it = mDCSurfaces.find(aId);
  MOZ_RELEASE_ASSERT(it != mDCSurfaces.end());
  auto surface = it->second.get();

  mPendingCommit = true;

  if (!surface->AsDCLayerSurface()->Resize(aSize)) {
    RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE);
  }
}

void DCLayerTree::CreateExternalSurface(wr::NativeSurfaceId aId,
                                        bool aIsOpaque) {
  auto it = mDCSurfaces.find(aId);
  MOZ_RELEASE_ASSERT(it == mDCSurfaces.end());

  auto surface = MakeUnique<DCExternalSurfaceWrapper>(aIsOpaque, this);
  if (!surface->Initialize()) {
    gfxCriticalNote << "Failed to initialize DCExternalSurfaceWrapper: "
                    << wr::AsUint64(aId);
    return;
  }

  mDCSurfaces[aId] = std::move(surface);
}

void DCLayerTree::DestroySurface(NativeSurfaceId aId) {
  auto surface_it = mDCSurfaces.find(aId);
  MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
  auto surface = surface_it->second.get();

  mRootVisual->RemoveVisual(surface->GetRootVisual());
  mDCSurfaces.erase(surface_it);
}

void DCLayerTree::CreateTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY) {
  MOZ_ASSERT_UNREACHABLE("Unexpected to be called!");
}

void DCLayerTree::DestroyTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY) {
  MOZ_ASSERT_UNREACHABLE("Unexpected to be called!");
}

void DCLayerTree::AttachExternalImage(wr::NativeSurfaceId aId,
                                      wr::ExternalImageId aExternalImage) {
  auto surface_it = mDCSurfaces.find(aId);
  MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
  surface_it->second->AttachExternalImage(aExternalImage);
}

void DCExternalSurfaceWrapper::AttachExternalImage(
    wr::ExternalImageId aExternalImage) {
  if (auto* surface = EnsureSurfaceForExternalImage(aExternalImage)) {
    surface->AttachExternalImage(aExternalImage);
  }
}

template <class ToT>
struct QI {
  template <class FromT>
  [[nodiscard]] static inline RefPtr<ToT> From(FromT* const from) {
    RefPtr<ToT> to;
    (void)from->QueryInterface(static_cast<ToT**>(getter_AddRefs(to)));
    return to;
  }
};

DCSurface* DCExternalSurfaceWrapper::EnsureSurfaceForExternalImage(
    wr::ExternalImageId aExternalImage) {
  if (mSurface) {
    return mSurface.get();
  }

  // Create a new surface based on the texture type.
  RenderTextureHost* texture =
      RenderThread::Get()->GetRenderTexture(aExternalImage);
  if (texture && texture->AsRenderDXGITextureHost()) {
    auto format = texture->GetFormat();
    if (format == gfx::SurfaceFormat::B8G8R8A8 ||
        format == gfx::SurfaceFormat::B8G8R8X8) {
      MOZ_ASSERT(RenderDXGITextureHost::UseDCompositionTextureOverlay(format));
      mSurface.reset(
          new DCSurfaceDCompositionTextureOverlay(mIsOpaque, mDCLayerTree));
      if (!mSurface->Initialize()) {
        gfxCriticalNote
            << "Failed to initialize DCSurfaceDCompositionTextureOverlay: "
            << wr::AsUint64(aExternalImage);
        mSurface = nullptr;
      }
    } else {
      mSurface.reset(new DCSurfaceVideo(mIsOpaque, mDCLayerTree));
      if (!mSurface->Initialize()) {
        gfxCriticalNote << "Failed to initialize DCSurfaceVideo: "
                        << wr::AsUint64(aExternalImage);
        mSurface = nullptr;
      }
    }
  } else if (texture && texture->AsRenderDcompSurfaceTextureHost()) {
    mSurface.reset(new DCSurfaceHandle(mIsOpaque, mDCLayerTree));
    if (!mSurface->Initialize()) {
      gfxCriticalNote << "Failed to initialize DCSurfaceHandle: "
                      << wr::AsUint64(aExternalImage);
      mSurface = nullptr;
    }
  }
  if (!mSurface) {
    gfxCriticalNote << "Failed to create a surface for external image: "
                    << gfx::hexa(texture);
    return nullptr;
  }

  // Add surface's visual which will contain video data to our root visual.
  const auto surfaceVisual = mSurface->GetRootVisual();
  mContentVisual->AddVisual(surfaceVisual, true, nullptr);

  // -
  // Apply color management.

  [&]() {
    if (!StaticPrefs::gfx_webrender_dcomp_color_manage_with_filters()) return;

    const auto cmsMode = GfxColorManagementMode();
    if (cmsMode == CMSMode::Off) return;

    const auto dcomp = mDCLayerTree->GetCompositionDevice();
    const auto dcomp3 = QI<IDCompositionDevice3>::From(dcomp);
    if (!dcomp3) {
      NS_WARNING(
          "No IDCompositionDevice3, cannot use dcomp for color management.");
      return;
    }

    // -

    const auto cspace = [&]() {
      const auto rangedCspace = texture->GetYUVColorSpace();
      const auto info = FromYUVRangedColorSpace(rangedCspace);
      auto ret = ToColorSpace2(info.space);
      if (ret == gfx::ColorSpace2::Display && cmsMode == CMSMode::All) {
        ret = gfx::ColorSpace2::SRGB;
      }
      return ret;
    }();

    const bool rec709GammaAsSrgb =
        StaticPrefs::gfx_color_management_rec709_gamma_as_srgb();
    const bool rec2020GammaAsRec709 =
        StaticPrefs::gfx_color_management_rec2020_gamma_as_rec709();

    auto cspaceDesc = color::ColorspaceDesc{};
    const auto rangedCspace = texture->GetYUVColorSpace();
    const auto info = FromYUVRangedColorSpace(rangedCspace);
    const auto tf = info.transferFunction;
    switch (cspace) {
      case gfx::ColorSpace2::Display:
        return;  // No color management needed!
      case gfx::ColorSpace2::SRGB:
        cspaceDesc.chrom = color::Chromaticities::Srgb();
        cspaceDesc.tf = color::TransferFunctionDesc::Srgb();
        switch (tf) {
          case gfx::TransferFunction::SRGB:
            cspaceDesc.tf = color::TransferFunctionDesc::Srgb();
            break;
          case gfx::TransferFunction::BT709:
            if (rec709GammaAsSrgb) {
              cspaceDesc.tf = color::TransferFunctionDesc::Srgb();
            } else {
              cspaceDesc.tf = color::TransferFunctionDesc::Rec709();
            }
            break;
          case gfx::TransferFunction::HLG:
            cspaceDesc.tf = color::TransferFunctionDesc::Rec2100_HLG();
            break;
          case gfx::TransferFunction::PQ:
            cspaceDesc.tf = color::TransferFunctionDesc::Rec2100_PQ();
            break;
          case gfx::TransferFunction::LINEAR:
            cspaceDesc.tf = color::TransferFunctionDesc::Linear();
            break;
        }
        break;
      case gfx::ColorSpace2::DISPLAY_P3:
        cspaceDesc.chrom = color::Chromaticities::DisplayP3();
        cspaceDesc.tf = color::TransferFunctionDesc::DisplayP3();
        switch (tf) {
          case gfx::TransferFunction::SRGB:
            cspaceDesc.tf = color::TransferFunctionDesc::DisplayP3();
            break;
          case gfx::TransferFunction::BT709:
            if (rec709GammaAsSrgb) {
              cspaceDesc.tf = color::TransferFunctionDesc::Srgb();
            } else {
              cspaceDesc.tf = color::TransferFunctionDesc::Rec709();
            }
            break;
          case gfx::TransferFunction::HLG:
            cspaceDesc.tf = color::TransferFunctionDesc::Rec2100_HLG();
            break;
          case gfx::TransferFunction::PQ:
            cspaceDesc.tf = color::TransferFunctionDesc::Rec2100_PQ();
            break;
          case gfx::TransferFunction::LINEAR:
            cspaceDesc.tf = color::TransferFunctionDesc::Linear();
            break;
        }
        break;
      case gfx::ColorSpace2::BT601_525:  // aka smpte170m NTSC
        cspaceDesc.chrom = color::Chromaticities::Rec601_525_Ntsc();
        cspaceDesc.tf = color::TransferFunctionDesc::Rec709();
        switch (tf) {
          case gfx::TransferFunction::SRGB:
            cspaceDesc.tf = color::TransferFunctionDesc::Srgb();
            break;
          case gfx::TransferFunction::BT709:
            if (rec709GammaAsSrgb) {
              cspaceDesc.tf = color::TransferFunctionDesc::Srgb();
            } else {
              cspaceDesc.tf = color::TransferFunctionDesc::Rec709();
            }
            break;
          case gfx::TransferFunction::HLG:
            cspaceDesc.tf = color::TransferFunctionDesc::Rec2100_HLG();
            break;
          case gfx::TransferFunction::PQ:
            cspaceDesc.tf = color::TransferFunctionDesc::Rec2100_PQ();
            break;
          case gfx::TransferFunction::LINEAR:
            cspaceDesc.tf = color::TransferFunctionDesc::Linear();
            break;
        }
        break;
      case gfx::ColorSpace2::BT709:  // Same gamut as SRGB, but different gamma.
        cspaceDesc.chrom = color::Chromaticities::Rec709();
        cspaceDesc.tf = color::TransferFunctionDesc::Rec709();
        switch (tf) {
          case gfx::TransferFunction::SRGB:
            cspaceDesc.tf = color::TransferFunctionDesc::Srgb();
            break;
          case gfx::TransferFunction::BT709:
            if (rec709GammaAsSrgb) {
              cspaceDesc.tf = color::TransferFunctionDesc::Srgb();
            } else {
              cspaceDesc.tf = color::TransferFunctionDesc::Rec709();
            }
            break;
          case gfx::TransferFunction::HLG:
            cspaceDesc.tf = color::TransferFunctionDesc::Rec2100_HLG();
            break;
          case gfx::TransferFunction::PQ:
            cspaceDesc.tf = color::TransferFunctionDesc::Rec2100_PQ();
            break;
          case gfx::TransferFunction::LINEAR:
            cspaceDesc.tf = color::TransferFunctionDesc::Linear();
            break;
        }
        break;
      case gfx::ColorSpace2::BT2020:
        cspaceDesc.chrom = color::Chromaticities::Rec2020();
        cspaceDesc.tf = color::TransferFunctionDesc::Rec2020_12bit();
        switch (tf) {
          case gfx::TransferFunction::SRGB:
            cspaceDesc.tf = color::TransferFunctionDesc::Srgb();
            break;
          case gfx::TransferFunction::BT709:
            // BT2020 uses a higher precision version of BT709/BT1886 values.
            if (rec2020GammaAsRec709 && rec709GammaAsSrgb) {
              cspaceDesc.tf = color::TransferFunctionDesc::Srgb();
            } else if (rec2020GammaAsRec709) {
              cspaceDesc.tf = color::TransferFunctionDesc::Rec709();
            } else {
              cspaceDesc.tf = color::TransferFunctionDesc::Rec2020_12bit();
            }
            break;
          case gfx::TransferFunction::HLG:
            cspaceDesc.tf = color::TransferFunctionDesc::Rec2100_HLG();
            break;
          case gfx::TransferFunction::PQ:
            cspaceDesc.tf = color::TransferFunctionDesc::Rec2100_PQ();
            break;
          case gfx::TransferFunction::LINEAR:
            cspaceDesc.tf = color::TransferFunctionDesc::Linear();
            break;
        }
    }

    const auto cprofileIn = color::ColorProfileDesc::From(cspaceDesc);
    auto cprofileOut = mDCLayerTree->OutputColorProfile();
    bool pretendSrgb = true;
    if (pretendSrgb) {
      cprofileOut = color::ColorProfileDesc::From(color::ColorspaceDesc{
          .chrom = color::Chromaticities::Srgb(),
          .tf = color::TransferFunctionDesc::Srgb(),
      });
    }
    const auto conversion = color::ColorProfileConversionDesc::From({
        .src = cprofileIn,
        .dst = cprofileOut,
    });

    // -

    auto chain = ColorManagementChain::From(*dcomp3, conversion);
    mCManageChain = Some(chain);

    surfaceVisual->SetEffect(mCManageChain->last.get());
  }();

  return mSurface.get();
}

void DCExternalSurfaceWrapper::PresentExternalSurface(gfx::Matrix& aTransform) {
  MOZ_ASSERT(mSurface);
  if (auto* surface = mSurface->AsDCSurfaceVideo()) {
    if (surface->CalculateSwapChainSize(aTransform)) {
      surface->PresentVideo();
    }
  } else if (auto* surface = mSurface->AsDCSurfaceHandle()) {
    surface->PresentSurfaceHandle();
  } else if (auto* surface =
                 mSurface->AsDCSurfaceDCompositionTextureOverlay()) {
    surface->Present();
  }
}

template <typename T>
static inline D2D1_RECT_F D2DRect(const T& aRect) {
  return D2D1::RectF(aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost());
}

static inline D2D1_MATRIX_3X2_F D2DMatrix(const gfx::Matrix& aTransform) {
  return D2D1::Matrix3x2F(aTransform._11, aTransform._12, aTransform._21,
                          aTransform._22, aTransform._31, aTransform._32);
}

void DCLayerTree::AddSurface(wr::NativeSurfaceId aId,
                             const wr::CompositorSurfaceTransform& aTransform,
                             wr::DeviceIntRect aClipRect,
                             wr::ImageRendering aImageRendering,
                             wr::DeviceIntRect aRoundedClipRect,
                             wr::ClipRadius aClipRadius) {
  auto it = mDCSurfaces.find(aId);
  MOZ_RELEASE_ASSERT(it != mDCSurfaces.end());
  const auto surface = it->second.get();
  const auto visual = surface->GetContentVisual();

  float sx = aTransform.scale.x;
  float sy = aTransform.scale.y;
  float tx = aTransform.offset.x;
  float ty = aTransform.offset.y;
  gfx::Matrix transform(sx, 0.0, 0.0, sy, tx, ty);

  surface->PresentExternalSurface(transform);

  if (UseLayerCompositor() &&
      !surface->IsUpdated(aTransform, aClipRect, aImageRendering,
                          aRoundedClipRect, aClipRadius)) {
    mCurrentLayers.push_back(aId);
    return;
  }

  mPendingCommit = true;

  // The DirectComposition API applies clipping *before* any
  // transforms/offset, whereas we want the clip applied after. Right now, we
  // only support rectilinear transforms, and then we transform our clip into
  // pre-transform coordinate space for it to be applied there.
  // DirectComposition does have an option for pre-transform clipping, if you
  // create an explicit IDCompositionEffectGroup object and set a 3D transform
  // on that. I suspect that will perform worse though, so we should only do
  // that for complex transforms (which are never provided right now).
  MOZ_ASSERT(transform.IsRectilinear());
  gfx::Rect clip = transform.Inverse().TransformBounds(gfx::Rect(
      aClipRect.min.x, aClipRect.min.y, aClipRect.width(), aClipRect.height()));
  // Set the clip rect - converting from world space to the pre-offset space
  // that DC requires for rectangle clips.
  visual->SetClip(D2DRect(clip));

  // TODO: The input matrix is a 4x4, but we only support a 3x2 at
  // the D3D API level (unless we QI to IDCompositionVisual3, which might
  // not be available?).
  // Should we assert here, or restrict at the WR API level.
  visual->SetTransform(D2DMatrix(transform));

  if (aImageRendering == wr::ImageRendering::Auto) {
    visual->SetBitmapInterpolationMode(
        DCOMPOSITION_BITMAP_INTERPOLATION_MODE_LINEAR);
  } else {
    visual->SetBitmapInterpolationMode(
        DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
  }

  surface->SetClip(aRoundedClipRect, aClipRadius);

  mCurrentLayers.push_back(aId);
}

GLuint DCLayerTree::GetOrCreateFbo(int aWidth, int aHeight) {
  const auto gl = GetGLContext();
  GLuint fboId = 0;

  // Check if we have a cached FBO with matching dimensions
  for (auto it = mFrameBuffers.begin(); it != mFrameBuffers.end(); ++it) {
    if (it->width == aWidth && it->height == aHeight) {
      fboId = it->fboId;
      it->lastFrameUsed = mCurrentFrame;
      break;
    }
  }

  // If not, create a new FBO with attached depth buffer
  if (fboId == 0) {
    // Create the depth buffer
    GLuint depthRboId;
    gl->fGenRenderbuffers(1, &depthRboId);
    gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, depthRboId);
    gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, LOCAL_GL_DEPTH_COMPONENT24,
                             aWidth, aHeight);

    // Create the framebuffer and attach the depth buffer to it
    gl->fGenFramebuffers(1, &fboId);
    gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fboId);
    gl->fFramebufferRenderbuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
                                 LOCAL_GL_DEPTH_ATTACHMENT,
                                 LOCAL_GL_RENDERBUFFER, depthRboId);

    // Store this in the cache for future calls.
    // TODO(gw): Maybe we should periodically scan this list and remove old
    // entries that
    //           haven't been used for some time?
    DCLayerTree::CachedFrameBuffer frame_buffer_info;
    frame_buffer_info.width = aWidth;
    frame_buffer_info.height = aHeight;
    frame_buffer_info.fboId = fboId;
    frame_buffer_info.depthRboId = depthRboId;
    frame_buffer_info.lastFrameUsed = mCurrentFrame;
    mFrameBuffers.AppendElement(frame_buffer_info);
  }

  return fboId;
}

bool DCLayerTree::EnsureVideoProcessor(const gfx::IntSize& aInputSize,
                                       const gfx::IntSize& aOutputSize) {
  HRESULT hr;

  if (!mVideoDevice || !mVideoContext) {
    return false;
  }

  if (mVideoProcessor && (aInputSize <= mVideoInputSize) &&
      (aOutputSize <= mVideoOutputSize)) {
    return true;
  }

  mVideoProcessor = nullptr;
  mVideoProcessorEnumerator = nullptr;

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

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

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

  // Reduce power cosumption
  // By default, the driver might perform certain processing tasks
  // automatically
  mVideoContext->VideoProcessorSetStreamAutoProcessingMode(mVideoProcessor, 0,
                                                           FALSE);

  mVideoInputSize = aInputSize;
  mVideoOutputSize = aOutputSize;

  return true;
}

bool DCLayerTree::SupportsHardwareOverlays() {
  return sGpuOverlayInfo->mSupportsHardwareOverlays;
}

bool DCLayerTree::SupportsHardwareOverlayRGB10A2() {
  return sGpuOverlayInfo->mSupportsHardwareOverlayRGB10A2;
}

bool DCLayerTree::SupportsHardwareOverlayRGBA16F() {
  return sGpuOverlayInfo->mSupportsHardwareOverlayRGBA16F;
}

bool DCLayerTree::SupportsSwapChainTearing() {
  RefPtr<ID3D11Device> device = mDevice;
  static const bool supported = [device] {
    RefPtr<IDXGIDevice> dxgiDevice;
    RefPtr<IDXGIAdapter> adapter;
    device->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice));
    dxgiDevice->GetAdapter(getter_AddRefs(adapter));

    RefPtr<IDXGIFactory5> dxgiFactory;
    HRESULT hr = adapter->GetParent(
        IID_PPV_ARGS((IDXGIFactory5**)getter_AddRefs(dxgiFactory)));
    if (FAILED(hr)) {
      return false;
    }

    BOOL presentAllowTearing = FALSE;
    hr = dxgiFactory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING,
                                          &presentAllowTearing,
                                          sizeof(presentAllowTearing));
    if (FAILED(hr)) {
      return false;
    }

    if (auto* gpuParent = gfx::GPUParent::GetSingleton()) {
      gpuParent->NotifySwapChainInfo(
          layers::SwapChainInfo(!!presentAllowTearing));
    } else if (XRE_IsParentProcess()) {
      MOZ_ASSERT_UNREACHABLE("unexpected to be called");
    }
    return !!presentAllowTearing;
  }();

  if (!StaticPrefs::gfx_webrender_swap_chain_allow_tearing_AtStartup()) {
    return false;
  }

  return supported;
}

bool DCLayerTree::UseDCLayerDCompositionTexture() {
  if (!gfx::gfxVars::WebRenderLayerCompositorDCompTexture()) {
    return false;
  }
  return gfx::DeviceManagerDx::Get()->CanUseDCompositionTexture();
}

DXGI_FORMAT DCLayerTree::GetOverlayFormatForSDR() {
  return sGpuOverlayInfo->mOverlayFormatUsed;
}

static layers::OverlaySupportType FlagsToOverlaySupportType(
    UINT aFlags, bool aSoftwareOverlaySupported) {
  if (aFlags & DXGI_OVERLAY_SUPPORT_FLAG_SCALING) {
    return layers::OverlaySupportType::Scaling;
  }
  if (aFlags & DXGI_OVERLAY_SUPPORT_FLAG_DIRECT) {
    return layers::OverlaySupportType::Direct;
  }
  if (aSoftwareOverlaySupported) {
    return layers::OverlaySupportType::Software;
  }
  return layers::OverlaySupportType::None;
}

layers::OverlayInfo DCLayerTree::GetOverlayInfo() {
  layers::OverlayInfo info;

  info.mSupportsOverlays = sGpuOverlayInfo->mSupportsHardwareOverlays;
  info.mNv12Overlay =
      FlagsToOverlaySupportType(sGpuOverlayInfo->mNv12OverlaySupportFlags,
                                /* aSoftwareOverlaySupported */ false);
  info.mYuy2Overlay =
      FlagsToOverlaySupportType(sGpuOverlayInfo->mYuy2OverlaySupportFlags,
                                /* aSoftwareOverlaySupported */ false);
  info.mBgra8Overlay =
      FlagsToOverlaySupportType(sGpuOverlayInfo->mBgra8OverlaySupportFlags,
                                /* aSoftwareOverlaySupported */ true);
  info.mRgb10a2Overlay =
      FlagsToOverlaySupportType(sGpuOverlayInfo->mRgb10a2OverlaySupportFlags,
                                /* aSoftwareOverlaySupported */ false);
  info.mRgba16fOverlay =
      FlagsToOverlaySupportType(sGpuOverlayInfo->mRgba16fOverlaySupportFlags,
                                /* aSoftwareOverlaySupported */ false);

  info.mSupportsVpSuperResolution = sGpuOverlayInfo->mSupportsVpSuperResolution;
  info.mSupportsVpAutoHDR = sGpuOverlayInfo->mSupportsVpAutoHDR;
  info.mSupportsHDR = sGpuOverlayInfo->mSupportsHDR;

  return info;
}

void DCLayerTree::SetUsedOverlayTypeInFrame(DCompOverlayTypes aTypes) {
  mUsedOverlayTypesInFrame |= aTypes;
}

DCSurface::DCSurface(bool aIsOpaque, DCLayerTree* aDCLayerTree)
    : mDCLayerTree(aDCLayerTree), mIsOpaque(aIsOpaque) {}

DCSurface::~DCSurface() {}

bool DCSurface::IsUpdated(const wr::CompositorSurfaceTransform& aTransform,
                          const wr::DeviceIntRect& aClipRect,
                          const wr::ImageRendering aImageRendering,
                          const wr::DeviceIntRect& aRoundedClipRect,
                          const wr::ClipRadius& aClipRadius) {
  if (mDCSurfaceData.isSome() &&
      mDCSurfaceData.ref().mTransform == aTransform &&
      mDCSurfaceData.ref().mClipRect == aClipRect &&
      mDCSurfaceData.ref().mImageRendering == aImageRendering &&
      mDCSurfaceData.ref().mRoundedClipRect == aRoundedClipRect &&
      mDCSurfaceData.ref().mClipRadius == aClipRadius) {
    return false;
  }
  mDCSurfaceData = Some(DCSurfaceData(aTransform, aClipRect, aImageRendering,
                                      aRoundedClipRect, aClipRadius));
  return true;
}

bool DCSurface::Initialize() {
  HRESULT hr;
  const auto dCompDevice = mDCLayerTree->GetCompositionDevice();
  hr = dCompDevice->CreateVisual(getter_AddRefs(mRootVisual));
  if (FAILED(hr)) {
    gfxCriticalNote << "Failed to create DCompositionVisual: " << gfx::hexa(hr);
    return false;
  }
  hr = dCompDevice->CreateVisual(getter_AddRefs(mContentVisual));
  if (FAILED(hr)) {
    gfxCriticalNote << "Failed to create DCompositionVisual: " << gfx::hexa(hr);
    return false;
  }
  mRootVisual->AddVisual(mContentVisual, false, nullptr);
  hr = dCompDevice->CreateRectangleClip(getter_AddRefs(mClip));
  if (FAILED(hr)) {
    gfxCriticalNote << "Failed to create RectangleClip: " << gfx::hexa(hr);
    return false;
  }

  return true;
}

void DCSurface::SetClip(wr::DeviceIntRect aClipRect,
                        wr::ClipRadius aClipRadius) {
  bool needsClip =
      aClipRadius.top_left > 0.0f || aClipRadius.top_right > 0.0f ||
      aClipRadius.bottom_left > 0.0f || aClipRadius.bottom_right > 0.0f;

  if (needsClip) {
    mClip->SetLeft(aClipRect.min.x);
    mClip->SetRight(aClipRect.max.x);
    mClip->SetTop(aClipRect.min.y);
    mClip->SetBottom(aClipRect.max.y);

    mClip->SetTopLeftRadiusX(aClipRadius.top_left);
    mClip->SetTopLeftRadiusY(aClipRadius.top_left);

    mClip->SetTopRightRadiusX(aClipRadius.top_right);
    mClip->SetTopRightRadiusY(aClipRadius.top_right);

    mClip->SetBottomLeftRadiusX(aClipRadius.bottom_left);
    mClip->SetBottomLeftRadiusY(aClipRadius.bottom_left);

    mClip->SetBottomRightRadiusX(aClipRadius.bottom_right);
    mClip->SetBottomRightRadiusY(aClipRadius.bottom_right);

    mRootVisual->SetBorderMode(DCOMPOSITION_BORDER_MODE_SOFT);
    mRootVisual->SetClip(mClip);
  } else {
    mRootVisual->SetBorderMode(DCOMPOSITION_BORDER_MODE_INHERIT);
    mRootVisual->SetClip(nullptr);
  }
}

DCLayerDCompositionTexture::TextureHolder::TextureHolder(
    ID3D11Texture2D* aTexture, IDCompositionTexture* aDCompositionTexture,
    EGLSurface aEGLSurface)
    : mTexture(aTexture),
      mDCompositionTexture(aDCompositionTexture),
      mEGLSurface(aEGLSurface) {}

DCLayerDCompositionTexture::DCLayerDCompositionTexture(
    wr::DeviceIntSize aSize, bool aIsOpaque, DCLayerTree* aDCLayerTree)
    : DCLayerSurface(aIsOpaque, aDCLayerTree),
      mSwapChainBufferCount(gfx::gfxVars::UseWebRenderTripleBufferingWin() ? 3
                                                                           : 2),
      mSize(aSize) {}

DCLayerDCompositionTexture::~DCLayerDCompositionTexture() { DestroyTextures(); }

bool DCLayerDCompositionTexture::Initialize() {
  DCSurface::Initialize();

  if (!AllocateTextures()) {
    return false;
  }
  return true;
}

bool DCLayerDCompositionTexture::AllocateTextures() {
  MOZ_ASSERT(mAvailableTextureHolders.empty());

  HRESULT hr;
  const auto device = mDCLayerTree->GetDevice();
  const auto dcomp = mDCLayerTree->GetCompositionDevice();
  const auto dcomp4 = QI<IDCompositionDevice4>::From(dcomp);
  if (!dcomp4) {
    return false;
  }

  const auto gl = mDCLayerTree->GetGLContext();
  const auto& gle = gl::GLContextEGL::Cast(gl);
  const auto& egl = gle->mEgl;
  const EGLConfig eglConfig = mDCLayerTree->GetEGLConfig();

  CD3D11_TEXTURE2D_DESC desc(
      DXGI_FORMAT_B8G8R8A8_UNORM, mSize.width, mSize.height, 1, 1,
      D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET);

  desc.MiscFlags =
      D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED;

  for (size_t i = 0; i < mSwapChainBufferCount; i++) {
    // Allocate ID3D11Texture2D
    RefPtr<ID3D11Texture2D> texture;
    hr = device->CreateTexture2D(&desc, nullptr, getter_AddRefs(texture));
    if (FAILED(hr)) {
      gfxCriticalNoteOnce << "CreateTexture2D failed:  " << gfx::hexa(hr);
      return false;
    }

    // Allocate IDCompositionTexture
    RefPtr<IDCompositionTexture> dcompTexture;
    hr =
        dcomp4->CreateCompositionTexture(texture, getter_AddRefs(dcompTexture));
    if (FAILED(hr)) {
      gfxCriticalNoteOnce << "CreateCompositionTexture failed:  "
                          << gfx::hexa(hr);
      return false;
    }

    const auto alphaMode =
        mIsOpaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
    dcompTexture->SetAlphaMode(alphaMode);
    // XXX
    // dcompTexture->SetColorSpace();

    // Allocate mEGLSurface
    EGLSurface surface = EGL_NO_SURFACE;
    const EGLint pbuffer_attribs[]{LOCAL_EGL_WIDTH, mSize.width,
                                   LOCAL_EGL_HEIGHT, mSize.height,
                                   LOCAL_EGL_NONE};
    const auto buffer = reinterpret_cast<EGLClientBuffer>(texture.get());

    surface = egl->fCreatePbufferFromClientBuffer(
        LOCAL_EGL_D3D_TEXTURE_ANGLE, buffer, eglConfig, pbuffer_attribs);
    if (!surface) {
      EGLint err = egl->mLib->fGetError();
      gfxCriticalNote << "Failed to create Pbuffer error: " << gfx::hexa(err)
                      << " Size : "
                      << LayoutDeviceIntSize(mSize.width, mSize.height);
      return false;
    }

    auto textureHolder =
        MakeUnique<TextureHolder>(texture, dcompTexture, surface);
    mAvailableTextureHolders.push_back(std::move(textureHolder));
  }

  MOZ_ASSERT(mAvailableTextureHolders.size() == mSwapChainBufferCount);

  return true;
}

void DCLayerDCompositionTexture::DestroyTextures() {
  const auto gl = mDCLayerTree->GetGLContext();
  const auto& gle = gl::GLContextEGL::Cast(gl);
  const auto& egl = gle->mEgl;

  if (mCurrentTextureHolder) {
    mAvailableTextureHolders.push_back(std::move(mCurrentTextureHolder));
  }

  if (mPresentingTextureHolder) {
    mAvailableTextureHolders.push_back(std::move(mPresentingTextureHolder));
  }

  while (!mAvailableTextureHolders.empty()) {
    auto& front = mAvailableTextureHolders.front();

    if (front->mEGLSurface) {
      if (gle->GetEGLSurfaceOverride() == front->mEGLSurface) {
        gle->SetEGLSurfaceOverride(EGL_NO_SURFACE);
      }
      egl->fDestroySurface(front->mEGLSurface);
      front->mEGLSurface = EGL_NO_SURFACE;
    }

    mAvailableTextureHolders.pop_front();
  }

  MOZ_ASSERT(!mCurrentTextureHolder);
  MOZ_ASSERT(!mPresentingTextureHolder);
  MOZ_ASSERT(mAvailableTextureHolders.empty());
}

UniquePtr<DCLayerDCompositionTexture::TextureHolder>
DCLayerDCompositionTexture::GetNextTexture() {
  MOZ_ASSERT(!mAvailableTextureHolders.empty());

  if (mAvailableTextureHolders.empty()) {
    return nullptr;
  }

  UniquePtr<TextureHolder> textureHolder =
      std::move(mAvailableTextureHolders.front());
  mAvailableTextureHolders.pop_front();

  return textureHolder;
}

void DCLayerDCompositionTexture::UpdateCurrentTexture() {
  if (mCurrentTextureHolder) {
    mAvailableTextureHolders.push_back(std::move(mCurrentTextureHolder));
  }

  MOZ_ASSERT(!mCurrentTextureHolder);

  mCurrentTextureHolder = GetNextTexture();
}

void DCLayerDCompositionTexture::Bind(const wr::DeviceIntRect* aDirtyRects,
                                      size_t aNumDirtyRects) {
  UpdateCurrentTexture();

  if (!mCurrentTextureHolder ||
      (mCurrentTextureHolder->mEGLSurface == EGL_NO_SURFACE)) {
    return;
  }

  const auto gl = mDCLayerTree->GetGLContext();
  const auto& gle = gl::GLContextEGL::Cast(gl);

  gle->SetEGLSurfaceOverride(mCurrentTextureHolder->mEGLSurface);
}

bool DCLayerDCompositionTexture::Resize(wr::DeviceIntSize aSize) {
  DestroyTextures();
  mSize = aSize;
  bool ret = AllocateTextures();
  return ret;
}

void DCLayerDCompositionTexture::Present(const wr::DeviceIntRect* aDirtyRects,
                                         size_t aNumDirtyRects) {
  if (!mCurrentTextureHolder) {
    return;
  }

  if (mPresentingTextureHolder) {
    mAvailableTextureHolders.push_back(std::move(mPresentingTextureHolder));
  }
  MOZ_ASSERT(!mPresentingTextureHolder);

  mPresentingTextureHolder = std::move(mCurrentTextureHolder);
  MOZ_ASSERT(!mCurrentTextureHolder);
  MOZ_ASSERT(mPresentingTextureHolder);

  mContentVisual->SetContent(mPresentingTextureHolder->mDCompositionTexture);
}

DCSwapChain::DCSwapChain(wr::DeviceIntSize aSize, bool aIsOpaque,
                         DCLayerTree* aDCLayerTree)
    : DCLayerSurface(aIsOpaque, aDCLayerTree),
      mSwapChainBufferCount(gfx::gfxVars::UseWebRenderTripleBufferingWin() ? 3
                                                                           : 2),
      mSize(aSize),
      mEGLSurface(EGL_NO_SURFACE) {
  MOZ_ASSERT(mSwapChainBufferCount == 2 || mSwapChainBufferCount == 3);
}

DCSwapChain::~DCSwapChain() {
  if (mEGLSurface) {
    const auto gl = mDCLayerTree->GetGLContext();

    const auto& gle = gl::GLContextEGL::Cast(gl);
    const auto& egl = gle->mEgl;

    if (gle->GetEGLSurfaceOverride() == mEGLSurface) {
      gle->SetEGLSurfaceOverride(EGL_NO_SURFACE);
    }
    egl->fDestroySurface(mEGLSurface);
    mEGLSurface = EGL_NO_SURFACE;
  }
}

bool DCSwapChain::Initialize() {
  DCSurface::Initialize();

  const auto gl = mDCLayerTree->GetGLContext();
  const auto& gle = gl::GLContextEGL::Cast(gl);
  const auto& egl = gle->mEgl;

  HRESULT hr;
  auto device = mDCLayerTree->GetDevice();

  RefPtr<IDXGIDevice> dxgiDevice;
  device->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice));

  RefPtr<IDXGIFactory2> dxgiFactory;
  {
    RefPtr<IDXGIAdapter> adapter;
    dxgiDevice->GetAdapter(getter_AddRefs(adapter));
    adapter->GetParent(
        IID_PPV_ARGS((IDXGIFactory2**)getter_AddRefs(dxgiFactory)));
  }

  DXGI_SWAP_CHAIN_DESC1 desc{};
  desc.Width = mSize.width;
  desc.Height = mSize.height;
  desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
  desc.SampleDesc.Count = 1;
  desc.SampleDesc.Quality = 0;
  desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
  desc.BufferCount = mSwapChainBufferCount;
  // DXGI_SCALING_NONE caused swap chain creation failure.
  desc.Scaling = DXGI_SCALING_STRETCH;
  desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
  desc.AlphaMode =
      mIsOpaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
  desc.Flags = 0;
  if (mDCLayerTree->SupportsSwapChainTearing()) {
    desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
  }

  hr = dxgiFactory->CreateSwapChainForComposition(device, &desc, nullptr,
                                                  getter_AddRefs(mSwapChain));
  if (FAILED(hr)) {
    gfxCriticalNote << "CreateSwapChainForComposition() failed: "
                    << gfx::hexa(hr) << " Size : "
                    << LayoutDeviceIntSize(mSize.width, mSize.height);
    return false;
  }
  mContentVisual->SetContent(mSwapChain);

  RefPtr<ID3D11Texture2D> backBuffer;
  hr = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
                             (void**)getter_AddRefs(backBuffer));
  if (hr == DXGI_ERROR_INVALID_CALL) {
    // This happens on some GPUs/drivers when there's a TDR.
    if (device->GetDeviceRemovedReason() != S_OK) {
      gfxCriticalNote << "GetBuffer returned invalid call: " << gfx::hexa(hr)
                      << " Size : "
                      << LayoutDeviceIntSize(mSize.width, mSize.height);
      return false;
    }
  }

  const EGLint pbuffer_attribs[]{LOCAL_EGL_WIDTH, mSize.width, LOCAL_EGL_HEIGHT,
                                 mSize.height, LOCAL_EGL_NONE};
  const auto buffer = reinterpret_cast<EGLClientBuffer>(backBuffer.get());
  EGLConfig eglConfig = mDCLayerTree->GetEGLConfig();

  mEGLSurface = egl->fCreatePbufferFromClientBuffer(
      LOCAL_EGL_D3D_TEXTURE_ANGLE, buffer, eglConfig, pbuffer_attribs);
  if (!mEGLSurface) {
    EGLint err = egl->mLib->fGetError();
    gfxCriticalNote << "Failed to create Pbuffer error: " << gfx::hexa(err)
                    << " Size : "
                    << LayoutDeviceIntSize(mSize.width, mSize.height);
    return false;
  }

  return true;
}

void DCSwapChain::Bind(const wr::DeviceIntRect* aDirtyRects,
                       size_t aNumDirtyRects) {
  const auto gl = mDCLayerTree->GetGLContext();
  const auto& gle = gl::GLContextEGL::Cast(gl);

  gle->SetEGLSurfaceOverride(mEGLSurface);
}

bool DCSwapChain::Resize(wr::DeviceIntSize aSize) {
  MOZ_ASSERT(mSwapChain);

  if (!mSwapChain) {
    return false;
  }

  const auto gl = mDCLayerTree->GetGLContext();

  const auto& gle = gl::GLContextEGL::Cast(gl);
  const auto& egl = gle->mEgl;

  if (mEGLSurface) {
    egl->fDestroySurface(mEGLSurface);
    mEGLSurface = EGL_NO_SURFACE;
  }

  DXGI_SWAP_CHAIN_DESC desc;
  HRESULT hr;

  mSwapChain->GetDesc(&desc);

  UINT flags = mDCLayerTree->SupportsSwapChainTearing()
                   ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING
                   : 0;
  hr = mSwapChain->ResizeBuffers(desc.BufferCount, aSize.width, aSize.height,
                                 DXGI_FORMAT_B8G8R8A8_UNORM, flags);
  if (FAILED(hr)) {
    gfxCriticalNote << "Failed to resize swap chain buffers: " << gfx::hexa(hr)
                    << " Size : "
                    << LayoutDeviceIntSize(aSize.width, aSize.height);
    return false;
  }

  RefPtr<ID3D11Texture2D> backBuffer;
  hr = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
                             (void**)getter_AddRefs(backBuffer));
  if (hr == DXGI_ERROR_INVALID_CALL) {
    auto device = mDCLayerTree->GetDevice();
    // This happens on some GPUs/drivers when there's a TDR.
    if (device->GetDeviceRemovedReason() != S_OK) {
      gfxCriticalNote << "GetBuffer returned invalid call: " << gfx::hexa(hr)
                      << " Size : "
                      << LayoutDeviceIntSize(aSize.width, aSize.height);
      return false;
    }
  }

  const EGLint pbuffer_attribs[]{LOCAL_EGL_WIDTH, aSize.width, LOCAL_EGL_HEIGHT,
                                 aSize.height, LOCAL_EGL_NONE};
  const auto buffer = reinterpret_cast<EGLClientBuffer>(backBuffer.get());
  EGLConfig eglConfig = mDCLayerTree->GetEGLConfig();

  mEGLSurface = egl->fCreatePbufferFromClientBuffer(
      LOCAL_EGL_D3D_TEXTURE_ANGLE, buffer, eglConfig, pbuffer_attribs);
  if (!mEGLSurface) {
    EGLint err = egl->mLib->fGetError();
    gfxCriticalNote << "Failed to create Pbuffer error: " << gfx::hexa(err)
                    << " Size : "
                    << LayoutDeviceIntSize(aSize.width, aSize.height);
    return false;
  }

  mSize = aSize;
  return true;
}

void DCSwapChain::Present(const wr::DeviceIntRect* aDirtyRects,
                          size_t aNumDirtyRects) {
  MOZ_ASSERT_IF(aNumDirtyRects > 0, !mFirstPresent);

  MOZ_ASSERT(mSwapChain);

  if (!mSwapChain) {
    return;
  }

  HRESULT hr = S_OK;
  int rectsCount = 0;
  StackArray<RECT, 1> rects(aNumDirtyRects);
  const UINT flags =
      mDCLayerTree->SupportsSwapChainTearing() ? DXGI_PRESENT_ALLOW_TEARING : 0;

  if (aNumDirtyRects > 0) {
    for (size_t i = 0; i < aNumDirtyRects; ++i) {
      const auto& rect = aDirtyRects[i];
      // Clip rect to bufferSize
      int left = std::clamp((int)rect.min.x, 0, mSize.width);
      int top = std::clamp((int)rect.min.y, 0, mSize.height);
      int right = std::clamp((int)rect.max.x, 0, mSize.width);
      int bottom = std::clamp((int)rect.max.y, 0, mSize.height);

      // When rect is not empty, the rect could be passed to Present1().
      if (left < right && top < bottom) {
        rects[rectsCount].left = left;
        rects[rectsCount].top = top;
        rects[rectsCount].right = right;
        rects[rectsCount].bottom = bottom;
        rectsCount++;
      }
    }

    if (rectsCount > 0) {
      DXGI_PRESENT_PARAMETERS params;
      PodZero(&params);
      params.DirtyRectsCount = rectsCount;
      params.pDirtyRects = rects.data();

      hr = mSwapChain->Present1(0, flags, &params);
      if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) {
        auto* device = mDCLayerTree->GetDevice();
        auto reason = device->GetDeviceRemovedReason();
        gfxCriticalNote << "Present1 failed: " << gfx::hexa(hr) << " reason "
                        << gfx::hexa(reason);
      }
    }
  } else {
    mSwapChain->Present(0, flags);
  }

  if (mFirstPresent) {
    mFirstPresent = false;

    // Wait for the GPU to finish executing its commands before
    // committing the DirectComposition tree, or else the swapchain
    // may flicker black when it's first presented.
    auto* device = mDCLayerTree->GetDevice();
    RefPtr<IDXGIDevice2> dxgiDevice2;
    device->QueryInterface((IDXGIDevice2**)getter_AddRefs(dxgiDevice2));
    MOZ_ASSERT(dxgiDevice2);

    HANDLE event = ::CreateEvent(nullptr, false, false, nullptr);
    hr = dxgiDevice2->EnqueueSetEvent(event);
    if (SUCCEEDED(hr)) {
      DebugOnly<DWORD> result = ::WaitForSingleObject(event, INFINITE);
      MOZ_ASSERT(result == WAIT_OBJECT_0);
    } else {
      gfxCriticalNoteOnce << "EnqueueSetEvent failed: " << gfx::hexa(hr);
    }
    ::CloseHandle(event);
  }
}

DCLayerCompositionSurface::DCLayerCompositionSurface(wr::DeviceIntSize aSize,
                                                     bool aIsOpaque,
                                                     DCLayerTree* aDCLayerTree)
    : DCLayerSurface(aIsOpaque, aDCLayerTree), mSize(aSize) {}

DCLayerCompositionSurface::~DCLayerCompositionSurface() {
  if (mEGLSurface) {
    const auto gl = mDCLayerTree->GetGLContext();
    const auto& gle = gl::GLContextEGL::Cast(gl);
    const auto& egl = gle->mEgl;

    egl->fDestroySurface(mEGLSurface);
    mEGLSurface = EGL_NO_SURFACE;
  }
}

bool DCLayerCompositionSurface::Initialize() {
  DCSurface::Initialize();

  if (!Resize(mSize)) {
    return false;
  }
  return true;
}

void DCLayerCompositionSurface::Bind(const wr::DeviceIntRect* aDirtyRects,
                                     size_t aNumDirtyRects) {
  MOZ_ASSERT(mCompositionSurface);

  if (!mCompositionSurface) {
    return;
  }

  RefPtr<ID3D11Texture2D> backBuffer;
  POINT offset;
  HRESULT hr;

  RECT updateRect;
  gfx::IntPoint updatePos;
  if (aNumDirtyRects > 0) {
    MOZ_ASSERT(!mFirstDraw);
    MOZ_ASSERT(aNumDirtyRects == 1);

    updateRect.left = std::clamp(aDirtyRects[0].min.x, 0, mSize.width);
    updateRect.top = std::clamp(aDirtyRects[0].min.y, 0, mSize.height);
    updateRect.right = std::clamp(aDirtyRects[0].max.x, 0, mSize.width);
    updateRect.bottom = std::clamp(aDirtyRects[0].max.y, 0, mSize.height);

    updatePos = {updateRect.left, updateRect.top};
  } else {
    updateRect.left = 0;
    updateRect.top = 0;
    updateRect.right = mSize.width;
    updateRect.bottom = mSize.height;

    updatePos = {0, 0};
  }

  mFirstDraw = false;
  LayoutDeviceIntRect rect = widget::WinUtils::ToIntRect(updateRect);
  MOZ_ASSERT(!rect.IsEmpty());

  hr = mCompositionSurface->BeginDraw(&updateRect, __uuidof(ID3D11Texture2D),
                                      (void**)getter_AddRefs(backBuffer),
                                      &offset);

  if (FAILED(hr)) {
    gfxCriticalNote << "DCLayerCompositionSurface::Bind failed: "
                    << gfx::hexa(hr) << " " << rect;
    RenderThread::Get()->HandleWebRenderError(WebRenderError::BEGIN_DRAW);
    return;
  }

  const auto gl = mDCLayerTree->GetGLContext();
  const auto& gle = gl::GLContextEGL::Cast(gl);
  const auto& egl = gle->mEgl;

  gfx::IntPoint originOffset = {(int)offset.x - updatePos.x,
                                (int)offset.y - updatePos.y};
  const EGLint pbuffer_attribs[]{LOCAL_EGL_WIDTH,
                                 mSize.width,
                                 LOCAL_EGL_HEIGHT,
                                 mSize.height,
                                 LOCAL_EGL_TEXTURE_OFFSET_X_ANGLE,
                                 originOffset.x,
                                 LOCAL_EGL_TEXTURE_OFFSET_Y_ANGLE,
                                 originOffset.y,
                                 LOCAL_EGL_NONE};
  const auto buffer = reinterpret_cast<EGLClientBuffer>(backBuffer.get());
  EGLConfig eglConfig = mDCLayerTree->GetEGLConfig();

  mEGLSurface = egl->fCreatePbufferFromClientBuffer(
      LOCAL_EGL_D3D_TEXTURE_ANGLE, buffer, eglConfig, pbuffer_attribs);
  if (!mEGLSurface) {
    EGLint err = egl->mLib->fGetError();
    gfxCriticalNote << "Failed to create Pbuffer error: " << gfx::hexa(err)
                    << " Size : "
                    << LayoutDeviceIntSize(mSize.width, mSize.height);
    return;
  }

  gle->SetEGLSurfaceOverride(mEGLSurface);
}

bool DCLayerCompositionSurface::Resize(wr::DeviceIntSize aSize) {
  MOZ_ASSERT(mEGLSurface == EGL_NO_SURFACE);

  if (mSize.width == 0 || mSize.height == 0) {
    MOZ_ASSERT_UNREACHABLE("unexpected to be called");
    return false;
  }

  HRESULT hr;
  auto* dcompDevice = mDCLayerTree->GetCompositionDevice();
  const auto alphaMode =
      mIsOpaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;

  RefPtr<IDCompositionSurface> surface;
  hr = dcompDevice->CreateSurface(aSize.width, aSize.height,
                                  DXGI_FORMAT_R8G8B8A8_UNORM, alphaMode,
                                  getter_AddRefs(surface));
  if (FAILED(hr)) {
    gfxCriticalNote << "Failed to create DCompositionSurface: "
                    << gfx::hexa(hr);
    return false;
  }

  hr = mContentVisual->SetContent(surface);
  if (FAILED(hr)) {
    gfxCriticalNote << "Failed to SetContent: " << gfx::hexa(hr);
    return false;
  }

  mCompositionSurface = surface;
  mSize = aSize;
  mFirstDraw = true;
  return true;
}

void DCLayerCompositionSurface::Present(const wr::DeviceIntRect* aDirtyRects,
                                        size_t aNumDirtyRects) {
  MOZ_ASSERT(mEGLSurface);
  MOZ_ASSERT(mCompositionSurface);

  mDCSurfaceData = Nothing();

  if (!mCompositionSurface) {
    return;
  }

  mCompositionSurface->EndDraw();

  if (!mEGLSurface) {
    return;
  }

  const auto gl = mDCLayerTree->GetGLContext();
  const auto& gle = gl::GLContextEGL::Cast(gl);
  const auto& egl = gle->mEgl;

  gle->SetEGLSurfaceOverride(EGL_NO_SURFACE);

  egl->fDestroySurface(mEGLSurface);
  mEGLSurface = EGL_NO_SURFACE;
}

DCSurfaceDCompositionTextureOverlay::DCSurfaceDCompositionTextureOverlay(
    bool aIsOpaque, DCLayerTree* aDCLayerTree)
    : DCSurface(aIsOpaque, aDCLayerTree) {}

DCSurfaceDCompositionTextureOverlay::~DCSurfaceDCompositionTextureOverlay() {}

void DCSurfaceDCompositionTextureOverlay::AttachExternalImage(
    wr::ExternalImageId aExternalImage) {
  auto* texture = RenderThread::Get()->GetRenderTexture(aExternalImage);
  if (!texture) {
    return;
  }
  mRenderTextureHost = texture;
}

void DCSurfaceDCompositionTextureOverlay::Present() {
  if (!mRenderTextureHost) {
    return;
  }

  // Content is not updated
  if (mPrevRenderTextureHost == mRenderTextureHost) {
    return;
  }

  const auto textureHost = mRenderTextureHost->AsRenderDXGITextureHost();

  auto start = TimeStamp::Now();
  RefPtr<IDCompositionTexture> dcompTexture =
      textureHost->GetDCompositionTexture();
  auto end = TimeStamp::Now();
  if (!dcompTexture) {
    gfxCriticalNote << "Failed to get DCompTexture";
    RenderThread::Get()->NotifyWebRenderError(
        WebRenderError::DCOMP_TEXTURE_OVERLAY);
    return;
  }

  const auto maxGetWaitDurationMs = 2;
  const auto maxSlowGetCount = 5;

  const auto getDurationMs =
      static_cast<uint32_t>((end - start).ToMilliseconds());

  if (getDurationMs > maxGetWaitDurationMs) {
    mSlowGetCount++;
  } else {
    mSlowGetCount = 0;
  }

  if (mSlowGetCount > maxSlowGetCount) {
    RenderThread::Get()->NotifyWebRenderError(
        WebRenderError::DCOMP_TEXTURE_OVERLAY);
  }

  const auto alphaMode =
      mIsOpaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
  dcompTexture->SetAlphaMode(alphaMode);
  //  XXX
  //  dcompTexture->SetColorSpace();

  mContentVisual->SetContent(dcompTexture);
  mPrevRenderTextureHost = mRenderTextureHost;
  mDCLayerTree->SetPendingCommit();
}

DCSurfaceVideo::DCSurfaceVideo(bool aIsOpaque, DCLayerTree* aDCLayerTree)
    : DCSurface(aIsOpaque, aDCLayerTree),
      mSwapChainBufferCount(
          StaticPrefs::gfx_webrender_dcomp_video_force_triple_buffering() ? 3
                                                                          : 2) {
}

DCSurfaceVideo::~DCSurfaceVideo() {
  ReleaseDecodeSwapChainResources();
  MOZ_ASSERT(!mSwapChainSurfaceHandle);
}

bool IsYUVSwapChainFormat(DXGI_FORMAT aFormat) {
  switch (aFormat) {
    case DXGI_FORMAT_P010:
    case DXGI_FORMAT_P016:
    case DXGI_FORMAT_NV12:
    case DXGI_FORMAT_YUY2:
      return true;
    default:
      return false;
  }
}

void DCSurfaceVideo::AttachExternalImage(wr::ExternalImageId aExternalImage) {
  auto [texture, usageInfo] =
      RenderThread::Get()->GetRenderTextureAndUsageInfo(aExternalImage);
  if (!texture) {
    gfxCriticalNoteOnce << "Failed to attach ExternalImage for extId:"
                        << AsUint64(aExternalImage);
    mRenderTextureHost = nullptr;
    return;
  }

  if (usageInfo) {
    mRenderTextureHostUsageInfo = usageInfo;
  }

  if (mPrevTexture == texture) {
    return;
  }

  // If the content is HDR, we will want to use more than 8bit. A high bit-depth
  // pixel format alone is not sufficient — 10-bit SDR content (e.g. BT.2020
  // with a non-HDR transfer function) must not be treated as HDR, otherwise
  // the compositor applies HDR tone mapping to SDR content and corrupts the
  // image. Check both the pixel format and the transfer function.
  mContentIsHDR = false;
  if (texture) {
    const auto format = texture->GetFormat();
    nsPrintfCString str("AttachExternalImage: SurfaceFormat %d", (int)format);
    PROFILER_MARKER_TEXT("DCSurfaceVideo", GRAPHICS, {}, str);
    switch (format) {
      case gfx::SurfaceFormat::R10G10B10A2_UINT32:
      case gfx::SurfaceFormat::R10G10B10X2_UINT32:
      case gfx::SurfaceFormat::R16G16B16A16F:
      case gfx::SurfaceFormat::P010:
      case gfx::SurfaceFormat::P016: {
        const auto* dxgiTexture = texture->AsRenderDXGITextureHost();
        mContentIsHDR = dxgiTexture && gfx::IsHDRTransferFunction(
                                           dxgiTexture->GetTransferFunction());
        break;
      }
      default:
        break;
    }
  }

  // XXX if software decoded video frame format is nv12, it could be used as
  // video overlay.
  if (!texture || !texture->AsRenderDXGITextureHost() ||
      ((texture->GetFormat() != gfx::SurfaceFormat::NV12) &&
       (texture->GetFormat() != gfx::SurfaceFormat::P010) &&
       (texture->GetFormat() != gfx::SurfaceFormat::P016))) {
    gfxCriticalNote << "Unsupported RenderTexture for overlay: "
                    << gfx::hexa(texture);
    return;
  }

  mRenderTextureHost = texture;
}

bool DCSurfaceVideo::CalculateSwapChainSize(gfx::Matrix& aTransform) {
  if (!mRenderTextureHost) {
    MOZ_ASSERT_UNREACHABLE("unexpected to be called");
    return false;
  }

  const auto overlayType = mRenderTextureHost->IsSoftwareDecodedVideo()
                               ? DCompOverlayTypes::SOFTWARE_DECODED_VIDEO
                               : DCompOverlayTypes::HARDWARE_DECODED_VIDEO;
  mDCLayerTree->SetUsedOverlayTypeInFrame(overlayType);

  mVideoSize = mRenderTextureHost->AsRenderDXGITextureHost()->GetSize(0);

  // When RenderTextureHost, swapChainSize or VideoSwapChain are updated,
  // DCSurfaceVideo::PresentVideo() needs to be called.
  bool needsToPresent = mPrevTexture != mRenderTextureHost;
  gfx::IntSize swapChainSize = mVideoSize;
  gfx::Matrix transform = aTransform;
  const bool isDRM = mRenderTextureHost->IsFromDRMSource();

  // When video is rendered to axis aligned integer rectangle, video scaling
  // could be done by VideoProcessor
  bool scaleVideoAtVideoProcessor = false;
  if (StaticPrefs::gfx_webrender_dcomp_video_vp_scaling_win_AtStartup() &&
      aTransform.PreservesAxisAlignedRectangles() &&
      !aTransform.HasNegativeScaling()) {
    gfx::Size scaledSize = gfx::Size(mVideoSize) * aTransform.ScaleFactors();
    gfx::IntSize size(int32_t(std::round(scaledSize.width)),
                      int32_t(std::round(scaledSize.height)));
    if (gfx::FuzzyEqual(scaledSize.width, size.width, 0.1f) &&
        gfx::FuzzyEqual(scaledSize.height, size.height, 0.1f)) {
      scaleVideoAtVideoProcessor = true;
      swapChainSize = size;
    }
  }

  if (scaleVideoAtVideoProcessor) {
    // 4:2:2 subsampled formats like YUY2 must have an even width, and 4:2:0
    // subsampled formats like NV12 must have an even width and height.
    if (swapChainSize.width % 2 == 1) {
      swapChainSize.width += 1;
    }
    if (swapChainSize.height % 2 == 1) {
      swapChainSize.height += 1;
    }
    transform = gfx::Matrix::Translation(aTransform.GetTranslation());
  }

  if (!mDCLayerTree->EnsureVideoProcessor(mVideoSize, swapChainSize)) {
    gfxCriticalNote << "EnsureVideoProcessor Failed";
    return false;
  }

  MOZ_ASSERT(mDCLayerTree->GetVideoContext());
  MOZ_ASSERT(mDCLayerTree->GetVideoProcessor());

  const UINT vendorId = GetVendorId(mDCLayerTree->GetVideoDevice());
  const bool driverSupportsAutoHDR =
      GetVpAutoHDRSupported(vendorId, mDCLayerTree->GetVideoContext(),
                            mDCLayerTree->GetVideoProcessor());
  const bool contentIsHDR = mContentIsHDR;
  const bool monitorIsHDR =
      gfx::DeviceManagerDx::Get()->WindowHDREnabled(mDCLayerTree->GetHwnd());
  const bool powerIsCharging = RenderThread::Get()->GetPowerIsCharging();

  bool useVpAutoHDR = gfx::gfxVars::WebRenderOverlayVpAutoHDR() &&
                      !contentIsHDR && monitorIsHDR && driverSupportsAutoHDR &&
                      powerIsCharging && !mVpAutoHDRFailed;

  bool useHDR = gfx::gfxVars::WebRenderOverlayHDR() && contentIsHDR;
  // We can rely on the Desktop Window Manager (DWM) to handle RGB10A2 format
  // swapchains with BT2100 PQ color space, even if hardware overlays are not
  // supported it will convert for us, it also guarantees support for scRGB in
  // RGBA16F format but that uses twice the bandwidth and VideoProcessor is not
  // required to implement conversion from YCBCR BT2100 PQ to scRGB RGBA16F, so
  // some drivers can't do that and we should stick to RGB10A2 where possible.
  bool useHDRRGB10A2 = useHDR;
  bool useHDRRGBA16F = false;

  if (profiler_thread_is_being_profiled_for_markers()) {
    nsPrintfCString str(
        "useVpAutoHDR %d gfxVars %d contentIsHDR %d monitor %d driver %d "
        "charging %d failed %d",
        useVpAutoHDR, gfx::gfxVars::WebRenderOverlayVpAutoHDR(), contentIsHDR,
        monitorIsHDR, driverSupportsAutoHDR, powerIsCharging, mVpAutoHDRFailed);
    PROFILER_MARKER_TEXT("DCSurfaceVideo", GRAPHICS, {}, str);
  }

  if (!mVideoSwapChain || mSwapChainSize != swapChainSize || mIsDRM != isDRM ||
      mUseVpAutoHDR != useVpAutoHDR) {
    needsToPresent = true;
    ReleaseDecodeSwapChainResources();
    // Update mSwapChainSize before creating SwapChain
    mSwapChainSize = swapChainSize;
    mIsDRM = isDRM;

    auto swapChainFormat =
        GetSwapChainFormat(useVpAutoHDR, useHDRRGB10A2, useHDRRGBA16F);
    bool useYUVSwapChain = IsYUVSwapChainFormat(swapChainFormat);
    if (useYUVSwapChain) {
      // Tries to create YUV SwapChain
      nsPrintfCString str("Creating video swapchain for YUV as DXGI format %d",
                          (int)swapChainFormat);
      PROFILER_MARKER_TEXT("DCSurfaceVideo", GRAPHICS, {}, str);
      CreateVideoSwapChain(swapChainFormat);
      if (!mVideoSwapChain) {
        mFailedYuvSwapChain = true;
        ReleaseDecodeSwapChainResources();

        gfxCriticalNote << "Fallback to RGB SwapChain";
      }
    }
    // Tries to create RGB SwapChain
    if (!mVideoSwapChain) {
      nsPrintfCString str("Creating video swapchain for RGB as DXGI format %d",
                          (int)swapChainFormat);
      PROFILER_MARKER_TEXT("DCSurfaceVideo", GRAPHICS, {}, str);
      CreateVideoSwapChain(swapChainFormat);
    }
    if (!mVideoSwapChain && useVpAutoHDR) {
      mVpAutoHDRFailed = true;
      gfxCriticalNoteOnce << "Failed to create video SwapChain for VpAutoHDR";

      // Disable VpAutoHDR
      useVpAutoHDR = false;
      swapChainFormat =
          GetSwapChainFormat(useVpAutoHDR, useHDRRGB10A2, useHDRRGBA16F);
      nsPrintfCString str(
          "Creating video swapchain for RGB as DXGI format %d after fallback "
          "from VpAutoHDR",
          (int)swapChainFormat);
      PROFILER_MARKER_TEXT("DCSurfaceVideo", GRAPHICS, {}, str);
      CreateVideoSwapChain(swapChainFormat);
    }
  }

  aTransform = transform;
  mUseVpAutoHDR = useVpAutoHDR;
  mUseHDR = useHDR;

  return needsToPresent;
}

void DCSurfaceVideo::PresentVideo() {
  if (!mRenderTextureHost) {
    return;
  }

  if (!mVideoSwapChain) {
    gfxCriticalNote << "Failed to create VideoSwapChain";
    RenderThread::Get()->NotifyWebRenderError(
        wr::WebRenderError::VIDEO_OVERLAY);
    return;
  }

  if (!CallVideoProcessorBlt()) {
    bool useYUVSwapChain = IsYUVSwapChainFormat(mSwapChainFormat);
    if (useYUVSwapChain) {
      mFailedYuvSwapChain = true;
      ReleaseDecodeSwapChainResources();
      return;
    }
    RenderThread::Get()->NotifyWebRenderError(
        wr::WebRenderError::VIDEO_OVERLAY);
    return;
  }

  const auto device = mDCLayerTree->GetDevice();
  HRESULT hr;

  auto start = TimeStamp::Now();
  if (mFirstPresent) {
    mFirstPresent = false;
    UINT flags = DXGI_PRESENT_USE_DURATION;
    // DirectComposition can display black for a swap chain between the first
    // and second time it's presented to - maybe the first Present can get lost
    // somehow and it shows the wrong buffer. In that case copy the buffers so
    // all have the correct contents, which seems to help. The first Present()
    // after this needs to have SyncInterval > 0, or else the workaround doesn't
    // help.
    for (size_t i = 0; i < mSwapChainBufferCount - 1; ++i) {
      hr = mVideoSwapChain->Present(0, flags);
      // Ignore DXGI_STATUS_OCCLUDED since that's not an error but only
      // indicates that the window is occluded and we can stop rendering.
      if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) {
        gfxCriticalNoteOnce << "video Present failed during first present: "
                            << gfx::hexa(hr);
        return;
      }

      RefPtr<ID3D11Texture2D> destTexture;
      mVideoSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
                                 (void**)getter_AddRefs(destTexture));
      MOZ_ASSERT(destTexture);
      RefPtr<ID3D11Texture2D> srcTexture;
      hr = mVideoSwapChain->GetBuffer(1, __uuidof(ID3D11Texture2D),
                                      (void**)getter_AddRefs(srcTexture));
      MOZ_ASSERT(srcTexture);
      RefPtr<ID3D11DeviceContext> context;
      device->GetImmediateContext(getter_AddRefs(context));
      MOZ_ASSERT(context);
      context->CopyResource(destTexture, srcTexture);
    }

    // Additionally wait for the GPU to finish executing its commands, or
    // there still may be a black flicker when presenting expensive content
    // (e.g. 4k video).

    RefPtr<IDXGIDevice2> dxgiDevice2;
    device->QueryInterface((IDXGIDevice2**)getter_AddRefs(dxgiDevice2));
    MOZ_ASSERT(dxgiDevice2);

    HANDLE event = ::CreateEvent(nullptr, false, false, nullptr);
    hr = dxgiDevice2->EnqueueSetEvent(event);
    if (SUCCEEDED(hr)) {
      DebugOnly<DWORD> result = ::WaitForSingleObject(event, INFINITE);
      MOZ_ASSERT(result == WAIT_OBJECT_0);
    } else {
      gfxCriticalNoteOnce << "EnqueueSetEvent failed: " << gfx::hexa(hr);
    }
    ::CloseHandle(event);
  }

  UINT flags = DXGI_PRESENT_USE_DURATION;
  UINT interval = 1;
  if (StaticPrefs::gfx_webrender_dcomp_video_swap_chain_present_interval_0()) {
    interval = 0;
  }

  hr = mVideoSwapChain->Present(interval, flags);
  auto end = TimeStamp::Now();

  if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) {
    gfxCriticalNoteOnce << "video Present failed: " << gfx::hexa(hr);
  }

  mPrevTexture = mRenderTextureHost;

  // Disable video overlay if mVideoSwapChain->Present() is too slow. It drops
  // fps.

  if (!StaticPrefs::gfx_webrender_dcomp_video_check_slow_present()) {
    return;
  }

  const auto presentDurationMs =
      static_cast<uint32_t>((end - start).ToMilliseconds());
  const auto overlayType = mRenderTextureHost->IsSoftwareDecodedVideo()
                               ? DCompOverlayTypes::SOFTWARE_DECODED_VIDEO
                               : DCompOverlayTypes::HARDWARE_DECODED_VIDEO;

  nsPrintfCString marker("PresentWait overlay %u %ums ", (uint8_t)overlayType,
                         presentDurationMs);
  PROFILER_MARKER_TEXT("PresentWait", GRAPHICS, {}, marker);

  // RenderTextureHostUsageInfo::OnVideoPresent() is called to disable video
  // overlay if present is slow. However, HDR requires video overlay to show
  // correct colors. To avoid displaying incorrect color, don't make this call
  // if the content is HDR.
  if (!mContentIsHDR && mRenderTextureHostUsageInfo) {
    mRenderTextureHostUsageInfo->OnVideoPresent(mDCLayerTree->GetFrameId(),
                                                presentDurationMs);
  }
}

void DCSurfaceVideo::OnCompositorEndFrame(int aFrameId, uint32_t aDurationMs) {
  if (!mRenderTextureHostUsageInfo) {
    return;
  }
  mRenderTextureHostUsageInfo->OnCompositorEndFrame(aFrameId, aDurationMs);
}

DXGI_FORMAT DCSurfaceVideo::GetSwapChainFormat(bool aUseVpAutoHDR,
                                               bool aUseRGB10A2,
                                               bool aUseRGBA16F) {
  if (aUseVpAutoHDR) {
    return DXGI_FORMAT_R16G16B16A16_FLOAT;
  }
  if (aUseRGB10A2) {
    return DXGI_FORMAT_R10G10B10A2_UNORM;
  }
  if (aUseRGBA16F) {
    return DXGI_FORMAT_R16G16B16A16_FLOAT;
  }
  if (mFailedYuvSwapChain || !mDCLayerTree->SupportsHardwareOverlays()) {
    return DXGI_FORMAT_B8G8R8A8_UNORM;
  }
  return mDCLayerTree->GetOverlayFormatForSDR();
}

bool DCSurfaceVideo::CreateVideoSwapChain(DXGI_FORMAT aSwapChainFormat) {
  MOZ_ASSERT(mRenderTextureHost);

  mFirstPresent = true;

  const auto device = mDCLayerTree->GetDevice();

  RefPtr<IDXGIDevice> dxgiDevice;
  device->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice));

  RefPtr<IDXGIFactoryMedia> dxgiFactoryMedia;
  {
    RefPtr<IDXGIAdapter> adapter;
    dxgiDevice->GetAdapter(getter_AddRefs(adapter));
    adapter->GetParent(
        IID_PPV_ARGS((IDXGIFactoryMedia**)getter_AddRefs(dxgiFactoryMedia)));
  }

  mSwapChainSurfaceHandle = gfx::DeviceManagerDx::CreateDCompSurfaceHandle();
  if (!mSwapChainSurfaceHandle) {
    gfxCriticalNote << "Failed to create DCompSurfaceHandle";
    return false;
  }

  DXGI_SWAP_CHAIN_DESC1 desc = {};
  desc.Width = mSwapChainSize.width;
  desc.Height = mSwapChainSize.height;
  desc.Format = aSwapChainFormat;
  desc.Stereo = FALSE;
  desc.SampleDesc.Count = 1;
  desc.BufferCount = mSwapChainBufferCount;
  desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
  desc.Scaling = DXGI_SCALING_STRETCH;
  desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
  desc.Flags = DXGI_SWAP_CHAIN_FLAG_FULLSCREEN_VIDEO;
  if (IsYUVSwapChainFormat(aSwapChainFormat)) {
    desc.Flags |= DXGI_SWAP_CHAIN_FLAG_YUV_VIDEO;
  }
  if (mIsDRM) {
    desc.Flags |= DXGI_SWAP_CHAIN_FLAG_DISPLAY_ONLY;
  }
  desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;

  HRESULT hr;
  hr = dxgiFactoryMedia->CreateSwapChainForCompositionSurfaceHandle(
      device, mSwapChainSurfaceHandle, &desc, nullptr,
      getter_AddRefs(mVideoSwapChain));

  if (FAILED(hr)) {
    gfxCriticalNote << "Failed to create video SwapChain: " << gfx::hexa(hr)
                    << " " << mSwapChainSize;
    return false;
  }

  mSwapChainFormat = aSwapChainFormat;
  mContentVisual->SetContent(mVideoSwapChain);
  return true;
}

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 Maybe<DXGI_COLOR_SPACE_TYPE> GetOutputDXGIColorSpace(
    DXGI_FORMAT aSwapChainFormat, DXGI_COLOR_SPACE_TYPE aInputColorSpace,
    bool aUseVpAutoHDR) {
  switch (aSwapChainFormat) {
    case DXGI_FORMAT_NV12:
    case DXGI_FORMAT_YUY2:
      return Some(aInputColorSpace);
    case DXGI_FORMAT_P010:
    case DXGI_FORMAT_P016:
      return Some(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
    case DXGI_FORMAT_R16G16B16A16_FLOAT:
      return Some(DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709);
    case DXGI_FORMAT_R10G10B10A2_UNORM:
      switch (aInputColorSpace) {
        case DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020:
          return Some(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
        case DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020:
          return Some(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
        case DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020:
          return Some(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
        default:
          return Some(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020);
      }
    case DXGI_FORMAT_R8G8B8A8_UNORM:
    case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
    case DXGI_FORMAT_B8G8R8A8_UNORM:
    case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
    case DXGI_FORMAT_B8G8R8X8_UNORM:
    case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
      // Refactor note - not sure if mUseVpAutoHDR is ever true here,
      // it may only ever use DXGI_FORMAT_R16G16B16A16_FLOAT.
      if (aUseVpAutoHDR) {
        return Some(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
      }
      return Some(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
    default:
      return Nothing();
  }
}

static DXGI_HDR_METADATA_HDR10 ToStreamHDR10Metadata(
    const gfx::HDRMetadata& aMetadata) {
  constexpr float kChromaticityScale = 50000.0f;
  constexpr float kMinLuminanceScale = 10000.0f;
  DXGI_HDR_METADATA_HDR10 hdr10 = {};
  if (const auto& smpte = aMetadata.mSmpte2086) {
    hdr10.RedPrimary[0] =
        static_cast<UINT16>(smpte->displayPrimaryRed.x * kChromaticityScale);
    hdr10.RedPrimary[1] =
        static_cast<UINT16>(smpte->displayPrimaryRed.y * kChromaticityScale);
    hdr10.GreenPrimary[0] =
        static_cast<UINT16>(smpte->displayPrimaryGreen.x * kChromaticityScale);
    hdr10.GreenPrimary[1] =
        static_cast<UINT16>(smpte->displayPrimaryGreen.y * kChromaticityScale);
    hdr10.BluePrimary[0] =
        static_cast<UINT16>(smpte->displayPrimaryBlue.x * kChromaticityScale);
    hdr10.BluePrimary[1] =
        static_cast<UINT16>(smpte->displayPrimaryBlue.y * kChromaticityScale);
    hdr10.WhitePoint[0] =
        static_cast<UINT16>(smpte->whitePoint.x * kChromaticityScale);
    hdr10.WhitePoint[1] =
        static_cast<UINT16>(smpte->whitePoint.y * kChromaticityScale);
    hdr10.MaxMasteringLuminance = static_cast<UINT>(smpte->maxLuminance);
    hdr10.MinMasteringLuminance =
        static_cast<UINT>(smpte->minLuminance * kMinLuminanceScale);
  }
  if (const auto& cll = aMetadata.mContentLightLevel) {
    hdr10.MaxContentLightLevel = cll->maxContentLightLevel;
    hdr10.MaxFrameAverageLightLevel = cll->maxFrameAverageLightLevel;
  }
  return hdr10;
}

static const char kShaderBltYUVHLGToRGBPQ_VS[] = R"(
  struct VS_INPUT {
    float4 position : POSITION;
    float2 texCoord : TEXCOORD0;
  };

  struct PS_INPUT {
    float4 position : SV_POSITION;
    float2 texCoord : TEXCOORD0;
  };

  PS_INPUT main(VS_INPUT input) {
    PS_INPUT output;
    output.position = input.position;
    output.texCoord = input.texCoord;
    return output;
  }
)";

static constexpr uint32_t MODE_BT709 = 0x00;
static constexpr uint32_t MODE_PQ = 0x01;
static constexpr uint32_t MODE_HLG = 0x02;

struct psConstants {
  // Converts from encoded YUV to encoded RGB (same transfer function).
  color::mat4 yuvToRgbMatrix;
  // Converts from one color space to another (e.g. BT.2020 to BT.709).
  color::mat4 colorSpaceMatrix;
  // Tonemapping parameters for Reinhard tonemapping curve.
  // Based on equation 4 in
  // "Photographic Tone Reproduction for Digital Images" by Reinhard et al
  // https://www-old.cs.utah.edu/docs/techreports/2002/pdf/UUCS-02-001.pdf
  float tonemapping[2];
  // Conversion factor between display-referred HLG and scene-referred PQ.
  float pqMultiplier;
  // Which conversion mode to use, see MODE_* enum values above.
  uint32_t inputMode;
  uint32_t outputMode;
  // align the size of this struct to 16 bytes for HLSL constant buffer packing
  // rules.
  uint32_t padding[3];
};

static const char kShaderBltYUVHLGToRGBPQ_PS[] = R"(
  Texture2D texY : register(t0);
  Texture2D texUV : register(t1);
  SamplerState samplerLinear : register(s0);

  static const uint MODE_BT709 = 0;
  static const uint MODE_PQ = 1;
  static const uint MODE_HLG = 2;

  cbuffer Constants : register(b0) {
    float4x4 yuvToRgbMatrix;
    float4x4 colorSpaceMatrix;
    float2 tonemapping;
    float pqMultiplier;
    uint inputMode;
    uint outputMode;
  };

  struct PS_INPUT {
    float4 position : SV_POSITION;
    float2 texCoord : TEXCOORD0;
  };

  float3 BT709toLinear(float3 rgb) {
    // EOTF for BT.709
    return lerp((rgb / 4.5f), pow((rgb + 0.099f) / 1.099f, 1.0f / 0.45f), step(0.081f, rgb));
  }

  float3 HLGtoLinear(float3 rgb) {
    // EOTF for HLG (ITU-R BT.2100)
    const float a = 0.17883277f;
    const float b = 0.28466892f;
    const float c = 0.55991073f;

    return lerp((rgb * rgb) * 4.0f, exp((rgb - c) / a) + b, step(0.5f, rgb));
  }

  float3 PQToLinear(float3 rgb) {
    // EOTF for PQ (ST.2084)
    const float m1 = 0.1593017578f;
    const float m2 = 78.84375f;
    const float c1 = 0.8359375f;
    const float c2 = 18.8515625f;
    const float c3 = 18.6875f;

    float3 y = pow(max(rgb, 0.0f), 1.0f / m2);
    float3 l = pow(max((y - c1) / (c2 - c3 * y), 0.0f), 1.0f / m1) * (1.0f / pqMultiplier);
    return l;
  }

  float3 LinearToPQ(float3 rgb) {
    // OETF / Inverse EOTF for PQ (ST.2084)
    const float m1 = 0.1593017578f;
    const float m2 = 78.84375f;
    const float c1 = 0.8359375f;
    const float c2 = 18.8515625f;
    const float c3 = 18.6875f;

    float3 y = pow(max(rgb, 0.0f) * pqMultiplier, m1);
    float3 pq = pow((c1 + c2 * y) / (1.0f + c3 * y), m2);
    return pq;
  }

  float3 LinearToBT709(float3 rgb) {
    // EOTF for BT.709
    return lerp((rgb * 4.5f), 1.099f * pow(rgb, 0.45f) - 0.099f, step(0.018f, rgb));
  }

  float3 tonemap(float3 color, float a, float b) {
    float m = max(color.x, max(color.y, color.z));
    return color * (1.0f + a * m) / (1.0f + b * m);
  }

  float4 main(PS_INPUT input) : SV_TARGET {
    float2 uv = input.texCoord;

    float y = texY.Sample(samplerLinear, uv).x;
    float2 chroma = texUV.Sample(samplerLinear, uv).xy;

    float3 srcYUV = float3(y, chroma.x, chroma.y);

    float3 srcRGB = mul(float4(srcYUV, 1.0), yuvToRgbMatrix).xyz;
    // Clamp to avoid negative values for narrow range YUV to RGB conversion.
    srcRGB = max(srcRGB, 0.0f);

    float3 srcLinearRGB;
    switch(inputMode) {
      default:
      case MODE_BT709:
        srcLinearRGB = BT709toLinear(srcRGB);
        break;
      case MODE_HLG:
        srcLinearRGB = HLGtoLinear(srcRGB);
        break;
      case MODE_PQ:
        srcLinearRGB = PQToLinear(srcRGB);
        break;
    }

    float3 dstLinearRGB = mul(colorSpaceMatrix, float4(srcLinearRGB, 1.0)).xyz;

    float3 dstTonemappedRGB = tonemap(dstLinearRGB, tonemapping.x, tonemapping.y);

    float3 dstRGB;
    switch(outputMode) {
      default:
      case MODE_PQ:
        dstRGB = LinearToPQ(dstTonemappedRGB);
        break;
      case MODE_BT709:
        dstRGB = LinearToBT709(dstTonemappedRGB);
        break;
    }

    return float4(dstRGB, 1.0f);
  }
)";

bool DCSurfaceVideo::ShaderBltSetup() {
  // Compile the pixel shader
  HRESULT hr;
  const auto device = mDCLayerTree->GetDevice();
  if (!mShaderBltVSBlob) {
    RefPtr<ID3DBlob> errorBlob;
    hr = D3DCompile(
        kShaderBltYUVHLGToRGBPQ_VS, strlen(kShaderBltYUVHLGToRGBPQ_VS), nullptr,
        nullptr, nullptr, "main", "vs_5_0", 0, 0,
        getter_AddRefs(mShaderBltVSBlob), getter_AddRefs(errorBlob));
    if (FAILED(hr)) {
      if (errorBlob) {
        gfxCriticalNoteOnce
            << "Vertex shader compilation error: "
            << static_cast<const char*>(errorBlob->GetBufferPointer());
      } else {
        gfxCriticalNoteOnce << "Failed to compile vertex shader: hresult="
                            << gfx::hexa(hr);
      }
      return false;
    }
    hr = device->CreateVertexShader(mShaderBltVSBlob->GetBufferPointer(),
                                    mShaderBltVSBlob->GetBufferSize(), nullptr,
                                    getter_AddRefs(mShaderBltVertexShader));
    if (FAILED(hr)) {
      gfxCriticalNoteOnce << "Failed to create vertex shader: hresult="
                          << gfx::hexa(hr);
      return false;
    }
  }

  if (!mShaderBltPSBlob) {
    RefPtr<ID3DBlob> errorBlob;
    hr = D3DCompile(
        kShaderBltYUVHLGToRGBPQ_PS, strlen(kShaderBltYUVHLGToRGBPQ_PS), nullptr,
        nullptr, nullptr, "main", "ps_5_0", 0, 0,
        getter_AddRefs(mShaderBltPSBlob), getter_AddRefs(errorBlob));
    if (FAILED(hr)) {
      if (errorBlob) {
        gfxCriticalNoteOnce
            << "Pixel shader compilation error: "
            << static_cast<const char*>(errorBlob->GetBufferPointer());
      } else {
        gfxCriticalNoteOnce << "Failed to compile pixel shader: hresult="
                            << gfx::hexa(hr);
      }
      return false;
    }
    hr = device->CreatePixelShader(mShaderBltPSBlob->GetBufferPointer(),
                                   mShaderBltPSBlob->GetBufferSize(), nullptr,
                                   getter_AddRefs(mShaderBltPixelShader));
    if (FAILED(hr)) {
      gfxCriticalNoteOnce << "Failed to create pixel shader: hresult="
                          << gfx::hexa(hr);
      return false;
    }
  }

  if (!mShaderBltIndexBuffer) {
    uint16_t vsIndices[] = {0, 1, 2, 0, 2, 3};
    D3D11_BUFFER_DESC indexBufferDesc = {};
    indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(vsIndices);
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    D3D11_SUBRESOURCE_DATA indexInitData = {};
    indexInitData.pSysMem = vsIndices;
    hr = device->CreateBuffer(&indexBufferDesc, &indexInitData,
                              getter_AddRefs(mShaderBltIndexBuffer));
    if (FAILED(hr)) {
      gfxCriticalNoteOnce << "Failed to create index buffer";
      return false;
    }
  }

  if (!mShaderBltVertexBuffer) {
    D3D11_BUFFER_DESC vsBufferDesc = {};
    vsBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vsBufferDesc.ByteWidth = sizeof(float[4][6]);
    vsBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    hr = device->CreateBuffer(&vsBufferDesc, nullptr,
                              getter_AddRefs(mShaderBltVertexBuffer));
    if (FAILED(hr)) {
      gfxCriticalNoteOnce << "Failed to create vertex buffer";
      return false;
    }
  }

  if (!mShaderBltInputLayout) {
    // Set input layout
    D3D11_INPUT_ELEMENT_DESC layout[] = {
        {"POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0,
         D3D11_INPUT_PER_VERTEX_DATA, 0},
        {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 16,
         D3D11_INPUT_PER_VERTEX_DATA, 0}};
    hr = device->CreateInputLayout(layout, 2,
                                   mShaderBltVSBlob->GetBufferPointer(),
                                   mShaderBltVSBlob->GetBufferSize(),
                                   getter_AddRefs(mShaderBltInputLayout));
    if (FAILED(hr)) {
      gfxCriticalNoteOnce << "Failed to create input layout";
      return false;
    }
  }

  if (!mShaderBltRasterizerState) {
    D3D11_RASTERIZER_DESC rasterizerDesc = {};
    rasterizerDesc.FillMode = D3D11_FILL_SOLID;
    rasterizerDesc.CullMode = D3D11_CULL_NONE;
    hr = device->CreateRasterizerState(
        &rasterizerDesc, getter_AddRefs(mShaderBltRasterizerState));
    if (FAILED(hr)) {
      gfxCriticalNoteOnce << "Failed to create rasterizer state";
      return false;
    }
  }

  if (!mShaderBltBlendState) {
    D3D11_BLEND_DESC blendDesc = {};
    blendDesc.RenderTarget[0].BlendEnable = FALSE;
    blendDesc.RenderTarget[0].RenderTargetWriteMask =
        D3D11_COLOR_WRITE_ENABLE_ALL;
    hr = device->CreateBlendState(&blendDesc,
                                  getter_AddRefs(mShaderBltBlendState));
    if (FAILED(hr)) {
      gfxCriticalNoteOnce << "Failed to create blend state";
      return false;
    }
  }

  if (!mShaderBltConstantBuffer) {
    D3D11_BUFFER_DESC cbDesc = {};
    cbDesc.Usage = D3D11_USAGE_DEFAULT;
    cbDesc.ByteWidth = sizeof(psConstants);
    cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    hr = device->CreateBuffer(&cbDesc, nullptr,
                              getter_AddRefs(mShaderBltConstantBuffer));
    if (FAILED(hr)) {
      gfxCriticalNoteOnce << "Failed to create constant buffer";
      return false;
    }
  }

  if (!mShaderBltSamplerState) {
    D3D11_SAMPLER_DESC samplerDesc = {};
    samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
    samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
    samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
    hr = device->CreateSamplerState(&samplerDesc,
                                    getter_AddRefs(mShaderBltSamplerState));
    if (FAILED(hr)) {
      gfxCriticalNoteOnce << "Failed to create sampler state";
      return false;
    }
  }

  return true;
}

class SavedD3D11State {
  RefPtr<ID3D11DeviceContext> context;
  RefPtr<ID3D11Buffer> vertexBuffers[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
  UINT vertexBufferStrides[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
  UINT vertexBufferOffsets[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
  RefPtr<ID3D11InputLayout> inputLayout;
  RefPtr<ID3D11Buffer> indexBuffer;
  DXGI_FORMAT indexBufferFormat;
  UINT indexBufferOffset;
  D3D11_PRIMITIVE_TOPOLOGY primitiveTopology;
  RefPtr<ID3D11VertexShader> vertexShader;
  RefPtr<ID3D11ClassInstance>
      vertexShaderClassInstances[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT];
  UINT vertexShaderClassInstanceCount;
  RefPtr<ID3D11ShaderResourceView>
      vertexShaderResources[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT];
  RefPtr<ID3D11Buffer>
      vertexConstantBuffers[D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT];
  RefPtr<ID3D11PixelShader> pixelShader;
  RefPtr<ID3D11ClassInstance>
      pixelShaderClassInstances[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT];
  UINT pixelShaderClassInstanceCount;
  RefPtr<ID3D11ShaderResourceView>
      pixelShaderResources[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT];
  RefPtr<ID3D11Buffer>
      pixelConstantBuffers[D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT];
  RefPtr<ID3D11SamplerState>
      pixelSamplerStates[D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT];
  UINT viewportsCount = 0;
  D3D11_VIEWPORT viewports[D3D11_VIEWPORT_AND_SCISSORRECT_MAX_INDEX + 1];
  UINT scissorRectsCount = 0;
  D3D11_RECT scissorRects[D3D11_VIEWPORT_AND_SCISSORRECT_MAX_INDEX + 1];
  RefPtr<ID3D11RasterizerState> rasterizerState;
  RefPtr<ID3D11RenderTargetView>
      renderTargetViews[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT];
  RefPtr<ID3D11DepthStencilView> depthStencilView;
  RefPtr<ID3D11DepthStencilState> depthStencilState;
  UINT stencilRef;
  RefPtr<ID3D11BlendState> blendState;
  FLOAT blendFactors[4];
  UINT sampleMask;

 public:
  explicit SavedD3D11State(RefPtr<ID3D11DeviceContext> _context) {
    context = _context;
    ID3D11Buffer*
        savedVertexBuffers[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT] = {};
    ID3D11Buffer* savedVertexConstantBuffers
        [D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT] = {};
    ID3D11ClassInstance* savedVertexClassInstances
        [D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = {};
    ID3D11ShaderResourceView* savedVertexShaderResources
        [D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = {};
    ID3D11Buffer* savedPixelConstantBuffers
        [D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT] = {};
    ID3D11ClassInstance* savedPixelShaderClassInstances
        [D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = {};
    ID3D11ShaderResourceView* savedPixelShaderResources
        [D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = {};
    ID3D11SamplerState*
        savedPixelSamplerStates[D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT] = {};
    ID3D11RenderTargetView*
        savedRenderTargetViews[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT] = {};

    context->IAGetInputLayout(getter_AddRefs(inputLayout));
    context->IAGetVertexBuffers(0, D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT,
                                savedVertexBuffers, vertexBufferStrides,
                                vertexBufferOffsets);
    context->IAGetIndexBuffer(getter_AddRefs(indexBuffer), &indexBufferFormat,
                              &indexBufferOffset);
    context->IAGetPrimitiveTopology(&primitiveTopology);
    context->VSGetShader(getter_AddRefs(vertexShader),
                         savedVertexClassInstances,
                         &vertexShaderClassInstanceCount);
    context->VSGetConstantBuffers(
        0, D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT,
        savedVertexConstantBuffers);
    context->VSGetShaderResources(0,
                                  D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT,
                                  savedVertexShaderResources);
    context->PSGetShader(getter_AddRefs(pixelShader),
                         savedPixelShaderClassInstances,
                         &pixelShaderClassInstanceCount);
    context->PSGetConstantBuffers(
        0, D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT,
        savedPixelConstantBuffers);
    context->PSGetShaderResources(0,
                                  D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT,
                                  savedPixelShaderResources);
    context->PSGetSamplers(0, D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT,
                           savedPixelSamplerStates);
    context->RSGetViewports(&viewportsCount, viewports);
    context->RSGetScissorRects(&scissorRectsCount, scissorRects);
    context->RSGetState(getter_AddRefs(rasterizerState));
    context->OMGetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT,
                                savedRenderTargetViews,
                                getter_AddRefs(depthStencilView));
    context->OMGetBlendState(getter_AddRefs(blendState), blendFactors,
                             &sampleMask);
    context->OMGetDepthStencilState(getter_AddRefs(depthStencilState),
                                    &stencilRef);

    for (UINT i = 0; i < D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT; ++i) {
      vertexBuffers[i] = dont_AddRef(savedVertexBuffers[i]);
    }
    for (UINT i = 0; i < D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT; ++i) {
      vertexShaderClassInstances[i] = dont_AddRef(savedVertexClassInstances[i]);
    }
    for (UINT i = 0; i < D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
         ++i) {
      vertexConstantBuffers[i] = dont_AddRef(savedVertexConstantBuffers[i]);
    }
    for (UINT i = 0; i < D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT; ++i) {
      vertexShaderResources[i] = dont_AddRef(savedVertexShaderResources[i]);
    }
    for (UINT i = 0; i < D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT; ++i) {
      pixelShaderClassInstances[i] =
          dont_AddRef(savedPixelShaderClassInstances[i]);
    }
    for (UINT i = 0; i < D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
         ++i) {
      pixelConstantBuffers[i] = dont_AddRef(savedPixelConstantBuffers[i]);
    }
    for (UINT i = 0; i < D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT; ++i) {
      pixelShaderResources[i] = dont_AddRef(savedPixelShaderResources[i]);
    }
    for (UINT i = 0; i < D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT; ++i) {
      pixelSamplerStates[i] = dont_AddRef(savedPixelSamplerStates[i]);
    }
    for (UINT i = 0; i < D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) {
      renderTargetViews[i] = dont_AddRef(savedRenderTargetViews[i]);
    }
  }

  ~SavedD3D11State() {
    ID3D11Buffer*
        savedVertexBuffers[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT] = {};
    ID3D11Buffer* savedVertexConstantBuffers
        [D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT] = {};
    ID3D11ClassInstance* savedVertexShaderClassInstances
        [D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = {};
    ID3D11ShaderResourceView* savedVertexShaderResources
        [D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = {};
    ID3D11Buffer* savedPixelConstantBuffers
        [D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT] = {};
    ID3D11ClassInstance* savedPixelShaderClassInstances
        [D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = {};
    ID3D11ShaderResourceView* savedPixelShaderResources
        [D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = {};
    ID3D11SamplerState*
        savedPixelSamplerStates[D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT] = {};
    ID3D11RenderTargetView*
        savedRenderTargetViews[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT] = {};

    for (UINT i = 0; i < D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT; ++i) {
      savedVertexBuffers[i] = vertexBuffers[i];
    }
    for (UINT i = 0; i < D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT; ++i) {
      savedVertexShaderClassInstances[i] = vertexShaderClassInstances[i];
    }
    for (UINT i = 0; i < D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
         ++i) {
      savedVertexConstantBuffers[i] = vertexConstantBuffers[i];
    }
    for (UINT i = 0; i < D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT; ++i) {
      savedVertexShaderResources[i] = vertexShaderResources[i];
    }
    for (UINT i = 0; i < D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT; ++i) {
      savedPixelShaderClassInstances[i] = pixelShaderClassInstances[i];
    }
    for (UINT i = 0; i < D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT;
         ++i) {
      savedPixelConstantBuffers[i] = pixelConstantBuffers[i];
    }
    for (UINT i = 0; i < D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT; ++i) {
      savedPixelShaderResources[i] = pixelShaderResources[i];
    }
    for (UINT i = 0; i < D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT; ++i) {
      savedPixelSamplerStates[i] = pixelSamplerStates[i];
    }
    for (UINT i = 0; i < D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) {
      savedRenderTargetViews[i] = renderTargetViews[i];
    }

    context->IASetVertexBuffers(0, 1, savedVertexBuffers, vertexBufferStrides,
                                vertexBufferOffsets);
    context->IASetInputLayout(inputLayout);
    context->IASetIndexBuffer(indexBuffer, indexBufferFormat,
                              indexBufferOffset);
    context->IASetPrimitiveTopology(primitiveTopology);
    context->VSSetShader(vertexShader, savedVertexShaderClassInstances,
                         vertexShaderClassInstanceCount);
    context->VSSetShaderResources(0, 2, savedVertexShaderResources);
    context->VSSetConstantBuffers(0, 1, savedVertexConstantBuffers);
    context->PSSetShader(pixelShader, savedPixelShaderClassInstances,
                         pixelShaderClassInstanceCount);
    context->PSSetShaderResources(0, 2, savedPixelShaderResources);
    context->PSSetConstantBuffers(0, 1, savedPixelConstantBuffers);
    context->PSSetSamplers(0, 2, savedPixelSamplerStates);
    context->RSSetViewports(viewportsCount, viewports);
    context->RSSetScissorRects(scissorRectsCount, scissorRects);
    context->RSSetState(rasterizerState);
    context->OMSetRenderTargets(1, savedRenderTargetViews, depthStencilView);
    context->OMSetDepthStencilState(depthStencilState, stencilRef);
    context->OMSetBlendState(blendState, blendFactors, sampleMask);
  }
};

// Shader-based YUV->RGB blit, this is a fallback for VideoProcessorBlt when it
// doesn't support certain format combinations (notably HLG to PQ).
bool DCSurfaceVideo::ShaderBlt(DXGI_COLOR_SPACE_TYPE inputColorSpace,
                               const RECT& sourceRect,
                               DXGI_COLOR_SPACE_TYPE outputColorSpace,
                               const RECT& destRect) {
  HRESULT hr;
  const auto device = mDCLayerTree->GetDevice();
  const auto texture = mRenderTextureHost->AsRenderDXGITextureHost();
  RefPtr<ID3D11Texture2D> inputTexture = texture->GetD3D11Texture2DWithGL();
  RefPtr<ID3D11Texture2D> outputTexture;
  mVideoSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
                             (void**)getter_AddRefs(outputTexture));
  if (!inputTexture || !outputTexture) {
    gfxCriticalNoteOnce << "Failed to get D3D11Texture2D for ShaderBlt";
    return false;
  }
  RefPtr<ID3D11DeviceContext> context;
  device->GetImmediateContext(getter_AddRefs(context));
  if (!context) {
    gfxCriticalNoteOnce << "Failed to get D3D11DeviceContext for ShaderBlt";
    return false;
  }

  if (!ShaderBltSetup()) {
    return false;
  }

  RefPtr<ID3D11ShaderResourceView> yResourceView;
  D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
  srvDesc.Format = DXGI_FORMAT_R16_UNORM;
  srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
  srvDesc.Texture2D.MostDetailedMip = 0;
  srvDesc.Texture2D.MipLevels = 1;
  hr = device->CreateShaderResourceView(inputTexture, &srvDesc,
                                        getter_AddRefs(yResourceView));
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "Failed to create Y shader resource view";
    return false;
  }

  RefPtr<ID3D11ShaderResourceView> uvResourceView;
  srvDesc.Format = DXGI_FORMAT_R16G16_UNORM;
  hr = device->CreateShaderResourceView(inputTexture, &srvDesc,
                                        getter_AddRefs(uvResourceView));
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "Failed to create UV shader resource view";
    return false;
  }

  // Create and set render target view for output texture
  RefPtr<ID3D11RenderTargetView> rtView;
  D3D11_RENDER_TARGET_VIEW_DESC rtvDesc = {};
  D3D11_TEXTURE2D_DESC outputDesc;
  outputTexture->GetDesc(&outputDesc);
  rtvDesc.Format = outputDesc.Format;
  rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
  rtvDesc.Texture2D.MipSlice = 0;

  hr = device->CreateRenderTargetView(outputTexture, &rtvDesc,
                                      getter_AddRefs(rtView));
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "Failed to create render target view";
    return false;
  }

  // Set up shader resources
  D3D11_TEXTURE2D_DESC inputTextureDesc = {};
  inputTexture->GetDesc(&inputTextureDesc);
  float scale[2] = {
      1.0f / static_cast<float>(inputTextureDesc.Width),
      1.0f / static_cast<float>(inputTextureDesc.Height),
  };
  float offset[2] = {0.0f * scale[0], 0.0f * scale[1]};
  float vertexData[4][6] = {
      // position[4]    texCoord[2]
      {-1.0f, -1.0f, 0.1f, 1.0f, sourceRect.left * scale[0] + offset[0],
       sourceRect.bottom * scale[1] + offset[1]},
      {1.0f, -1.0f, 0.1f, 1.0f, sourceRect.right * scale[0] + offset[0],
       sourceRect.bottom * scale[1] + offset[1]},
      {-1.0f, 1.0f, 0.1f, 1.0f, sourceRect.left * scale[0] + offset[0],
       sourceRect.top * scale[1] + offset[1]},
      {1.0f, 1.0f, 0.1f, 1.0f, sourceRect.right * scale[0] + offset[0],
       sourceRect.top * scale[1] + offset[1]}};
  context->UpdateSubresource(mShaderBltVertexBuffer, 0, nullptr, vertexData[0],
                             0, 0);

  static const float yuvToRgbMatrixP709Studio[16] = {
      1.164384f,  -0.000000f, 1.792741f, -0.972945f, 1.164384f,  -0.213249f,
      -0.532909f, 0.301483f,  1.164384f, 2.112402f,  -0.000000f, -1.133402f,
      0.000000f,  0.000000f,  0.000000f, 1.000000f};

  static const float yuvToRgbMatrixP709Full[16] = {
      1.000000f,  -0.000000f, 1.574800f, -0.790488f, 1.000000f,  -0.187324f,
      -0.468124f, 0.329010f,  1.000000f, 1.855600f,  -0.000000f, -0.931439f,
      0.000000f,  0.000000f,  0.000000f, 1.000000f};

  static const float yuvToRgbMatrixP601Studio[16] = {
      1.164384f,  -0.000000f, 1.596027f, -0.874202f, 1.164384f, -0.391762f,
      -0.812968f, 0.531668f,  1.164384f, 2.017232f,  0.000000f, -1.085631f,
      0.000000f,  0.000000f,  0.000000f, 1.000000f};

  static const float yuvToRgbMatrixP601Full[16] = {
      1.0000f,    0.000000f,  1.402000f, -0.701000f, 1.0000f,    -0.344136f,
      -0.714136f, 0.5271972f, 1.0000f,   1.772000f,  -0.000000f, -0.861777f,
      0.000000f,  0.000000f,  0.000000f, 1.000000f};

  static const float yuvToRgbMatrixP2020Studio[16] = {
      1.168932f,  0.000000f, 1.685231f, -0.915688f, 1.168932f,  -0.188058f,
      -0.652965f, 0.347458f, 1.168932f, 2.150139f,  -0.000000f, -1.148145f,
      0.000000f,  0.000000f, 0.000000f, 1.000000f};

  static const float yuvToRgbMatrixP2020Full[16] = {
      1.000000f,  -0.000000f, 1.474600f, -0.737311f, 1.000000f,  -0.164553f,
      -0.571353f, 0.367959f,  1.000000f, 1.881400f,  -0.000000f, -0.940714f,
      0.000000f,  0.000000f,  0.000000f, 1.000000f};

  // Update constant buffer with the color transforms we need,
  // currently the P601 and P709 cases are unused because VideoProcessorBlt
  // always succeeds for those cases, but are included for completeness.
  uint32_t inputMode = 0;
  uint32_t outputMode = 0;
  float const* m;
  switch (inputColorSpace) {
    default:
      gfxCriticalNoteOnce << "ShaderBlt: Unhandled input color space "
                          << static_cast<int>(inputColorSpace)
                          << ", treating as BT709 limited color space";
      FMT_FALLTHROUGH;
    case DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709:
      inputMode = MODE_BT709;
      m = yuvToRgbMatrixP709Studio;
      break;
    case DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709:
      inputMode = MODE_BT709;
      m = yuvToRgbMatrixP709Full;
      break;
    case DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601:
      inputMode = MODE_BT709;
      m = yuvToRgbMatrixP601Studio;
      break;
    case DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601:
      inputMode = MODE_BT709;
      m = yuvToRgbMatrixP601Full;
      break;
    case DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020:
      inputMode = MODE_PQ;
      m = yuvToRgbMatrixP2020Studio;
      break;
    case DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020:
      inputMode = MODE_HLG;
      m = yuvToRgbMatrixP2020Studio;
      break;
    case DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020:
      inputMode = MODE_HLG;
      m = yuvToRgbMatrixP2020Full;
      break;
  }
  color::mat4 yuvToRgb = color::mat4{{color::vec4{{m[0], m[1], m[2], m[3]}},
                                      {{m[4], m[5], m[6], m[7]}},
                                      {{m[8], m[9], m[10], m[11]}},
                                      {{m[12], m[13], m[14], m[15]}}}};

  switch (outputColorSpace) {
    default:
      gfxCriticalNoteOnce << "ShaderBlt: Unhandled output color space "
                          << static_cast<int>(outputColorSpace)
                          << ", treating as BT709 output color space";
      FMT_FALLTHROUGH;
    case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709:
    case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020:
      outputMode = MODE_BT709;
      break;
    case DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020:
      outputMode = MODE_PQ;
      break;
  }

  // This is identity for now, if converting between BT2020 and BT709 we would
  // need to set it.
  color::mat4 linearColor = color::mat4::Identity();

  // Set Reinhard tonemapping based on content and display luminance.
  //
  // Currently this is unused functionality - to configure this properly we will
  // need to get the content luminance from the video stream and the display
  // luminance from the screen info. For now we just set it to 1.0f for both
  // which is a no-op.
  const float luminanceContent = 1.0f;
  const float luminanceDisplay = 1.0f;
  float a = 0.0f;
  float b = 0.0f;
  if (luminanceContent <= luminanceDisplay) {
    // No op - the content fits within display limits
    a = 0.0f;
    b = 0.0f;
  } else {
    // Use tonemapping to make content fit in display limits.
    a = luminanceDisplay / (luminanceContent * luminanceContent);
    b = 1.0f / luminanceDisplay;
  }

  // This is the ratio of reference white in PQ (scene-referred) to HLG
  // (display-referred)
  const float pqMultiplier = 80.0f / 10000.0f;

  psConstants constants = {.yuvToRgbMatrix = yuvToRgb,
                           .colorSpaceMatrix = linearColor,
                           .tonemapping = {a, b},
                           .pqMultiplier = pqMultiplier,
                           .inputMode = inputMode,
                           .outputMode = outputMode};
  context->UpdateSubresource(mShaderBltConstantBuffer, 0, nullptr, &constants,
                             0, 0);

  UINT vertexBufferStride = sizeof(vertexData[0]);
  UINT vertexBufferOffset = 0;
  // Set viewport for destination rectangle
  D3D11_VIEWPORT vp = {};
  vp.TopLeftX = static_cast<FLOAT>(destRect.left);
  vp.TopLeftY = static_cast<FLOAT>(destRect.top);
  vp.Width = static_cast<FLOAT>(destRect.right - destRect.left);
  vp.Height = static_cast<FLOAT>(destRect.bottom - destRect.top);
  vp.MinDepth = 0.0f;
  vp.MaxDepth = 1.0f;
  ID3D11ShaderResourceView* srvs[] = {yResourceView, uvResourceView};
  ID3D11SamplerState* samplers[] = {mShaderBltSamplerState,
                                    mShaderBltSamplerState};
  ID3D11Buffer* psConstantBuffers[1] = {mShaderBltConstantBuffer};
  ID3D11Buffer* vertexBuffers[1] = {mShaderBltVertexBuffer};
  ID3D11RenderTargetView* rtViews[1] = {rtView};

  {
    // Save D3D11 state
    SavedD3D11State savedState(context);

    // Set state for the shader
    context->IASetVertexBuffers(0, 1, vertexBuffers, &vertexBufferStride,
                                &vertexBufferOffset);
    context->IASetInputLayout(mShaderBltInputLayout);
    context->IASetIndexBuffer(nullptr, DXGI_FORMAT_R16_UINT, 0);
    context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
    context->VSSetShader(mShaderBltVertexShader, nullptr, 0);
    context->VSSetSamplers(0, 0, nullptr);
    context->VSSetShaderResources(0, 0, nullptr);
    context->VSSetConstantBuffers(0, 0, nullptr);
    context->PSSetShader(mShaderBltPixelShader, nullptr, 0);
    context->PSSetSamplers(0, 2, samplers);
    context->PSSetShaderResources(0, 2, srvs);
    context->PSSetConstantBuffers(0, 1, psConstantBuffers);
    context->RSSetViewports(1, &vp);
    context->RSSetScissorRects(0, nullptr);
    context->RSSetState(mShaderBltRasterizerState);
    context->OMSetRenderTargets(1, rtViews, nullptr);
    context->OMSetDepthStencilState(nullptr, 0);
    context->OMSetBlendState(mShaderBltBlendState, nullptr, 0xffffffff);

    // Draw the quad
    context->Draw(4, 0);

    // D3D11 state will be restored when savedState goes out of scope
  }

  return true;
}

bool DCSurfaceVideo::CallVideoProcessorBlt() {
  MOZ_ASSERT(mRenderTextureHost);

  HRESULT hr;
  const auto device = mDCLayerTree->GetDevice();
  const auto videoDevice = mDCLayerTree->GetVideoDevice();
  const auto videoContext = mDCLayerTree->GetVideoContext();
  const auto texture = mRenderTextureHost->AsRenderDXGITextureHost();

  Maybe<DXGI_COLOR_SPACE_TYPE> sourceColorSpace =
      GetSourceDXGIColorSpace(texture->GetYUVColorSpace());
  if (sourceColorSpace.isNothing()) {
    gfxCriticalNote << "Unsupported color space";
    return false;
  }

  RefPtr<ID3D11Texture2D> texture2D = texture->GetD3D11Texture2DWithGL();
  if (!texture2D) {
    gfxCriticalNote << "Failed to get D3D11Texture2D";
    return false;
  }

  if (!mVideoSwapChain) {
    return false;
  }

  if (texture->mFencesHolderId.isSome()) {
    auto* fencesHolderMap = layers::CompositeProcessD3D11FencesHolderMap::Get();
    MOZ_ASSERT(fencesHolderMap);
    fencesHolderMap->WaitWriteFence(texture->mFencesHolderId.ref(), device);
  }

  RefPtr<IDXGISwapChain3> swapChain3;
  mVideoSwapChain->QueryInterface(
      (IDXGISwapChain3**)getter_AddRefs(swapChain3));
  if (!swapChain3) {
    gfxCriticalNote << "Failed to get IDXGISwapChain3";
    return false;
  }

  RefPtr<ID3D11VideoContext1> videoContext1;
  videoContext->QueryInterface(
      (ID3D11VideoContext1**)getter_AddRefs(videoContext1));
  if (!videoContext1) {
    gfxCriticalNote << "Failed to get ID3D11VideoContext1";
    return false;
  }

  const auto videoProcessor = mDCLayerTree->GetVideoProcessor();
  const auto videoProcessorEnumerator =
      mDCLayerTree->GetVideoProcessorEnumerator();

  DXGI_COLOR_SPACE_TYPE inputColorSpace = sourceColorSpace.ref();
  videoContext1->VideoProcessorSetStreamColorSpace1(videoProcessor, 0,
                                                    inputColorSpace);

  Maybe<DXGI_COLOR_SPACE_TYPE> outputColorSpaceRef =
      GetOutputDXGIColorSpace(mSwapChainFormat, inputColorSpace, mUseVpAutoHDR);
  if (outputColorSpaceRef.isNothing()) {
    gfxCriticalNoteOnce << "Unrecognized DXGI mSwapChainFormat, unsure of "
                           "correct DXGI colorspace: "
                        << gfx::hexa(mSwapChainFormat);
    return false;
  }
  DXGI_COLOR_SPACE_TYPE outputColorSpace = outputColorSpaceRef.ref();

  hr = swapChain3->SetColorSpace1(outputColorSpace);
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "SetColorSpace1 failed: " << gfx::hexa(hr);
    RenderThread::Get()->NotifyWebRenderError(
        wr::WebRenderError::VIDEO_OVERLAY);
    return false;
  }
  videoContext1->VideoProcessorSetOutputColorSpace1(videoProcessor,
                                                    outputColorSpace);

  auto hdrMetadata =
      gfx::DeviceManagerDx::Get()->WindowHDRMetadata(mDCLayerTree->GetHwnd());
  RefPtr<ID3D11VideoContext2> videoContext2;
  videoContext->QueryInterface(
      (ID3D11VideoContext2**)getter_AddRefs(videoContext2));
  if (hdrMetadata.isSome() && videoContext2) {
    // If we had to fall back to non-HDR color spaces in VideoProcessorBlt we
    // should remove the HDR meta data to avoid confusing the
    // VideoProcessorBlt implementation.  We'll still display as HDR if it was
    // PQ content but without the metadata being fed to VideoProcessorBlt, so it
    // may be too bright but at least it should work.
    if (mFailedVideoProcessorBltYUVHLGToRGBPQ ||
        mFailedVideoProcessorBltYUVPQtoRGBPQ) {
      videoContext2->VideoProcessorSetOutputHDRMetaData(
          videoProcessor, DXGI_HDR_METADATA_TYPE_NONE, 0, NULL);
    } else {
      videoContext2->VideoProcessorSetOutputHDRMetaData(
          videoProcessor, DXGI_HDR_METADATA_TYPE_HDR10,
          sizeof(DXGI_HDR_METADATA_HDR10), &(hdrMetadata.ref()));
    }
  }

  if (videoContext2) {
    const auto& streamHdrMetadata = texture->GetHDRMetadata();
    if (streamHdrMetadata.isSome()) {
      DXGI_HDR_METADATA_HDR10 hdr10 = ToStreamHDR10Metadata(*streamHdrMetadata);
      videoContext2->VideoProcessorSetStreamHDRMetaData(
          videoProcessor, 0, DXGI_HDR_METADATA_TYPE_HDR10, sizeof(hdr10),
          &hdr10);
    }
  }

  D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC inputDesc = {};
  inputDesc.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D;
  inputDesc.Texture2D.ArraySlice = texture->ArrayIndex();

  RefPtr<ID3D11VideoProcessorInputView> inputView;
  hr = videoDevice->CreateVideoProcessorInputView(
      texture2D, videoProcessorEnumerator, &inputDesc,
      getter_AddRefs(inputView));
  if (FAILED(hr)) {
    gfxCriticalNote << "ID3D11VideoProcessorInputView creation failed: "
                    << gfx::hexa(hr);
    return false;
  }

  D3D11_VIDEO_PROCESSOR_STREAM stream = {};
  stream.Enable = true;
  stream.OutputIndex = 0;
  stream.InputFrameOrField = 0;
  stream.PastFrames = 0;
  stream.FutureFrames = 0;
  stream.pInputSurface = inputView.get();

  RECT destRect;
  destRect.left = 0;
  destRect.top = 0;
  destRect.right = mSwapChainSize.width;
  destRect.bottom = mSwapChainSize.height;

  videoContext->VideoProcessorSetOutputTargetRect(videoProcessor, TRUE,
                                                  &destRect);
  videoContext->VideoProcessorSetStreamDestRect(videoProcessor, 0, TRUE,
                                                &destRect);
  RECT sourceRect;
  sourceRect.left = 0;
  sourceRect.top = 0;
  sourceRect.right = mVideoSize.width;
  sourceRect.bottom = mVideoSize.height;
  videoContext->VideoProcessorSetStreamSourceRect(videoProcessor, 0, TRUE,
                                                  &sourceRect);

  if (outputColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 &&
      StaticPrefs::gfx_color_management_hdr_yuv_to_rgb_video_shader_always()) {
    return ShaderBlt(inputColorSpace, sourceRect, outputColorSpace, destRect);
  }

  if (!mOutputView) {
    RefPtr<ID3D11Texture2D> backBuf;
    mVideoSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
                               (void**)getter_AddRefs(backBuf));

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

    hr = videoDevice->CreateVideoProcessorOutputView(
        backBuf, videoProcessorEnumerator, &outputDesc,
        getter_AddRefs(mOutputView));
    if (FAILED(hr)) {
      gfxCriticalNote << "ID3D11VideoProcessorOutputView creation failed: "
                      << gfx::hexa(hr);
      return false;
    }
  }

  const UINT vendorId = GetVendorId(videoDevice);
  const auto powerIsCharging = RenderThread::Get()->GetPowerIsCharging();
  const bool useSuperResolution =
      gfx::gfxVars::WebRenderOverlayVpSuperResolution() && powerIsCharging &&
      !mVpSuperResolutionFailed;

  if (profiler_thread_is_being_profiled_for_markers()) {
    nsPrintfCString str(
        "useSuperResolution %d gfxVars %d charging %d failed %d",
        useSuperResolution, gfx::gfxVars::WebRenderOverlayVpSuperResolution(),
        powerIsCharging, mVpSuperResolutionFailed);
    PROFILER_MARKER_TEXT("DCSurfaceVideo", GRAPHICS, {}, str);
  }

  if (useSuperResolution) {
    PROFILER_MARKER_TEXT("DCSurfaceVideo", GRAPHICS, {},
                         "SetVpSuperResolution"_ns);

    hr = SetVpSuperResolution(vendorId, videoContext, videoProcessor, true);
    if (FAILED(hr)) {
      if (hr != E_NOTIMPL) {
        gfxCriticalNoteOnce << "SetVpSuperResolution failed: " << gfx::hexa(hr);
      }
      mVpSuperResolutionFailed = true;
    }
  } else if (gfx::gfxVars::WebRenderOverlayVpSuperResolution() &&
             !useSuperResolution) {
    SetVpSuperResolution(vendorId, videoContext, videoProcessor, false);
  }

  if (profiler_thread_is_being_profiled_for_markers() && vendorId == 0x10DE) {
    AddProfileMarkerForNvidiaVpSuperResolutionInfo(videoContext,
                                                   videoProcessor);
  }

  if (mUseVpAutoHDR) {
    PROFILER_MARKER_TEXT("DCSurfaceVideo", GRAPHICS, {}, "SetVpAutoHDR"_ns);

    hr = SetVpAutoHDR(vendorId, videoContext, videoProcessor, true);
    if (FAILED(hr)) {
      gfxCriticalNoteOnce << "SetVpAutoHDR failed: " << gfx::hexa(hr);
      mVpAutoHDRFailed = true;
    }
  }

  hr = videoContext->VideoProcessorBlt(videoProcessor, mOutputView, 0, 1,
                                       &stream);
  if (hr == E_NOTIMPL &&
      outputColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) {
    if (StaticPrefs::
            gfx_color_management_hdr_yuv_to_rgb_video_shader_fallback()) {
      return ShaderBlt(inputColorSpace, sourceRect, outputColorSpace, destRect);
    }
    if (inputColorSpace == DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020 ||
        inputColorSpace == DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020) {
      // If YUV BT2100 HLG conversion to RGB BT2100 PQ is not working, we have
      // to fall back to displaying it as regular RGB BT2020, which is not HDR
      // but still has the gamut of BT2020.  Once we implement HLG conversion
      // ourselves (probably using a D3D11 shader instead of VideoProcessorBlt)
      // we can handle this more gracefully.
      gfxCriticalNoteOnce
          << "VideoProcessorBlt failed with BT2100 HLG content with "
             "error E_NOTIMPL, BT2100 HLG content will be displayed as BT2020 "
             "(not HDR).  Input color space: "
          << static_cast<int>(inputColorSpace)
          << ", output color space: " << static_cast<int>(outputColorSpace)
          << ", swap chain format: " << static_cast<int>(mSwapChainFormat);
      mFailedVideoProcessorBltYUVHLGToRGBPQ = true;
      hr = swapChain3->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020);
      if (FAILED(hr)) {
        gfxCriticalNoteOnce << "SetColorSpace1 failed: " << gfx::hexa(hr);
        RenderThread::Get()->NotifyWebRenderError(
            wr::WebRenderError::VIDEO_OVERLAY);
        return false;
      }
      // Now retry the VideoProcessorBlt with BT709 which should just work.
      if (inputColorSpace == DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020) {
        videoContext1->VideoProcessorSetStreamColorSpace1(
            videoProcessor, 0, DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709);
      } else {
        videoContext1->VideoProcessorSetStreamColorSpace1(
            videoProcessor, 0, DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709);
      }
      videoContext1->VideoProcessorSetOutputColorSpace1(
          videoProcessor, DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020);
      hr = videoContext->VideoProcessorBlt(videoProcessor, mOutputView, 0, 1,
                                           &stream);
    } else {
      // If YUV BT2100 PQ conversion to RGB BT2100 PQ is not working, we can
      // just pretend it is BT2020 and the VideoProcessorBlt should succeed,
      // it's not clear if this causes any color distortion.
      gfxCriticalNoteOnce
          << "VideoProcessorBlt failed with BT2100 PQ content with "
             "error E_NOTIMPL, will retry as BT709 for processing and display "
             "as BT2100 PQ.  Input color space: "
          << static_cast<int>(inputColorSpace)
          << ", output color space: " << static_cast<int>(outputColorSpace)
          << ", swap chain format: " << static_cast<int>(mSwapChainFormat);
      mFailedVideoProcessorBltYUVPQtoRGBPQ = true;
      // Now retry the VideoProcessorBlt with BT709 which should just work.
      videoContext1->VideoProcessorSetStreamColorSpace1(
          videoProcessor, 0, DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709);
      videoContext1->VideoProcessorSetOutputColorSpace1(
          videoProcessor, DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
      hr = videoContext->VideoProcessorBlt(videoProcessor, mOutputView, 0, 1,
                                           &stream);
    }
  }
  if (FAILED(hr)) {
    gfxCriticalNote << "VideoProcessorBlt failed: " << gfx::hexa(hr);
    return false;
  }

  return true;
}

void DCSurfaceVideo::ReleaseDecodeSwapChainResources() {
  mOutputView = nullptr;
  mVideoSwapChain = nullptr;
  mDecodeSwapChain = nullptr;
  mDecodeResource = nullptr;
  if (mSwapChainSurfaceHandle) {
    ::CloseHandle(mSwapChainSurfaceHandle);
    mSwapChainSurfaceHandle = 0;
  }
  mUseVpAutoHDR = false;
  mUseHDR = false;
  mShaderBltVertexShader = nullptr;
  mShaderBltVSBlob = nullptr;
  mShaderBltPixelShader = nullptr;
  mShaderBltPSBlob = nullptr;
  mShaderBltIndexBuffer = nullptr;
  mShaderBltVertexBuffer = nullptr;
  mShaderBltInputLayout = nullptr;
  mShaderBltRasterizerState = nullptr;
  mShaderBltBlendState = nullptr;
  mShaderBltConstantBuffer = nullptr;
  mShaderBltSamplerState = nullptr;
}

DCSurfaceHandle::DCSurfaceHandle(bool aIsOpaque, DCLayerTree* aDCLayerTree)
    : DCSurface(aIsOpaque, aDCLayerTree) {}

void DCSurfaceHandle::AttachExternalImage(wr::ExternalImageId aExternalImage) {
  RenderTextureHost* texture =
      RenderThread::Get()->GetRenderTexture(aExternalImage);
  RenderDcompSurfaceTextureHost* renderTexture =
      texture ? texture->AsRenderDcompSurfaceTextureHost() : nullptr;
  if (!renderTexture) {
    gfxCriticalNote << "Unsupported RenderTexture for DCSurfaceHandle: "
                    << gfx::hexa(texture);
    return;
  }

  const auto handle = renderTexture->GetDcompSurfaceHandle();
  if (GetSurfaceHandle() == handle) {
    return;
  }

  LOG_H("AttachExternalImage, ext-image=%" PRIu64 ", texture=%p, handle=%p",
        wr::AsUint64(aExternalImage), renderTexture, handle);
  mDcompTextureHost = renderTexture;
}

HANDLE DCSurfaceHandle::GetSurfaceHandle() const {
  if (mDcompTextureHost) {
    return mDcompTextureHost->GetDcompSurfaceHandle();
  }
  return nullptr;
}

IDCompositionSurface* DCSurfaceHandle::EnsureSurface() {
  if (auto* surface = mDcompTextureHost->GetSurface()) {
    return surface;
  }

  // Texture host hasn't created the surface yet, ask it to create a new one.
  RefPtr<IDCompositionDevice> device;
  HRESULT hr = mDCLayerTree->GetCompositionDevice()->QueryInterface(
      (IDCompositionDevice**)getter_AddRefs(device));
  if (FAILED(hr)) {
    gfxCriticalNote
        << "Failed to convert IDCompositionDevice2 to IDCompositionDevice: "
        << gfx::hexa(hr);
    return nullptr;
  }

  return mDcompTextureHost->CreateSurfaceFromDevice(device);
}

void DCSurfaceHandle::PresentSurfaceHandle() {
  LOG_H("PresentSurfaceHandle");
  if (IDCompositionSurface* surface = EnsureSurface()) {
    LOG_H("Set surface %p to visual", surface);
    mContentVisual->SetContent(surface);
  } else {
    mContentVisual->SetContent(nullptr);
  }
}

GLuint DCLayerTree::CreateEGLSurfaceForCompositionSurface(
    wr::DeviceIntRect aDirtyRect, wr::DeviceIntPoint* aOffset,
    RefPtr<IDCompositionSurface> aCompositionSurface,
    wr::DeviceIntPoint aSurfaceOffset) {
  MOZ_ASSERT(aCompositionSurface.get());

  HRESULT hr;
  const auto gl = GetGLContext();
  RefPtr<ID3D11Texture2D> backBuf;
  POINT offset;

  RECT update_rect;
  update_rect.left = aSurfaceOffset.x + aDirtyRect.min.x;
  update_rect.top = aSurfaceOffset.y + aDirtyRect.min.y;
  update_rect.right = aSurfaceOffset.x + aDirtyRect.max.x;
  update_rect.bottom = aSurfaceOffset.y + aDirtyRect.max.y;
  hr = aCompositionSurface->BeginDraw(&update_rect, __uuidof(ID3D11Texture2D),
                                      (void**)getter_AddRefs(backBuf), &offset);

  if (FAILED(hr)) {
    LayoutDeviceIntRect rect = widget::WinUtils::ToIntRect(update_rect);

    gfxCriticalNote << "DCompositionSurface::BeginDraw failed: "
                    << gfx::hexa(hr) << " " << rect;
    RenderThread::Get()->HandleWebRenderError(WebRenderError::BEGIN_DRAW);
    return false;
  }

  // DC includes the origin of the dirty / update rect in the draw offset,
  // undo that here since WR expects it to be an absolute offset.
  offset.x -= aDirtyRect.min.x;
  offset.y -= aDirtyRect.min.y;

  D3D11_TEXTURE2D_DESC desc;
  backBuf->GetDesc(&desc);

  const auto& gle = gl::GLContextEGL::Cast(gl);
  const auto& egl = gle->mEgl;

  const auto buffer = reinterpret_cast<EGLClientBuffer>(backBuf.get());

  // Construct an EGLImage wrapper around the D3D texture for ANGLE.
  const EGLint attribs[] = {LOCAL_EGL_NONE};
  mEGLImage = egl->fCreateImage(EGL_NO_CONTEXT, LOCAL_EGL_D3D11_TEXTURE_ANGLE,
                                buffer, attribs);

  // Get the current FBO and RBO id, so we can restore them later
  GLint currentFboId, currentRboId;
  gl->fGetIntegerv(LOCAL_GL_DRAW_FRAMEBUFFER_BINDING, &currentFboId);
  gl->fGetIntegerv(LOCAL_GL_RENDERBUFFER_BINDING, &currentRboId);

  // Create a render buffer object that is backed by the EGL image.
  gl->fGenRenderbuffers(1, &mColorRBO);
  gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, mColorRBO);
  gl->fEGLImageTargetRenderbufferStorage(LOCAL_GL_RENDERBUFFER, mEGLImage);

  // Get or create an FBO for the specified dimensions
  GLuint fboId = GetOrCreateFbo(desc.Width, desc.Height);

  // Attach the new renderbuffer to the FBO
  gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fboId);
  gl->fFramebufferRenderbuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
                               LOCAL_GL_COLOR_ATTACHMENT0,
                               LOCAL_GL_RENDERBUFFER, mColorRBO);

  // Restore previous FBO and RBO bindings
  gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, currentFboId);
  gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, currentRboId);

  aOffset->x = offset.x;
  aOffset->y = offset.y;

  return fboId;
}

void DCLayerTree::DestroyEGLSurface() {
  const auto gl = GetGLContext();

  if (mColorRBO) {
    gl->fDeleteRenderbuffers(1, &mColorRBO);
    mColorRBO = 0;
  }

  if (mEGLImage) {
    const auto& gle = gl::GLContextEGL::Cast(gl);
    const auto& egl = gle->mEgl;
    egl->fDestroyImage(mEGLImage);
    mEGLImage = EGL_NO_IMAGE;
  }
}

// -

}  // namespace wr
namespace gfx {

color::ColorProfileDesc QueryOutputColorProfile() {
  // GPU process can't simply init gfxPlatform, (and we don't need most of it)
  // but we do need gfxPlatform::GetCMSOutputProfile().
  // So we steal what we need through the window:
  const auto outputProfileData =
      gfxWindowsPlatform::GetPlatformCMSOutputProfileData_Impl();

  const auto qcmsProfile = qcms_profile_from_memory(
      outputProfileData.Elements(), outputProfileData.Length());
  const auto release = MakeScopeExit([&]() {
    if (qcmsProfile) {
      qcms_profile_release(qcmsProfile);
    }
  });

  const bool print = gfxEnv::MOZ_GL_SPEW();

  const auto ret = [&]() {
    if (qcmsProfile) {
      return color::ColorProfileDesc::From(*qcmsProfile);
    }
    if (print) {
      printf_stderr(
          "Missing or failed to load display color profile, defaulting to "
          "sRGB.\n");
    }
    const auto MISSING_PROFILE_DEFAULT_SPACE = color::ColorspaceDesc{
        color::Chromaticities::Srgb(),
        color::TransferFunctionDesc::Srgb(),
    };
    return color::ColorProfileDesc::From(MISSING_PROFILE_DEFAULT_SPACE);
  }();

  if (print) {
    const auto gammaGuess = color::GuessGamma(ret.linearFromTf.r);
    printf_stderr(
        "Display profile:\n"
        "  Approx Gamma: %f\n"
        "  XYZ-D65 Red  : %f, %f, %f\n"
        "  XYZ-D65 Green: %f, %f, %f\n"
        "  XYZ-D65 Blue : %f, %f, %f\n",
        gammaGuess, ret.xyzd65FromLinearRgb.at(0, 0),
        ret.xyzd65FromLinearRgb.at(0, 1), ret.xyzd65FromLinearRgb.at(0, 2),

        ret.xyzd65FromLinearRgb.at(1, 0), ret.xyzd65FromLinearRgb.at(1, 1),
        ret.xyzd65FromLinearRgb.at(1, 2),

        ret.xyzd65FromLinearRgb.at(2, 0), ret.xyzd65FromLinearRgb.at(2, 1),
        ret.xyzd65FromLinearRgb.at(2, 2));
  }

  return ret;
}

}  // namespace gfx
namespace wr {

inline D2D1_MATRIX_5X4_F to_D2D1_MATRIX_5X4_F(const color::mat4& m) {
  return D2D1_MATRIX_5X4_F{{{
      m.rows[0][0],
      m.rows[1][0],
      m.rows[2][0],
      m.rows[3][0],
      m.rows[0][1],
      m.rows[1][1],
      m.rows[2][1],
      m.rows[3][1],
      m.rows[0][2],
      m.rows[1][2],
      m.rows[2][2],
      m.rows[3][2],
      m.rows[0][3],
      m.rows[1][3],
      m.rows[2][3],
      m.rows[3][3],
      0,
      0,
      0,
      0,
  }}};
}

ColorManagementChain ColorManagementChain::From(
    IDCompositionDevice3& dcomp,
    const color::ColorProfileConversionDesc& conv) {
  auto ret = ColorManagementChain{};

  const auto Append = [&](const RefPtr<IDCompositionFilterEffect>& afterLast) {
    if (ret.last) {
      afterLast->SetInput(0, ret.last, 0);
    }
    ret.last = afterLast;
  };

  const auto MaybeAppendColorMatrix = [&](const color::mat4& m) {
    RefPtr<IDCompositionColorMatrixEffect> e;
    if (approx(m, color::mat4::Identity())) return e;
    dcomp.CreateColorMatrixEffect(getter_AddRefs(e));
    MOZ_ASSERT(e);
    if (!e) return e;
    e->SetMatrix(to_D2D1_MATRIX_5X4_F(m));
    Append(e);
    return e;
  };
  const auto MaybeAppendTableTransfer = [&](const color::RgbTransferTables& t) {
    RefPtr<IDCompositionTableTransferEffect> e;
    if (!t.r.size() && !t.g.size() && !t.b.size()) return e;
    dcomp.CreateTableTransferEffect(getter_AddRefs(e));
    MOZ_ASSERT(e);
    if (!e) return e;
    e->SetRedTable(t.r.data(), t.r.size());
    e->SetGreenTable(t.g.data(), t.g.size());
    e->SetBlueTable(t.b.data(), t.b.size());
    Append(e);
    return e;
  };

  ret.srcRgbFromSrcYuv = MaybeAppendColorMatrix(conv.srcRgbFromSrcYuv);
  ret.srcLinearFromSrcTf = MaybeAppendTableTransfer(conv.srcLinearFromSrcTf);
  ret.dstLinearFromSrcLinear =
      MaybeAppendColorMatrix(color::mat4(conv.dstLinearFromSrcLinear));
  ret.dstTfFromDstLinear = MaybeAppendTableTransfer(conv.dstTfFromDstLinear);

  return ret;
}

ColorManagementChain::~ColorManagementChain() = default;

}  // namespace wr
}  // namespace mozilla

#undef LOG_H
