/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "DeviceManagerDx.h"
#include "D3D11Checks.h"
#include "gfxConfig.h"
#include "GfxDriverInfo.h"
#include "gfxWindowsPlatform.h"
#include "mozilla/D3DMessageUtils.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/StaticPrefs_layers.h"
#include "mozilla/Telemetry.h"
#include "mozilla/WindowsVersion.h"
#include "mozilla/gfx/GPUParent.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/gfx/GraphicsMessages.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/layers/CompositorBridgeChild.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/layers/DeviceAttachmentsD3D11.h"
#include "mozilla/Preferences.h"
#include "nsPrintfCString.h"
#include "nsString.h"

// -

#include "mozilla/gfx/AllOfDcomp.h"
#include <d3d11.h>
#include <ddraw.h>
#include <dxgi.h>

namespace mozilla {
namespace gfx {

using namespace mozilla::widget;
using namespace mozilla::layers;

StaticAutoPtr<DeviceManagerDx> DeviceManagerDx::sInstance;

// We don't have access to the D3D11CreateDevice type in gfxWindowsPlatform.h,
// since it doesn't include d3d11.h, so we use a static here. It should only
// be used within InitializeD3D11.
decltype(D3D11CreateDevice)* sD3D11CreateDeviceFn = nullptr;

// It should only be used within CreateDirectCompositionDevice.
decltype(DCompositionCreateDevice2)* sDcompCreateDevice2Fn = nullptr;
decltype(DCompositionCreateDevice3)* sDcompCreateDevice3Fn = nullptr;

// It should only be used within CreateDCompSurfaceHandle
decltype(DCompositionCreateSurfaceHandle)* sDcompCreateSurfaceHandleFn =
    nullptr;

// We don't have access to the DirectDrawCreateEx type in gfxWindowsPlatform.h,
// since it doesn't include ddraw.h, so we use a static here. It should only
// be used within InitializeDirectDrawConfig.
decltype(DirectDrawCreateEx)* sDirectDrawCreateExFn = nullptr;

/* static */
void DeviceManagerDx::Init() { sInstance = new DeviceManagerDx(); }

/* static */
void DeviceManagerDx::Shutdown() { sInstance = nullptr; }

DeviceManagerDx::DeviceManagerDx()
    : mDeviceLock("gfxWindowsPlatform.mDeviceLock"),
      mCompositorDeviceSupportsVideo(false) {
  // Set up the D3D11 feature levels we can ask for.
  if (IsWin8OrLater()) {
    mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_11_1);
  }
  mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_11_0);
  mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_10_1);
  mFeatureLevels.AppendElement(D3D_FEATURE_LEVEL_10_0);
  MOZ_COUNT_CTOR(DeviceManagerDx);
}

DeviceManagerDx::~DeviceManagerDx() { MOZ_COUNT_DTOR(DeviceManagerDx); }

bool DeviceManagerDx::LoadD3D11() {
  FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING);
  MOZ_ASSERT(d3d11.IsEnabled());

  if (sD3D11CreateDeviceFn) {
    return true;
  }

  nsModuleHandle module(LoadLibrarySystem32(L"d3d11.dll"));
  if (!module) {
    d3d11.SetFailed(FeatureStatus::Unavailable,
                    "Direct3D11 not available on this computer",
                    "FEATURE_FAILURE_D3D11_LIB"_ns);
    return false;
  }

  sD3D11CreateDeviceFn =
      (decltype(D3D11CreateDevice)*)GetProcAddress(module, "D3D11CreateDevice");
  if (!sD3D11CreateDeviceFn) {
    // We should just be on Windows Vista or XP in this case.
    d3d11.SetFailed(FeatureStatus::Unavailable,
                    "Direct3D11 not available on this computer",
                    "FEATURE_FAILURE_D3D11_FUNCPTR"_ns);
    return false;
  }

  mD3D11Module.steal(module);
  return true;
}

bool DeviceManagerDx::LoadDcomp() {
  MOZ_ASSERT(gfxConfig::GetFeature(Feature::D3D11_COMPOSITING).IsEnabled());
  MOZ_ASSERT(gfxVars::UseWebRenderANGLE());
  MOZ_ASSERT(gfxVars::UseWebRenderDCompWin());

  if (sDcompCreateDevice2Fn) {
    return true;  // Already loaded.
  }

  nsModuleHandle module(LoadLibrarySystem32(L"dcomp.dll"));
  if (!module) {
    return false;
  }

  sDcompCreateDevice2Fn = (decltype(DCompositionCreateDevice2)*)GetProcAddress(
      module, "DCompositionCreateDevice2");
  sDcompCreateDevice3Fn = (decltype(DCompositionCreateDevice3)*)GetProcAddress(
      module, "DCompositionCreateDevice3");
  if (!sDcompCreateDevice2Fn) {
    return false;
  }

  // Load optional API for external compositing
  sDcompCreateSurfaceHandleFn =
      (decltype(DCompositionCreateSurfaceHandle)*)::GetProcAddress(
          module, "DCompositionCreateSurfaceHandle");

  mDcompModule.steal(module);
  return true;
}

void DeviceManagerDx::ReleaseD3D11() {
  MOZ_ASSERT(!mCompositorDevice);
  MOZ_ASSERT(!mContentDevice);
  MOZ_ASSERT(!mVRDevice);
  MOZ_ASSERT(!mDecoderDevice);

  mD3D11Module.reset();
  sD3D11CreateDeviceFn = nullptr;
}

nsTArray<DXGI_OUTPUT_DESC1> DeviceManagerDx::EnumerateOutputs() {
  RefPtr<IDXGIAdapter> adapter = GetDXGIAdapter();

  if (!adapter) {
    NS_WARNING("Failed to acquire a DXGI adapter for enumerating outputs.");
    return nsTArray<DXGI_OUTPUT_DESC1>();
  }

  nsTArray<DXGI_OUTPUT_DESC1> outputs;
  for (UINT i = 0;; ++i) {
    RefPtr<IDXGIOutput> output = nullptr;
    if (FAILED(adapter->EnumOutputs(i, getter_AddRefs(output)))) {
      break;
    }

    RefPtr<IDXGIOutput6> output6 = nullptr;
    if (FAILED(output->QueryInterface(__uuidof(IDXGIOutput6),
                                      getter_AddRefs(output6)))) {
      break;
    }

    DXGI_OUTPUT_DESC1 desc;
    if (FAILED(output6->GetDesc1(&desc))) {
      break;
    }

    outputs.AppendElement(desc);
  }
  return outputs;
}

bool DeviceManagerDx::GetOutputFromMonitor(HMONITOR monitor,
                                           RefPtr<IDXGIOutput>* aOutOutput) {
  RefPtr<IDXGIAdapter> adapter = GetDXGIAdapter();

  if (!adapter) {
    NS_WARNING("Failed to acquire a DXGI adapter for GetOutputFromMonitor.");
    return false;
  }

  for (UINT i = 0;; ++i) {
    RefPtr<IDXGIOutput> output = nullptr;
    if (FAILED(adapter->EnumOutputs(i, getter_AddRefs(output)))) {
      break;
    }

    DXGI_OUTPUT_DESC desc;
    if (FAILED(output->GetDesc(&desc))) {
      continue;
    }

    if (desc.Monitor == monitor) {
      *aOutOutput = output;
      return true;
    }
  }
  return false;
}

void DeviceManagerDx::CheckHardwareStretchingSupport(HwStretchingSupport& aRv) {
  RefPtr<IDXGIAdapter> adapter = GetDXGIAdapter();

  if (!adapter) {
    NS_WARNING(
        "Failed to acquire a DXGI adapter for checking hardware stretching "
        "support.");
    ++aRv.mError;
    return;
  }

  for (UINT i = 0;; ++i) {
    RefPtr<IDXGIOutput> output = nullptr;
    HRESULT result = adapter->EnumOutputs(i, getter_AddRefs(output));
    if (result == DXGI_ERROR_NOT_FOUND) {
      // No more outputs to check.
      break;
    }

    if (FAILED(result)) {
      ++aRv.mError;
      break;
    }

    RefPtr<IDXGIOutput6> output6 = nullptr;
    if (FAILED(output->QueryInterface(__uuidof(IDXGIOutput6),
                                      getter_AddRefs(output6)))) {
      ++aRv.mError;
      continue;
    }

    UINT flags = 0;
    if (FAILED(output6->CheckHardwareCompositionSupport(&flags))) {
      ++aRv.mError;
      continue;
    }

    bool fullScreen = flags & DXGI_HARDWARE_COMPOSITION_SUPPORT_FLAG_FULLSCREEN;
    bool window = flags & DXGI_HARDWARE_COMPOSITION_SUPPORT_FLAG_WINDOWED;
    if (fullScreen && window) {
      ++aRv.mBoth;
    } else if (fullScreen) {
      ++aRv.mFullScreenOnly;
    } else if (window) {
      ++aRv.mWindowOnly;
    } else {
      ++aRv.mNone;
    }
  }
}

#ifdef DEBUG
static inline bool ProcessOwnsCompositor() {
  return XRE_GetProcessType() == GeckoProcessType_GPU ||
         XRE_GetProcessType() == GeckoProcessType_VR ||
         (XRE_IsParentProcess() && !gfxConfig::IsEnabled(Feature::GPU_PROCESS));
}
#endif

bool DeviceManagerDx::CreateCompositorDevices() {
  MutexAutoLock lock(mDeviceLock);
  return CreateCompositorDevicesLocked();
}

bool DeviceManagerDx::CreateCompositorDevicesLocked() {
  MOZ_ASSERT(ProcessOwnsCompositor());

  FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING);
  MOZ_ASSERT(d3d11.IsEnabled());

  if (int32_t sleepSec =
          StaticPrefs::gfx_direct3d11_sleep_on_create_device_AtStartup()) {
    printf_stderr("Attach to PID: %lu\n", GetCurrentProcessId());
    Sleep(sleepSec * 1000);
  }

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

  CreateCompositorDevice(d3d11);

  if (!d3d11.IsEnabled()) {
    MOZ_ASSERT(!mCompositorDevice);
    ReleaseD3D11();

    return false;
  }

  // We leak these everywhere and we need them our entire runtime anyway, let's
  // leak it here as well. We keep the pointer to sD3D11CreateDeviceFn around
  // as well for D2D1 and device resets.
  mD3D11Module.disown();

  MOZ_ASSERT(mCompositorDevice);
  if (!d3d11.IsEnabled()) {
    return false;
  }

  // When WR is used, do not preload attachments for D3D11 Non-WR compositor.
  //
  // Fallback from WR to D3D11 Non-WR compositor without re-creating gpu process
  // could happen when WR causes error. In this case, the attachments are loaded
  // synchronously.
  if (gfx::gfxVars::UseSoftwareWebRender()) {
    PreloadAttachmentsOnCompositorThread();
  }

  return true;
}

bool DeviceManagerDx::CreateVRDevice() {
  MOZ_ASSERT(ProcessOwnsCompositor());

  if (mVRDevice) {
    return true;
  }

  if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
    NS_WARNING("Direct3D11 Compositing required for VR");
    return false;
  }

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

  RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapterLocked();
  if (!adapter) {
    NS_WARNING("Failed to acquire a DXGI adapter for VR");
    return false;
  }

  UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

  HRESULT hr;
  if (!CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr, mVRDevice)) {
    gfxCriticalError() << "Crash during D3D11 device creation for VR";
    return false;
  }

  if (FAILED(hr) || !mVRDevice) {
    NS_WARNING("Failed to acquire a D3D11 device for VR");
    return false;
  }

  return true;
}

bool DeviceManagerDx::CreateCanvasDevice() {
  MutexAutoLock lock(mDeviceLock);
  return CreateCanvasDeviceLocked();
}

bool DeviceManagerDx::CreateCanvasDeviceLocked() {
  MOZ_ASSERT(ProcessOwnsCompositor());

  if (mCanvasDevice) {
    return true;
  }

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

  RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapterLocked();
  if (!adapter) {
    NS_WARNING("Failed to acquire a DXGI adapter for Canvas");
    return false;
  }

  UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

  HRESULT hr;
  if (!CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr,
                    mCanvasDevice)) {
    gfxCriticalError() << "Crash during D3D11 device creation for Canvas";
    return false;
  }

  if (StaticPrefs::
          gfx_direct2d_target_independent_rasterization_disabled_AtStartup()) {
    int creationFlags = 0x2;  // disable target independent rasterization
    const GUID D2D_INTERNAL_DEVICE_CREATION_OPTIONS = {
        0xfb3a8e1a,
        0x2e3c,
        0x4de1,
        {0x84, 0x42, 0x40, 0x43, 0xe0, 0xb0, 0x94, 0x95}};
    mCanvasDevice->SetPrivateData(D2D_INTERNAL_DEVICE_CREATION_OPTIONS,
                                  sizeof(creationFlags), &creationFlags);
  }

  if (FAILED(hr) || !mCanvasDevice) {
    NS_WARNING("Failed to acquire a D3D11 device for Canvas");
    return false;
  }

  if (!D3D11Checks::DoesTextureSharingWork(mCanvasDevice)) {
    mCanvasDevice = nullptr;
    return false;
  }

  if (XRE_IsGPUProcess()) {
    Factory::SetDirect3D11Device(mCanvasDevice);
  }

  return true;
}

void DeviceManagerDx::CreateDirectCompositionDevice() {
  MutexAutoLock lock(mDeviceLock);
  CreateDirectCompositionDeviceLocked();
}

void DeviceManagerDx::CreateDirectCompositionDeviceLocked() {
  if (!gfxVars::UseWebRenderDCompWin()) {
    return;
  }

  if (!mCompositorDevice) {
    return;
  }

  if (!LoadDcomp()) {
    return;
  }

  RefPtr<IDXGIDevice> dxgiDevice;
  if (mCompositorDevice->QueryInterface(
          IID_PPV_ARGS((IDXGIDevice**)getter_AddRefs(dxgiDevice))) != S_OK) {
    return;
  }

  HRESULT hr;
  RefPtr<IDCompositionDesktopDevice> desktopDevice;
  MOZ_SEH_TRY {
    hr = sDcompCreateDevice3Fn(
        dxgiDevice.get(),
        IID_PPV_ARGS(
            (IDCompositionDesktopDevice**)getter_AddRefs(desktopDevice)));
    if (!desktopDevice) {
      hr = sDcompCreateDevice2Fn(
          dxgiDevice.get(),
          IID_PPV_ARGS(
              (IDCompositionDesktopDevice**)getter_AddRefs(desktopDevice)));
    }
  }
  MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { return; }

  if (!SUCCEEDED(hr)) {
    return;
  }

  RefPtr<IDCompositionDevice2> compositionDevice;
  if (desktopDevice->QueryInterface(IID_PPV_ARGS(
          (IDCompositionDevice2**)getter_AddRefs(compositionDevice))) != S_OK) {
    return;
  }

  mDirectCompositionDevice = compositionDevice;
}

/* static */
HANDLE DeviceManagerDx::CreateDCompSurfaceHandle() {
  if (!sDcompCreateSurfaceHandleFn) {
    return 0;
  }

  HANDLE handle = 0;
  HRESULT hr = sDcompCreateSurfaceHandleFn(COMPOSITIONOBJECT_ALL_ACCESS,
                                           nullptr, &handle);
  if (FAILED(hr)) {
    return 0;
  }

  return handle;
}

void DeviceManagerDx::ImportDeviceInfo(const D3D11DeviceStatus& aDeviceStatus) {
  MOZ_ASSERT(!ProcessOwnsCompositor());

  MutexAutoLock lock(mDeviceLock);
  mDeviceStatus = Some(aDeviceStatus);
}

bool DeviceManagerDx::ExportDeviceInfo(D3D11DeviceStatus* aOut) {
  MutexAutoLock lock(mDeviceLock);
  if (mDeviceStatus) {
    *aOut = mDeviceStatus.value();
    return true;
  }

  return false;
}

void DeviceManagerDx::CreateContentDevices() {
  MutexAutoLock lock(mDeviceLock);
  CreateContentDevicesLocked();
}

void DeviceManagerDx::CreateContentDevicesLocked() {
  MOZ_ASSERT(gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING));

  if (!LoadD3D11()) {
    return;
  }

  // We should have been assigned a DeviceStatus from the parent process,
  // GPU process, or the same process if using in-process compositing.
  MOZ_ASSERT(mDeviceStatus);

  if (CreateContentDevice() == FeatureStatus::CrashedInHandler) {
    DisableD3D11AfterCrash();
  }
}

already_AddRefed<IDXGIAdapter1> DeviceManagerDx::GetDXGIAdapter() {
  MutexAutoLock lock(mDeviceLock);
  return do_AddRef(GetDXGIAdapterLocked());
}

IDXGIAdapter1* DeviceManagerDx::GetDXGIAdapterLocked() {
  if (mAdapter) {
    return mAdapter;
  }

  nsModuleHandle dxgiModule(LoadLibrarySystem32(L"dxgi.dll"));
  decltype(CreateDXGIFactory1)* createDXGIFactory1 =
      (decltype(CreateDXGIFactory1)*)GetProcAddress(dxgiModule,
                                                    "CreateDXGIFactory1");
  if (!createDXGIFactory1) {
    return nullptr;
  }
  static const auto fCreateDXGIFactory2 =
      (decltype(CreateDXGIFactory2)*)GetProcAddress(dxgiModule,
                                                    "CreateDXGIFactory2");

  // Try to use a DXGI 1.1 adapter in order to share resources
  // across processes.
  RefPtr<IDXGIFactory1> factory1;
  if (StaticPrefs::gfx_direct3d11_enable_debug_layer_AtStartup()) {
    RefPtr<IDXGIFactory2> factory2;
    if (fCreateDXGIFactory2) {
      auto hr = fCreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG,
                                    __uuidof(IDXGIFactory2),
                                    getter_AddRefs(factory2));
      MOZ_ALWAYS_TRUE(!FAILED(hr));
    } else {
      NS_WARNING(
          "fCreateDXGIFactory2 not loaded, cannot create debug IDXGIFactory2.");
    }
    factory1 = factory2;
  }
  if (!factory1) {
    HRESULT hr =
        createDXGIFactory1(__uuidof(IDXGIFactory1), getter_AddRefs(factory1));
    if (FAILED(hr) || !factory1) {
      // This seems to happen with some people running the iZ3D driver.
      // They won't get acceleration.
      return nullptr;
    }
  }

  if (!mDeviceStatus) {
    // If we haven't created a device yet, and have no existing device status,
    // then this must be the compositor device. Pick the first adapter we can.
    if (FAILED(factory1->EnumAdapters1(0, getter_AddRefs(mAdapter)))) {
      return nullptr;
    }
  } else {
    // In the UI and GPU process, we clear mDeviceStatus on device reset, so we
    // should never reach here. Furthermore, the UI process does not create
    // devices when using a GPU process.
    //
    // So, this should only ever get called on the content process or RDD
    // process
    MOZ_ASSERT(XRE_IsContentProcess() || XRE_IsRDDProcess());

    // In the child process, we search for the adapter that matches the parent
    // process. The first adapter can be mismatched on dual-GPU systems.
    for (UINT index = 0;; index++) {
      RefPtr<IDXGIAdapter1> adapter;
      if (FAILED(factory1->EnumAdapters1(index, getter_AddRefs(adapter)))) {
        break;
      }

      const DxgiAdapterDesc& preferred = mDeviceStatus->adapter();

      DXGI_ADAPTER_DESC desc;
      if (SUCCEEDED(adapter->GetDesc(&desc)) &&
          desc.AdapterLuid.HighPart == preferred.AdapterLuid.HighPart &&
          desc.AdapterLuid.LowPart == preferred.AdapterLuid.LowPart &&
          desc.VendorId == preferred.VendorId &&
          desc.DeviceId == preferred.DeviceId) {
        mAdapter = adapter.forget();
        break;
      }
    }
  }

  if (!mAdapter) {
    return nullptr;
  }

  // We leak this module everywhere, we might as well do so here as well.
  dxgiModule.disown();
  return mAdapter;
}

bool DeviceManagerDx::CreateCompositorDeviceHelper(
    FeatureState& aD3d11, IDXGIAdapter1* aAdapter, bool aAttemptVideoSupport,
    RefPtr<ID3D11Device>& aOutDevice) {
  // Check if a failure was injected for testing.
  if (StaticPrefs::gfx_testing_device_fail()) {
    aD3d11.SetFailed(FeatureStatus::Failed,
                     "Direct3D11 device failure simulated by preference",
                     "FEATURE_FAILURE_D3D11_SIM"_ns);
    return false;
  }

  UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

  DXGI_ADAPTER_DESC desc;
  aAdapter->GetDesc(&desc);
  if (desc.VendorId != 0x1414) {
    // 0x1414 is Microsoft (e.g. WARP)
    // When not using WARP, use
    // D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS to prevent
    // bug 1092260. IE 11 also uses this flag.
    flags |= D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS;
  }

  if (aAttemptVideoSupport) {
    flags |= D3D11_CREATE_DEVICE_VIDEO_SUPPORT;
  }

  HRESULT hr;
  RefPtr<ID3D11Device> device;
  if (!CreateDevice(aAdapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr, device)) {
    if (!aAttemptVideoSupport) {
      gfxCriticalError() << "Crash during D3D11 device creation";
      aD3d11.SetFailed(FeatureStatus::CrashedInHandler,
                       "Crashed trying to acquire a D3D11 device",
                       "FEATURE_FAILURE_D3D11_DEVICE1"_ns);
    }
    return false;
  }

  if (FAILED(hr) || !device) {
    if (!aAttemptVideoSupport) {
      aD3d11.SetFailed(FeatureStatus::Failed,
                       "Failed to acquire a D3D11 device",
                       "FEATURE_FAILURE_D3D11_DEVICE2"_ns);
    }
    return false;
  }
  if (!D3D11Checks::DoesDeviceWork()) {
    if (!aAttemptVideoSupport) {
      aD3d11.SetFailed(FeatureStatus::Broken,
                       "Direct3D11 device was determined to be broken",
                       "FEATURE_FAILURE_D3D11_BROKEN"_ns);
    }
    return false;
  }

  aOutDevice = device;
  return true;
}

// Note that it's enough for us to just use a counter for a unique ID,
// even though the counter isn't synchronized between processes. If we
// start in the GPU process and wind up in the parent process, the
// whole graphics stack is blown away anyway. But just in case, we
// make gpu process IDs negative and parent process IDs positive.
static inline int32_t GetNextDeviceCounter() {
  static int32_t sDeviceCounter = 0;
  return XRE_IsGPUProcess() ? --sDeviceCounter : ++sDeviceCounter;
}

void DeviceManagerDx::CreateCompositorDevice(FeatureState& d3d11) {
  if (StaticPrefs::layers_d3d11_force_warp_AtStartup()) {
    CreateWARPCompositorDevice();
    return;
  }

  RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapterLocked();
  if (!adapter) {
    d3d11.SetFailed(FeatureStatus::Unavailable,
                    "Failed to acquire a DXGI adapter",
                    "FEATURE_FAILURE_D3D11_DXGI"_ns);
    return;
  }

  if (XRE_IsGPUProcess() && !D3D11Checks::DoesRemotePresentWork(adapter)) {
    d3d11.SetFailed(FeatureStatus::Unavailable,
                    "DXGI does not support out-of-process presentation",
                    "FEATURE_FAILURE_D3D11_REMOTE_PRESENT"_ns);
    return;
  }

  RefPtr<ID3D11Device> device;
  if (!CreateCompositorDeviceHelper(d3d11, adapter, true, device)) {
    // Try again without video support and record that it failed.
    mCompositorDeviceSupportsVideo = false;
    if (!CreateCompositorDeviceHelper(d3d11, adapter, false, device)) {
      return;
    }
  } else {
    mCompositorDeviceSupportsVideo = true;
  }

  // Only test this when not using WARP since it can fail and cause
  // GetDeviceRemovedReason to return weird values.
  bool textureSharingWorks = D3D11Checks::DoesTextureSharingWork(device);

  DXGI_ADAPTER_DESC desc;
  PodZero(&desc);
  adapter->GetDesc(&desc);

  if (!textureSharingWorks) {
    gfxConfig::SetFailed(Feature::D3D11_HW_ANGLE, FeatureStatus::Broken,
                         "Texture sharing doesn't work",
                         "FEATURE_FAILURE_HW_ANGLE_NEEDS_TEXTURE_SHARING"_ns);
  }
  if (D3D11Checks::DoesRenderTargetViewNeedRecreating(device)) {
    gfxConfig::SetFailed(Feature::D3D11_HW_ANGLE, FeatureStatus::Broken,
                         "RenderTargetViews need recreating",
                         "FEATURE_FAILURE_HW_ANGLE_NEEDS_RTV_RECREATION"_ns);
  }
  if (XRE_IsParentProcess()) {
    // It seems like this may only happen when we're using the NVIDIA gpu
    D3D11Checks::WarnOnAdapterMismatch(device);
  }

  uint32_t featureLevel = device->GetFeatureLevel();
  auto formatOptions = D3D11Checks::FormatOptions(device);
  mCompositorDevice = device;

  int32_t sequenceNumber = GetNextDeviceCounter();
  mDeviceStatus = Some(D3D11DeviceStatus(
      false, textureSharingWorks, featureLevel, DxgiAdapterDesc::From(desc),
      sequenceNumber, formatOptions));
  mCompositorDevice->SetExceptionMode(0);
}

bool DeviceManagerDx::CreateDevice(IDXGIAdapter* aAdapter,
                                   D3D_DRIVER_TYPE aDriverType, UINT aFlags,
                                   HRESULT& aResOut,
                                   RefPtr<ID3D11Device>& aOutDevice) {
  if (StaticPrefs::gfx_direct3d11_enable_debug_layer_AtStartup() ||
      StaticPrefs::gfx_direct3d11_break_on_error_AtStartup()) {
    aFlags |= D3D11_CREATE_DEVICE_DEBUG;
  }

  MOZ_SEH_TRY {
    aResOut = sD3D11CreateDeviceFn(
        aAdapter, aDriverType, nullptr, aFlags, mFeatureLevels.Elements(),
        mFeatureLevels.Length(), D3D11_SDK_VERSION, getter_AddRefs(aOutDevice),
        nullptr, nullptr);
  }
  MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { return false; }

  if (StaticPrefs::gfx_direct3d11_break_on_error_AtStartup()) {
    do {
      if (!aOutDevice) break;

      RefPtr<ID3D11Debug> debug;
      if (!SUCCEEDED(aOutDevice->QueryInterface(__uuidof(ID3D11Debug),
                                                getter_AddRefs(debug))))
        break;

      RefPtr<ID3D11InfoQueue> infoQueue;
      if (!SUCCEEDED(debug->QueryInterface(__uuidof(ID3D11InfoQueue),
                                           getter_AddRefs(infoQueue))))
        break;

      D3D11_INFO_QUEUE_FILTER filter;
      PodZero(&filter);

      // Disable warnings caused by Advanced Layers that are known and not
      // problematic.
      D3D11_MESSAGE_ID blockIDs[] = {
          D3D11_MESSAGE_ID_DEVICE_DRAW_CONSTANT_BUFFER_TOO_SMALL};
      filter.DenyList.NumIDs = MOZ_ARRAY_LENGTH(blockIDs);
      filter.DenyList.pIDList = blockIDs;
      infoQueue->PushStorageFilter(&filter);

      infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION, true);
      infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, true);
      infoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_WARNING, true);
    } while (false);
  }

  return true;
}

void DeviceManagerDx::CreateWARPCompositorDevice() {
  ScopedGfxFeatureReporter reporterWARP(
      "D3D11-WARP", StaticPrefs::layers_d3d11_force_warp_AtStartup());
  FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING);

  HRESULT hr;
  RefPtr<ID3D11Device> device;

  // Use D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS
  // to prevent bug 1092260. IE 11 also uses this flag.
  UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
  if (!CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, flags, hr, device)) {
    gfxCriticalError() << "Exception occurred initializing WARP D3D11 device!";
    d3d11.SetFailed(FeatureStatus::CrashedInHandler,
                    "Crashed creating a D3D11 WARP device",
                    "FEATURE_FAILURE_D3D11_WARP_DEVICE"_ns);
  }

  if (FAILED(hr) || !device) {
    // This should always succeed... in theory.
    gfxCriticalError() << "Failed to initialize WARP D3D11 device! "
                       << hexa(hr);
    d3d11.SetFailed(FeatureStatus::Failed,
                    "Failed to create a D3D11 WARP device",
                    "FEATURE_FAILURE_D3D11_WARP_DEVICE2"_ns);
    return;
  }

  // Only test for texture sharing on Windows 8 since it puts the device into
  // an unusable state if used on Windows 7
  bool textureSharingWorks = false;
  if (IsWin8OrLater()) {
    textureSharingWorks = D3D11Checks::DoesTextureSharingWork(device);
  }

  DXGI_ADAPTER_DESC desc;
  D3D11Checks::GetDxgiDesc(device, &desc);

  int featureLevel = device->GetFeatureLevel();

  auto formatOptions = D3D11Checks::FormatOptions(device);
  mCompositorDevice = device;

  int32_t sequenceNumber = GetNextDeviceCounter();
  mDeviceStatus = Some(D3D11DeviceStatus(
      true, textureSharingWorks, featureLevel, DxgiAdapterDesc::From(desc),
      sequenceNumber, formatOptions));
  mCompositorDevice->SetExceptionMode(0);

  reporterWARP.SetSuccessful();
}

FeatureStatus DeviceManagerDx::CreateContentDevice() {
  RefPtr<IDXGIAdapter1> adapter;
  if (!mDeviceStatus->isWARP()) {
    adapter = GetDXGIAdapterLocked();
    if (!adapter) {
      gfxCriticalNote << "Could not get a DXGI adapter";
      return FeatureStatus::Unavailable;
    }
  }

  HRESULT hr;
  RefPtr<ID3D11Device> device;

  UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
  D3D_DRIVER_TYPE type =
      mDeviceStatus->isWARP() ? D3D_DRIVER_TYPE_WARP : D3D_DRIVER_TYPE_UNKNOWN;
  if (!CreateDevice(adapter, type, flags, hr, device)) {
    gfxCriticalNote
        << "Recovered from crash while creating a D3D11 content device";
    gfxWindowsPlatform::RecordContentDeviceFailure(
        TelemetryDeviceCode::Content);
    return FeatureStatus::CrashedInHandler;
  }

  if (FAILED(hr) || !device) {
    gfxCriticalNote << "Failed to create a D3D11 content device: " << hexa(hr);
    gfxWindowsPlatform::RecordContentDeviceFailure(
        TelemetryDeviceCode::Content);
    return FeatureStatus::Failed;
  }

  // InitializeD2D() will abort early if the compositor device did not support
  // texture sharing. If we're in the content process, we can't rely on the
  // parent device alone: some systems have dual GPUs that are capable of
  // binding the parent and child processes to different GPUs. As a safety net,
  // we re-check texture sharing against the newly created D3D11 content device.
  // If it fails, we won't use Direct2D.
  if (XRE_IsContentProcess()) {
    if (!D3D11Checks::DoesTextureSharingWork(device)) {
      return FeatureStatus::Failed;
    }

    DebugOnly<bool> ok = ContentAdapterIsParentAdapter(device);
    MOZ_ASSERT(ok);
  }

  mContentDevice = device;
  mContentDevice->SetExceptionMode(0);

  RefPtr<ID3D10Multithread> multi;
  hr = mContentDevice->QueryInterface(__uuidof(ID3D10Multithread),
                                      getter_AddRefs(multi));
  if (SUCCEEDED(hr) && multi) {
    multi->SetMultithreadProtected(TRUE);
  }
  return FeatureStatus::Available;
}

RefPtr<ID3D11Device> DeviceManagerDx::CreateDecoderDevice(
    bool aHardwareWebRender) {
  MutexAutoLock lock(mDeviceLock);

  if (!mDeviceStatus) {
    return nullptr;
  }

  bool isAMD = mDeviceStatus->adapter().VendorId == 0x1002;
  bool reuseDevice = false;
  if (gfxVars::ReuseDecoderDevice()) {
    reuseDevice = true;
  } else if (isAMD) {
    reuseDevice = true;
    gfxCriticalNoteOnce << "Always have to reuse decoder device on AMD";
  }

  if (reuseDevice) {
    // Use mCompositorDevice for decoder device only for hardware WebRender.
    if (aHardwareWebRender && mCompositorDevice &&
        mCompositorDeviceSupportsVideo && !mDecoderDevice) {
      mDecoderDevice = mCompositorDevice;

      RefPtr<ID3D10Multithread> multi;
      mDecoderDevice->QueryInterface(__uuidof(ID3D10Multithread),
                                     getter_AddRefs(multi));
      if (multi) {
        multi->SetMultithreadProtected(TRUE);
      }
    }

    if (mDecoderDevice) {
      RefPtr<ID3D11Device> dev = mDecoderDevice;
      return dev.forget();
    }
  }

  if (!sD3D11CreateDeviceFn) {
    // We should just be on Windows Vista or XP in this case.
    return nullptr;
  }

  RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapterLocked();
  if (!adapter) {
    return nullptr;
  }

  HRESULT hr;
  RefPtr<ID3D11Device> device;

  UINT flags = D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS |
               D3D11_CREATE_DEVICE_VIDEO_SUPPORT;
  if (!CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, flags, hr, device)) {
    return nullptr;
  }
  if (FAILED(hr) || !device || !D3D11Checks::DoesDeviceWork()) {
    return nullptr;
  }

  RefPtr<ID3D10Multithread> multi;
  device->QueryInterface(__uuidof(ID3D10Multithread), getter_AddRefs(multi));
  if (multi) {
    multi->SetMultithreadProtected(TRUE);
  }
  if (reuseDevice) {
    mDecoderDevice = device;
  }
  return device;
}

// ID3D11DeviceChild, IDXGIObject and ID3D11Device implement SetPrivateData with
// the exact same parameters.
template <typename T>
static HRESULT SetDebugName(T* d3d11Object, const char* debugString) {
  return d3d11Object->SetPrivateData(WKPDID_D3DDebugObjectName,
                                     strlen(debugString), debugString);
}

RefPtr<ID3D11Device> DeviceManagerDx::CreateMediaEngineDevice() {
  MutexAutoLock lock(mDeviceLock);
  if (!LoadD3D11()) {
    return nullptr;
  }

  HRESULT hr;
  RefPtr<ID3D11Device> device;
  UINT flags = D3D11_CREATE_DEVICE_VIDEO_SUPPORT |
               D3D11_CREATE_DEVICE_BGRA_SUPPORT |
               D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS;
  if (!CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, flags, hr, device)) {
    return nullptr;
  }
  if (FAILED(hr) || !device || !D3D11Checks::DoesDeviceWork()) {
    return nullptr;
  }
  Unused << SetDebugName(device.get(), "MFMediaEngineDevice");

  RefPtr<ID3D10Multithread> multi;
  device->QueryInterface(__uuidof(ID3D10Multithread), getter_AddRefs(multi));
  if (multi) {
    multi->SetMultithreadProtected(TRUE);
  }
  return device;
}

void DeviceManagerDx::ResetDevices() {
  MutexAutoLock lock(mDeviceLock);
  ResetDevicesLocked();
}

void DeviceManagerDx::ResetDevicesLocked() {
  mAdapter = nullptr;
  mCompositorAttachments = nullptr;
  mCompositorDevice = nullptr;
  mContentDevice = nullptr;
  mCanvasDevice = nullptr;
  mImageDevice = nullptr;
  mVRDevice = nullptr;
  mDecoderDevice = nullptr;
  mDirectCompositionDevice = nullptr;
  mDeviceStatus = Nothing();
  mDeviceResetReason = Nothing();
  Factory::SetDirect3D11Device(nullptr);
}

bool DeviceManagerDx::MaybeResetAndReacquireDevices() {
  MutexAutoLock lock(mDeviceLock);

  DeviceResetReason resetReason;
  if (!HasDeviceResetLocked(&resetReason)) {
    return false;
  }

  GPUProcessManager::RecordDeviceReset(resetReason);

  bool createCompositorDevice = !!mCompositorDevice;
  bool createContentDevice = !!mContentDevice;
  bool createCanvasDevice = !!mCanvasDevice;
  bool createDirectCompositionDevice = !!mDirectCompositionDevice;

  ResetDevicesLocked();

  if (createCompositorDevice && !CreateCompositorDevicesLocked()) {
    // Just stop, don't try anything more
    return true;
  }
  if (createContentDevice) {
    CreateContentDevicesLocked();
  }
  if (createCanvasDevice) {
    CreateCanvasDeviceLocked();
  }
  if (createDirectCompositionDevice) {
    CreateDirectCompositionDeviceLocked();
  }

  return true;
}

bool DeviceManagerDx::ContentAdapterIsParentAdapter(ID3D11Device* device) {
  DXGI_ADAPTER_DESC desc;
  if (!D3D11Checks::GetDxgiDesc(device, &desc)) {
    gfxCriticalNote << "Could not query device DXGI adapter info";
    return false;
  }

  const DxgiAdapterDesc& preferred = mDeviceStatus->adapter();

  if (desc.VendorId != preferred.VendorId ||
      desc.DeviceId != preferred.DeviceId ||
      desc.SubSysId != preferred.SubSysId ||
      desc.AdapterLuid.HighPart != preferred.AdapterLuid.HighPart ||
      desc.AdapterLuid.LowPart != preferred.AdapterLuid.LowPart) {
    gfxCriticalNote << "VendorIDMismatch P " << hexa(preferred.VendorId) << " "
                    << hexa(desc.VendorId);
    return false;
  }

  return true;
}

static DeviceResetReason HResultToResetReason(HRESULT hr) {
  switch (hr) {
    case DXGI_ERROR_DEVICE_HUNG:
      return DeviceResetReason::HUNG;
    case DXGI_ERROR_DEVICE_REMOVED:
      return DeviceResetReason::REMOVED;
    case DXGI_ERROR_DEVICE_RESET:
      return DeviceResetReason::RESET;
    case DXGI_ERROR_DRIVER_INTERNAL_ERROR:
      return DeviceResetReason::DRIVER_ERROR;
    case DXGI_ERROR_INVALID_CALL:
      return DeviceResetReason::INVALID_CALL;
    case E_OUTOFMEMORY:
      return DeviceResetReason::OUT_OF_MEMORY;
    default:
      MOZ_ASSERT(false);
  }
  return DeviceResetReason::OTHER;
}

bool DeviceManagerDx::HasDeviceReset(DeviceResetReason* aOutReason) {
  MutexAutoLock lock(mDeviceLock);
  return HasDeviceResetLocked(aOutReason);
}

bool DeviceManagerDx::HasDeviceResetLocked(DeviceResetReason* aOutReason) {
  if (mDeviceResetReason) {
    if (aOutReason) {
      *aOutReason = mDeviceResetReason.value();
    }
    return true;
  }

  DeviceResetReason reason;
  if (GetAnyDeviceRemovedReason(&reason)) {
    mDeviceResetReason = Some(reason);
    if (aOutReason) {
      *aOutReason = reason;
    }
    return true;
  }

  return false;
}

static inline bool DidDeviceReset(const RefPtr<ID3D11Device>& aDevice,
                                  DeviceResetReason* aOutReason) {
  if (!aDevice) {
    return false;
  }
  HRESULT hr = aDevice->GetDeviceRemovedReason();
  if (hr == S_OK) {
    return false;
  }

  *aOutReason = HResultToResetReason(hr);
  return true;
}

bool DeviceManagerDx::GetAnyDeviceRemovedReason(DeviceResetReason* aOutReason) {
  if (DidDeviceReset(mCompositorDevice, aOutReason) ||
      DidDeviceReset(mContentDevice, aOutReason) ||
      DidDeviceReset(mCanvasDevice, aOutReason)) {
    return true;
  }

  if (XRE_IsParentProcess() && NS_IsMainThread() &&
      StaticPrefs::gfx_testing_device_reset()) {
    Preferences::SetInt("gfx.testing.device-reset", 0);
    *aOutReason = DeviceResetReason::FORCED_RESET;
    return true;
  }

  return false;
}

void DeviceManagerDx::ForceDeviceReset(ForcedDeviceResetReason aReason) {
  Telemetry::Accumulate(Telemetry::FORCED_DEVICE_RESET_REASON,
                        uint32_t(aReason));
  {
    MutexAutoLock lock(mDeviceLock);
    if (!mDeviceResetReason) {
      mDeviceResetReason = Some(DeviceResetReason::FORCED_RESET);
    }
  }
}

void DeviceManagerDx::DisableD3D11AfterCrash() {
  gfxConfig::Disable(Feature::D3D11_COMPOSITING,
                     FeatureStatus::CrashedInHandler,
                     "Crashed while acquiring a Direct3D11 device",
                     "FEATURE_FAILURE_D3D11_CRASH"_ns);
  ResetDevices();
}

RefPtr<ID3D11Device> DeviceManagerDx::GetCompositorDevice() {
  MutexAutoLock lock(mDeviceLock);
  return mCompositorDevice;
}

RefPtr<ID3D11Device> DeviceManagerDx::GetContentDevice() {
  MOZ_ASSERT(XRE_IsGPUProcess() ||
             gfxPlatform::GetPlatform()->DevicesInitialized());

  MutexAutoLock lock(mDeviceLock);
  return mContentDevice;
}

RefPtr<ID3D11Device> DeviceManagerDx::GetImageDevice() {
  MutexAutoLock lock(mDeviceLock);
  if (mImageDevice) {
    return mImageDevice;
  }

  RefPtr<ID3D11Device> device = mContentDevice;
  if (!device) {
    device = mCompositorDevice;
  }

  if (!device) {
    return nullptr;
  }

  RefPtr<ID3D10Multithread> multi;
  HRESULT hr =
      device->QueryInterface((ID3D10Multithread**)getter_AddRefs(multi));
  if (FAILED(hr) || !multi) {
    gfxWarning() << "Multithread safety interface not supported. " << hr;
    return nullptr;
  }
  multi->SetMultithreadProtected(TRUE);

  mImageDevice = device;

  return mImageDevice;
}

RefPtr<ID3D11Device> DeviceManagerDx::GetVRDevice() {
  MutexAutoLock lock(mDeviceLock);
  if (!mVRDevice) {
    CreateVRDevice();
  }
  return mVRDevice;
}

RefPtr<ID3D11Device> DeviceManagerDx::GetCanvasDevice() {
  MutexAutoLock lock(mDeviceLock);
  return mCanvasDevice;
}

RefPtr<IDCompositionDevice2> DeviceManagerDx::GetDirectCompositionDevice() {
  MutexAutoLock lock(mDeviceLock);
  return mDirectCompositionDevice;
}

unsigned DeviceManagerDx::GetCompositorFeatureLevel() const {
  MutexAutoLock lock(mDeviceLock);
  if (!mDeviceStatus) {
    return 0;
  }
  return mDeviceStatus->featureLevel();
}

bool DeviceManagerDx::TextureSharingWorks() {
  MutexAutoLock lock(mDeviceLock);
  if (!mDeviceStatus) {
    return false;
  }
  return mDeviceStatus->textureSharingWorks();
}

bool DeviceManagerDx::CanInitializeKeyedMutexTextures() {
  MutexAutoLock lock(mDeviceLock);
  return mDeviceStatus && StaticPrefs::gfx_direct3d11_allow_keyed_mutex() &&
         gfxVars::AllowD3D11KeyedMutex();
}

bool DeviceManagerDx::HasCrashyInitData() {
  MutexAutoLock lock(mDeviceLock);
  if (!mDeviceStatus) {
    return false;
  }

  return (mDeviceStatus->adapter().VendorId == 0x8086 && !IsWin10OrLater());
}

bool DeviceManagerDx::CheckRemotePresentSupport() {
  MOZ_ASSERT(XRE_IsParentProcess());

  RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapter();
  if (!adapter) {
    return false;
  }
  if (!D3D11Checks::DoesRemotePresentWork(adapter)) {
    return false;
  }
  return true;
}

bool DeviceManagerDx::IsWARP() {
  MutexAutoLock lock(mDeviceLock);
  if (!mDeviceStatus) {
    return false;
  }
  return mDeviceStatus->isWARP();
}

bool DeviceManagerDx::CanUseNV12() {
  MutexAutoLock lock(mDeviceLock);
  if (!mDeviceStatus) {
    return false;
  }
  return mDeviceStatus->formatOptions().contains(
      D3D11Checks::VideoFormatOption::NV12);
}

bool DeviceManagerDx::CanUseP010() {
  MutexAutoLock lock(mDeviceLock);
  if (!mDeviceStatus) {
    return false;
  }
  return mDeviceStatus->formatOptions().contains(
      D3D11Checks::VideoFormatOption::P010);
}

bool DeviceManagerDx::CanUseP016() {
  MutexAutoLock lock(mDeviceLock);
  if (!mDeviceStatus) {
    return false;
  }
  return mDeviceStatus->formatOptions().contains(
      D3D11Checks::VideoFormatOption::P016);
}

bool DeviceManagerDx::CanUseDComp() {
  MutexAutoLock lock(mDeviceLock);
  return !!mDirectCompositionDevice;
}

void DeviceManagerDx::InitializeDirectDraw() {
  MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread());

  if (mDirectDraw) {
    // Already initialized.
    return;
  }

  FeatureState& ddraw = gfxConfig::GetFeature(Feature::DIRECT_DRAW);
  if (!ddraw.IsEnabled()) {
    return;
  }

  // Check if DirectDraw is available on this system.
  mDirectDrawDLL.own(LoadLibrarySystem32(L"ddraw.dll"));
  if (!mDirectDrawDLL) {
    ddraw.SetFailed(FeatureStatus::Unavailable,
                    "DirectDraw not available on this computer",
                    "FEATURE_FAILURE_DDRAW_LIB"_ns);
    return;
  }

  sDirectDrawCreateExFn = (decltype(DirectDrawCreateEx)*)GetProcAddress(
      mDirectDrawDLL, "DirectDrawCreateEx");
  if (!sDirectDrawCreateExFn) {
    ddraw.SetFailed(FeatureStatus::Unavailable,
                    "DirectDraw not available on this computer",
                    "FEATURE_FAILURE_DDRAW_LIB"_ns);
    return;
  }

  HRESULT hr;
  MOZ_SEH_TRY {
    hr = sDirectDrawCreateExFn(nullptr, getter_AddRefs(mDirectDraw),
                               IID_IDirectDraw7, nullptr);
  }
  MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
    ddraw.SetFailed(FeatureStatus::Failed, "Failed to create DirectDraw",
                    "FEATURE_FAILURE_DDRAW_LIB"_ns);
    gfxCriticalNote << "DoesCreatingDirectDrawFailed";
    return;
  }
  if (FAILED(hr)) {
    ddraw.SetFailed(FeatureStatus::Failed, "Failed to create DirectDraw",
                    "FEATURE_FAILURE_DDRAW_LIB"_ns);
    gfxCriticalNote << "DoesCreatingDirectDrawFailed " << hexa(hr);
    return;
  }
}

IDirectDraw7* DeviceManagerDx::GetDirectDraw() { return mDirectDraw; }

void DeviceManagerDx::GetCompositorDevices(
    RefPtr<ID3D11Device>* aOutDevice,
    RefPtr<layers::DeviceAttachmentsD3D11>* aOutAttachments) {
  RefPtr<ID3D11Device> device;
  {
    MutexAutoLock lock(mDeviceLock);
    if (!mCompositorDevice) {
      return;
    }
    if (mCompositorAttachments) {
      *aOutDevice = mCompositorDevice;
      *aOutAttachments = mCompositorAttachments;
      return;
    }

    // Otherwise, we'll try to create attachments outside the lock.
    device = mCompositorDevice;
  }

  // We save the attachments object even if it fails to initialize, so the
  // compositor can grab the failure ID.
  RefPtr<layers::DeviceAttachmentsD3D11> attachments =
      layers::DeviceAttachmentsD3D11::Create(device);
  {
    MutexAutoLock lock(mDeviceLock);
    if (device != mCompositorDevice) {
      return;
    }
    mCompositorAttachments = attachments;
  }

  *aOutDevice = device;
  *aOutAttachments = attachments;
}

/* static */
void DeviceManagerDx::PreloadAttachmentsOnCompositorThread() {
  if (!CompositorThread()) {
    return;
  }

  RefPtr<Runnable> task = NS_NewRunnableFunction(
      "DeviceManagerDx::PreloadAttachmentsOnCompositorThread", []() -> void {
        if (DeviceManagerDx* dm = DeviceManagerDx::Get()) {
          RefPtr<ID3D11Device> device;
          RefPtr<layers::DeviceAttachmentsD3D11> attachments;
          dm->GetCompositorDevices(&device, &attachments);
        }
      });
  CompositorThread()->Dispatch(task.forget());
}

}  // namespace gfx
}  // namespace mozilla
