Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

SSL DCLayerTree.cpp

  Sprache: C
 

/* 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(100100);
  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, falsefalse, 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.00.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, 11,
      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(¶ms);
      params.DirtyRectsCount = rectsCount;
      params.pDirtyRects = rects.data();

      hr = mSwapChain->Present1(0, flags, ¶ms);
      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, falsefalse, 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 = {00};
  }

  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, falsefalse, 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"00,
        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"00,
        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[] = {012023};
    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, 00,
         D3D11_INPUT_PER_VERTEX_DATA, 0},
        {"TEXCOORD"0, DXGI_FORMAT_R32G32_FLOAT, 016,
         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(01, savedVertexBuffers, vertexBufferStrides,
                                vertexBufferOffsets);
    context->IASetInputLayout(inputLayout);
    context->IASetIndexBuffer(indexBuffer, indexBufferFormat,
                              indexBufferOffset);
    context->IASetPrimitiveTopology(primitiveTopology);
    context->VSSetShader(vertexShader, savedVertexShaderClassInstances,
                         vertexShaderClassInstanceCount);
    context->VSSetShaderResources(02, savedVertexShaderResources);
    context->VSSetConstantBuffers(01, savedVertexConstantBuffers);
    context->PSSetShader(pixelShader, savedPixelShaderClassInstances,
                         pixelShaderClassInstanceCount);
    context->PSSetShaderResources(02, savedPixelShaderResources);
    context->PSSetConstantBuffers(01, savedPixelConstantBuffers);
    context->PSSetSamplers(02, 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],
                             00);

  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,
                             00);

  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(01, 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(00, nullptr);
    context->VSSetShaderResources(00, nullptr);
    context->VSSetConstantBuffers(00, nullptr);
    context->PSSetShader(mShaderBltPixelShader, nullptr, 0);
    context->PSSetSamplers(02, samplers);
    context->PSSetShaderResources(02, srvs);
    context->PSSetConstantBuffers(01, 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(40);

    // 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, 01,
                                       &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, 01,
                                           &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, 01,
                                           &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, ¤tFboId);
  gl->fGetIntegerv(LOCAL_GL_RENDERBUFFER_BINDING, ¤tRboId);

  // 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(00),
        ret.xyzd65FromLinearRgb.at(01), ret.xyzd65FromLinearRgb.at(02),

        ret.xyzd65FromLinearRgb.at(10), ret.xyzd65FromLinearRgb.at(11),
        ret.xyzd65FromLinearRgb.at(12),

        ret.xyzd65FromLinearRgb.at(20), ret.xyzd65FromLinearRgb.at(21),
        ret.xyzd65FromLinearRgb.at(22));
  }

  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

Messung V0.5 in Prozent
C=94 H=94 G=93

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.71Angebot  ¤

*Eine klare Vorstellung vom Zielzustand






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....

Besucher

Besucher

Statistik
#Sources=277311
#Domains=752002