1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2025-02-05 02:52:11 +01:00
dxvk/src/dxgi/dxgi_adapter.cpp
Philip Rebohle 14eb469005 [dxgi] Be more robust against monitor enumeration issues.
If there are monitors on the system that are not associated with
any adapter, enumerate all monitors for all adatpers. May solve
some issues if device filter options are used on multi-GPU systems.
2023-05-11 16:38:45 +02:00

510 lines
16 KiB
C++

#include <cstdlib>
#include <cstring>
#include <d3d10_1.h>
#include "dxgi_adapter.h"
#include "dxgi_enums.h"
#include "dxgi_factory.h"
#include "dxgi_format.h"
#include "dxgi_options.h"
#include "dxgi_output.h"
#include "../util/util_luid.h"
#include "../util/util_win32_compat.h"
#include "../wsi/wsi_monitor.h"
namespace dxvk {
DxgiVkAdapter::DxgiVkAdapter(DxgiAdapter* pAdapter)
: m_adapter(pAdapter) {
}
ULONG STDMETHODCALLTYPE DxgiVkAdapter::AddRef() {
return m_adapter->AddRef();
}
ULONG STDMETHODCALLTYPE DxgiVkAdapter::Release() {
return m_adapter->Release();
}
HRESULT STDMETHODCALLTYPE DxgiVkAdapter::QueryInterface(
REFIID riid,
void** ppvObject) {
return m_adapter->QueryInterface(riid, ppvObject);
}
void STDMETHODCALLTYPE DxgiVkAdapter::GetVulkanHandles(
VkInstance* pInstance,
VkPhysicalDevice* pPhysDev) {
auto adapter = m_adapter->GetDXVKAdapter();
auto instance = m_adapter->GetDXVKInstance();
if (pInstance)
*pInstance = instance->handle();
if (pPhysDev)
*pPhysDev = adapter->handle();
}
DxgiAdapter::DxgiAdapter(
DxgiFactory* factory,
const Rc<DxvkAdapter>& adapter,
UINT index)
: m_factory (factory),
m_adapter (adapter),
m_interop (this),
m_index (index) {
}
DxgiAdapter::~DxgiAdapter() {
if (m_eventThread.joinable()) {
std::unique_lock<dxvk::mutex> lock(m_mutex);
m_eventCookie = ~0u;
m_cond.notify_one();
lock.unlock();
m_eventThread.join();
}
}
HRESULT STDMETHODCALLTYPE DxgiAdapter::QueryInterface(REFIID riid, void** ppvObject) {
if (ppvObject == nullptr)
return E_POINTER;
*ppvObject = nullptr;
if (riid == __uuidof(IUnknown)
|| riid == __uuidof(IDXGIObject)
|| riid == __uuidof(IDXGIAdapter)
|| riid == __uuidof(IDXGIAdapter1)
|| riid == __uuidof(IDXGIAdapter2)
|| riid == __uuidof(IDXGIAdapter3)
|| riid == __uuidof(IDXGIAdapter4)
|| riid == __uuidof(IDXGIDXVKAdapter)) {
*ppvObject = ref(this);
return S_OK;
}
if (riid == __uuidof(IDXGIVkInteropAdapter)) {
*ppvObject = ref(&m_interop);
return S_OK;
}
if (logQueryInterfaceError(__uuidof(IDXGIAdapter), riid)) {
Logger::warn("DxgiAdapter::QueryInterface: Unknown interface query");
Logger::warn(str::format(riid));
}
return E_NOINTERFACE;
}
HRESULT STDMETHODCALLTYPE DxgiAdapter::GetParent(REFIID riid, void** ppParent) {
return m_factory->QueryInterface(riid, ppParent);
}
HRESULT STDMETHODCALLTYPE DxgiAdapter::CheckInterfaceSupport(
REFGUID InterfaceName,
LARGE_INTEGER* pUMDVersion) {
HRESULT hr = DXGI_ERROR_UNSUPPORTED;
if (InterfaceName == __uuidof(IDXGIDevice)
|| InterfaceName == __uuidof(ID3D10Device)
|| InterfaceName == __uuidof(ID3D10Device1))
hr = S_OK;
// We can't really reconstruct the version numbers
// returned by Windows drivers from Vulkan data
if (SUCCEEDED(hr) && pUMDVersion)
pUMDVersion->QuadPart = ~0ull;
if (FAILED(hr)) {
Logger::err("DXGI: CheckInterfaceSupport: Unsupported interface");
Logger::err(str::format(InterfaceName));
}
return hr;
}
HRESULT STDMETHODCALLTYPE DxgiAdapter::EnumOutputs(
UINT Output,
IDXGIOutput** ppOutput) {
InitReturnPtr(ppOutput);
if (ppOutput == nullptr)
return E_INVALIDARG;
const auto& deviceId = m_adapter->devicePropertiesExt().vk11;
std::array<const LUID*, 2> adapterLUIDs = { };
uint32_t numLUIDs = 0;
if (m_adapter->isLinkedToDGPU())
return DXGI_ERROR_NOT_FOUND;
if (deviceId.deviceLUIDValid)
adapterLUIDs[numLUIDs++] = reinterpret_cast<const LUID*>(deviceId.deviceLUID);
auto linkedAdapter = m_adapter->linkedIGPUAdapter();
// If either LUID is not valid, enumerate all monitors.
if (numLUIDs && linkedAdapter != nullptr) {
const auto& deviceId = linkedAdapter->devicePropertiesExt().vk11;
if (deviceId.deviceLUIDValid)
adapterLUIDs[numLUIDs++] = reinterpret_cast<const LUID*>(deviceId.deviceLUID);
else
numLUIDs = 0;
}
// Enumerate all monitors if the robustness fallback is active.
if (m_factory->UseMonitorFallback())
numLUIDs = 0;
HMONITOR monitor = wsi::enumMonitors(adapterLUIDs.data(), numLUIDs, Output);
if (monitor == nullptr)
return DXGI_ERROR_NOT_FOUND;
*ppOutput = ref(new DxgiOutput(m_factory, this, monitor));
return S_OK;
}
HRESULT STDMETHODCALLTYPE DxgiAdapter::GetDesc(DXGI_ADAPTER_DESC* pDesc) {
if (pDesc == nullptr)
return E_INVALIDARG;
DXGI_ADAPTER_DESC3 desc;
HRESULT hr = GetDesc3(&desc);
if (SUCCEEDED(hr)) {
std::memcpy(pDesc->Description, desc.Description, sizeof(pDesc->Description));
pDesc->VendorId = desc.VendorId;
pDesc->DeviceId = desc.DeviceId;
pDesc->SubSysId = desc.SubSysId;
pDesc->Revision = desc.Revision;
pDesc->DedicatedVideoMemory = desc.DedicatedVideoMemory;
pDesc->DedicatedSystemMemory = desc.DedicatedSystemMemory;
pDesc->SharedSystemMemory = desc.SharedSystemMemory;
pDesc->AdapterLuid = desc.AdapterLuid;
}
return hr;
}
HRESULT STDMETHODCALLTYPE DxgiAdapter::GetDesc1(DXGI_ADAPTER_DESC1* pDesc) {
if (pDesc == nullptr)
return E_INVALIDARG;
DXGI_ADAPTER_DESC3 desc;
HRESULT hr = GetDesc3(&desc);
if (SUCCEEDED(hr)) {
std::memcpy(pDesc->Description, desc.Description, sizeof(pDesc->Description));
pDesc->VendorId = desc.VendorId;
pDesc->DeviceId = desc.DeviceId;
pDesc->SubSysId = desc.SubSysId;
pDesc->Revision = desc.Revision;
pDesc->DedicatedVideoMemory = desc.DedicatedVideoMemory;
pDesc->DedicatedSystemMemory = desc.DedicatedSystemMemory;
pDesc->SharedSystemMemory = desc.SharedSystemMemory;
pDesc->AdapterLuid = desc.AdapterLuid;
pDesc->Flags = desc.Flags;
}
return hr;
}
HRESULT STDMETHODCALLTYPE DxgiAdapter::GetDesc2(DXGI_ADAPTER_DESC2* pDesc) {
if (pDesc == nullptr)
return E_INVALIDARG;
DXGI_ADAPTER_DESC3 desc;
HRESULT hr = GetDesc3(&desc);
if (SUCCEEDED(hr)) {
std::memcpy(pDesc->Description, desc.Description, sizeof(pDesc->Description));
pDesc->VendorId = desc.VendorId;
pDesc->DeviceId = desc.DeviceId;
pDesc->SubSysId = desc.SubSysId;
pDesc->Revision = desc.Revision;
pDesc->DedicatedVideoMemory = desc.DedicatedVideoMemory;
pDesc->DedicatedSystemMemory = desc.DedicatedSystemMemory;
pDesc->SharedSystemMemory = desc.SharedSystemMemory;
pDesc->AdapterLuid = desc.AdapterLuid;
pDesc->Flags = desc.Flags;
pDesc->GraphicsPreemptionGranularity = desc.GraphicsPreemptionGranularity;
pDesc->ComputePreemptionGranularity = desc.ComputePreemptionGranularity;
}
return hr;
}
HRESULT STDMETHODCALLTYPE DxgiAdapter::GetDesc3(
DXGI_ADAPTER_DESC3* pDesc) {
if (pDesc == nullptr)
return E_INVALIDARG;
const DxgiOptions* options = m_factory->GetOptions();
auto deviceProp = m_adapter->deviceProperties();
auto memoryProp = m_adapter->memoryProperties();
auto vk11 = m_adapter->devicePropertiesExt().vk11;
// Custom Vendor / Device ID
if (options->customVendorId >= 0)
deviceProp.vendorID = options->customVendorId;
if (options->customDeviceId >= 0)
deviceProp.deviceID = options->customDeviceId;
std::string description = options->customDeviceDesc.empty()
? std::string(deviceProp.deviceName)
: options->customDeviceDesc;
// XXX nvapi workaround for a lot of Unreal Engine 4 games
if (options->customVendorId < 0 && options->customDeviceId < 0
&& options->nvapiHack && deviceProp.vendorID == uint16_t(DxvkGpuVendor::Nvidia)) {
Logger::info("DXGI: NvAPI workaround enabled, reporting AMD GPU");
deviceProp.vendorID = uint16_t(DxvkGpuVendor::Amd);
deviceProp.deviceID = 0x67df; /* RX 480 */
}
// Convert device name
std::memset(pDesc->Description, 0, sizeof(pDesc->Description));
str::transcodeString(pDesc->Description,
sizeof(pDesc->Description) / sizeof(pDesc->Description[0]) - 1,
description.c_str(), description.size());
// Get amount of video memory
// based on the Vulkan heaps
VkDeviceSize deviceMemory = 0;
VkDeviceSize sharedMemory = 0;
for (uint32_t i = 0; i < memoryProp.memoryHeapCount; i++) {
VkMemoryHeap heap = memoryProp.memoryHeaps[i];
if (heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT)
deviceMemory += heap.size;
else
sharedMemory += heap.size;
}
// Some games think we are on Intel given a lack of
// NVAPI or AGS/atiadlxx support.
// Report our device memory as shared memory,
// and some small amount for the carveout.
if (options->emulateUMA && !m_adapter->isUnifiedMemoryArchitecture()) {
sharedMemory = deviceMemory;
deviceMemory = 128 * (1 << 20);
}
// Some games are silly and need their memory limited
if (options->maxDeviceMemory > 0
&& options->maxDeviceMemory < deviceMemory)
deviceMemory = options->maxDeviceMemory;
if (options->maxSharedMemory > 0
&& options->maxSharedMemory < sharedMemory)
sharedMemory = options->maxSharedMemory;
if (env::is32BitHostPlatform()) {
// The value returned by DXGI is a 32-bit value
// on 32-bit platforms, so we need to clamp it
VkDeviceSize maxMemory = 0xC0000000;
deviceMemory = std::min(deviceMemory, maxMemory);
sharedMemory = std::min(sharedMemory, maxMemory);
}
pDesc->VendorId = deviceProp.vendorID;
pDesc->DeviceId = deviceProp.deviceID;
pDesc->SubSysId = 0;
pDesc->Revision = 0;
pDesc->DedicatedVideoMemory = deviceMemory;
pDesc->DedicatedSystemMemory = 0;
pDesc->SharedSystemMemory = sharedMemory;
pDesc->AdapterLuid = LUID { 0, 0 };
pDesc->Flags = DXGI_ADAPTER_FLAG3_NONE;
pDesc->GraphicsPreemptionGranularity = DXGI_GRAPHICS_PREEMPTION_DMA_BUFFER_BOUNDARY;
pDesc->ComputePreemptionGranularity = DXGI_COMPUTE_PREEMPTION_DMA_BUFFER_BOUNDARY;
if (vk11.deviceLUIDValid)
std::memcpy(&pDesc->AdapterLuid, vk11.deviceLUID, VK_LUID_SIZE);
else
pDesc->AdapterLuid = GetAdapterLUID(m_index);
return S_OK;
}
HRESULT STDMETHODCALLTYPE DxgiAdapter::QueryVideoMemoryInfo(
UINT NodeIndex,
DXGI_MEMORY_SEGMENT_GROUP MemorySegmentGroup,
DXGI_QUERY_VIDEO_MEMORY_INFO* pVideoMemoryInfo) {
if (NodeIndex > 0 || !pVideoMemoryInfo)
return E_INVALIDARG;
if (MemorySegmentGroup != DXGI_MEMORY_SEGMENT_GROUP_LOCAL
&& MemorySegmentGroup != DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL)
return E_INVALIDARG;
DxvkAdapterMemoryInfo memInfo = m_adapter->getMemoryHeapInfo();
VkMemoryHeapFlags heapFlagMask = VK_MEMORY_HEAP_DEVICE_LOCAL_BIT;
VkMemoryHeapFlags heapFlags = 0;
if (MemorySegmentGroup == DXGI_MEMORY_SEGMENT_GROUP_LOCAL)
heapFlags |= VK_MEMORY_HEAP_DEVICE_LOCAL_BIT;
pVideoMemoryInfo->Budget = 0;
pVideoMemoryInfo->CurrentUsage = 0;
pVideoMemoryInfo->AvailableForReservation = 0;
for (uint32_t i = 0; i < memInfo.heapCount; i++) {
if ((memInfo.heaps[i].heapFlags & heapFlagMask) != heapFlags)
continue;
pVideoMemoryInfo->Budget += memInfo.heaps[i].memoryBudget;
pVideoMemoryInfo->CurrentUsage += memInfo.heaps[i].memoryAllocated;
pVideoMemoryInfo->AvailableForReservation += memInfo.heaps[i].heapSize / 2;
}
// We don't implement reservation, but the observable
// behaviour should match that of Windows drivers
uint32_t segmentId = uint32_t(MemorySegmentGroup);
pVideoMemoryInfo->CurrentReservation = m_memReservation[segmentId];
return S_OK;
}
HRESULT STDMETHODCALLTYPE DxgiAdapter::SetVideoMemoryReservation(
UINT NodeIndex,
DXGI_MEMORY_SEGMENT_GROUP MemorySegmentGroup,
UINT64 Reservation) {
DXGI_QUERY_VIDEO_MEMORY_INFO info;
HRESULT hr = QueryVideoMemoryInfo(
NodeIndex, MemorySegmentGroup, &info);
if (FAILED(hr))
return hr;
if (Reservation > info.AvailableForReservation)
return DXGI_ERROR_INVALID_CALL;
uint32_t segmentId = uint32_t(MemorySegmentGroup);
m_memReservation[segmentId] = Reservation;
return S_OK;
}
HRESULT STDMETHODCALLTYPE DxgiAdapter::RegisterHardwareContentProtectionTeardownStatusEvent(
HANDLE hEvent,
DWORD* pdwCookie) {
Logger::err("DxgiAdapter::RegisterHardwareContentProtectionTeardownStatusEvent: Not implemented");
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE DxgiAdapter::RegisterVideoMemoryBudgetChangeNotificationEvent(
HANDLE hEvent,
DWORD* pdwCookie) {
if (!hEvent || !pdwCookie)
return DXGI_ERROR_INVALID_CALL;
std::unique_lock<dxvk::mutex> lock(m_mutex);
DWORD cookie = ++m_eventCookie;
m_eventMap.insert({ cookie, hEvent });
if (!m_eventThread.joinable())
m_eventThread = dxvk::thread([this] { runEventThread(); });
// This method seems to fire the
// event immediately on Windows
SetEvent(hEvent);
*pdwCookie = cookie;
return S_OK;
}
void STDMETHODCALLTYPE DxgiAdapter::UnregisterHardwareContentProtectionTeardownStatus(
DWORD dwCookie) {
Logger::err("DxgiAdapter::UnregisterHardwareContentProtectionTeardownStatus: Not implemented");
}
void STDMETHODCALLTYPE DxgiAdapter::UnregisterVideoMemoryBudgetChangeNotification(
DWORD dwCookie) {
std::unique_lock<dxvk::mutex> lock(m_mutex);
m_eventMap.erase(dwCookie);
}
Rc<DxvkAdapter> STDMETHODCALLTYPE DxgiAdapter::GetDXVKAdapter() {
return m_adapter;
}
Rc<DxvkInstance> STDMETHODCALLTYPE DxgiAdapter::GetDXVKInstance() {
return m_factory->GetDXVKInstance();
}
void DxgiAdapter::runEventThread() {
env::setThreadName(str::format("dxvk-adapter-", m_index));
std::unique_lock<dxvk::mutex> lock(m_mutex);
DxvkAdapterMemoryInfo memoryInfoOld = m_adapter->getMemoryHeapInfo();
while (true) {
m_cond.wait_for(lock, std::chrono::milliseconds(1500),
[this] { return m_eventCookie == ~0u; });
if (m_eventCookie == ~0u)
return;
auto memoryInfoNew = m_adapter->getMemoryHeapInfo();
bool budgetChanged = false;
for (uint32_t i = 0; i < memoryInfoNew.heapCount; i++) {
budgetChanged |= memoryInfoNew.heaps[i].memoryBudget
!= memoryInfoOld.heaps[i].memoryBudget;
}
if (budgetChanged) {
memoryInfoOld = memoryInfoNew;
for (const auto& pair : m_eventMap)
SetEvent(pair.second);
}
}
}
}