/* -*- 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/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 #include #include #include namespace mozilla { namespace gfx { using namespace mozilla::widget; using namespace mozilla::layers; StaticAutoPtr 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. 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 DeviceManagerDx::EnumerateOutputs() { RefPtr adapter = GetDXGIAdapter(); if (!adapter) { NS_WARNING("Failed to acquire a DXGI adapter for enumerating outputs."); return nsTArray(); } nsTArray outputs; for (UINT i = 0;; ++i) { RefPtr output = nullptr; if (FAILED(adapter->EnumOutputs(i, getter_AddRefs(output)))) { break; } RefPtr 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* aOutOutput) { RefPtr adapter = GetDXGIAdapter(); if (!adapter) { NS_WARNING("Failed to acquire a DXGI adapter for GetOutputFromMonitor."); return false; } for (UINT i = 0;; ++i) { RefPtr 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 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 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 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 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 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 dxgiDevice; if (mCompositorDevice->QueryInterface( IID_PPV_ARGS((IDXGIDevice**)getter_AddRefs(dxgiDevice))) != S_OK) { return; } HRESULT hr; RefPtr 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 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 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 factory1; if (StaticPrefs::gfx_direct3d11_enable_debug_layer_AtStartup()) { RefPtr 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 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& 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 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 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 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); } RefPtr multi; HRESULT hr = device->QueryInterface(__uuidof(ID3D10Multithread), getter_AddRefs(multi)); if (SUCCEEDED(hr) && multi) { multi->SetMultithreadProtected(TRUE); } 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& 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 debug; if (!SUCCEEDED(aOutDevice->QueryInterface(__uuidof(ID3D11Debug), getter_AddRefs(debug)))) break; RefPtr 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 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; } bool textureSharingWorks = D3D11Checks::DoesTextureSharingWork(device); RefPtr multi; hr = device->QueryInterface(__uuidof(ID3D10Multithread), getter_AddRefs(multi)); if (SUCCEEDED(hr) && multi) { multi->SetMultithreadProtected(TRUE); } 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 adapter; if (!mDeviceStatus->isWARP()) { adapter = GetDXGIAdapterLocked(); if (!adapter) { gfxCriticalNote << "Could not get a DXGI adapter"; return FeatureStatus::Unavailable; } } HRESULT hr; RefPtr 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 ok = ContentAdapterIsParentAdapter(device); MOZ_ASSERT(ok); } mContentDevice = device; mContentDevice->SetExceptionMode(0); RefPtr multi; hr = mContentDevice->QueryInterface(__uuidof(ID3D10Multithread), getter_AddRefs(multi)); if (SUCCEEDED(hr) && multi) { multi->SetMultithreadProtected(TRUE); } return FeatureStatus::Available; } RefPtr 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 multi; mDecoderDevice->QueryInterface(__uuidof(ID3D10Multithread), getter_AddRefs(multi)); if (multi) { MOZ_ASSERT(multi->GetMultithreadProtected()); } } if (mDecoderDevice) { RefPtr dev = mDecoderDevice; return dev.forget(); } } if (!sD3D11CreateDeviceFn) { // We should just be on Windows Vista or XP in this case. return nullptr; } RefPtr adapter = GetDXGIAdapterLocked(); if (!adapter) { return nullptr; } HRESULT hr; RefPtr 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 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 static HRESULT SetDebugName(T* d3d11Object, const char* debugString) { return d3d11Object->SetPrivateData(WKPDID_D3DDebugObjectName, strlen(debugString), debugString); } RefPtr DeviceManagerDx::CreateMediaEngineDevice() { MutexAutoLock lock(mDeviceLock); if (!LoadD3D11()) { return nullptr; } HRESULT hr; RefPtr 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 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& 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 DeviceManagerDx::GetCompositorDevice() { /// ID3D11Device is thread-safe. We need the lock to read the /// mDeviceLockPointer, but manipulating the pointee outside of the lock is /// safe. See /// https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-render-multi-thread-intro MutexAutoLock lock(mDeviceLock); return mCompositorDevice; } RefPtr DeviceManagerDx::GetContentDevice() { MOZ_ASSERT(XRE_IsGPUProcess() || gfxPlatform::GetPlatform()->DevicesInitialized()); MutexAutoLock lock(mDeviceLock); return mContentDevice; } RefPtr DeviceManagerDx::GetImageDevice() { MutexAutoLock lock(mDeviceLock); if (mImageDevice) { return mImageDevice; } RefPtr device = mContentDevice; if (!device) { device = mCompositorDevice; } if (!device) { return nullptr; } RefPtr multi; HRESULT hr = device->QueryInterface((ID3D10Multithread**)getter_AddRefs(multi)); if (FAILED(hr) || !multi) { gfxWarning() << "Multithread safety interface not supported. " << hr; return nullptr; } else { MOZ_ASSERT(multi->GetMultithreadProtected()); } mImageDevice = device; return mImageDevice; } RefPtr DeviceManagerDx::GetVRDevice() { MutexAutoLock lock(mDeviceLock); if (!mVRDevice) { CreateVRDevice(); } return mVRDevice; } RefPtr DeviceManagerDx::GetCanvasDevice() { MutexAutoLock lock(mDeviceLock); return mCanvasDevice; } RefPtr 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::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* aOutDevice, RefPtr* aOutAttachments) { RefPtr 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 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 task = NS_NewRunnableFunction( "DeviceManagerDx::PreloadAttachmentsOnCompositorThread", []() -> void { if (DeviceManagerDx* dm = DeviceManagerDx::Get()) { RefPtr device; RefPtr attachments; dm->GetCompositorDevices(&device, &attachments); } }); CompositorThread()->Dispatch(task.forget()); } } // namespace gfx } // namespace mozilla