diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index 5c99c268358..b2fbc959886 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -8767,9 +8767,76 @@ QRhi::~QRhi() delete d; } +QRhiImplementation *QRhiImplementation::newInstance(QRhi::Implementation impl, QRhiInitParams *params, QRhiNativeHandles *importDevice) +{ + QRhiImplementation *d = nullptr; + + switch (impl) { + case QRhi::Null: + d = new QRhiNull(static_cast(params)); + break; + case QRhi::Vulkan: +#if QT_CONFIG(vulkan) + d = new QRhiVulkan(static_cast(params), + static_cast(importDevice)); + break; +#else + Q_UNUSED(importDevice); + qWarning("This build of Qt has no Vulkan support"); + break; +#endif + case QRhi::OpenGLES2: +#ifndef QT_NO_OPENGL + d = new QRhiGles2(static_cast(params), + static_cast(importDevice)); + break; +#else + qWarning("This build of Qt has no OpenGL support"); + break; +#endif + case QRhi::D3D11: +#ifdef Q_OS_WIN + d = new QRhiD3D11(static_cast(params), + static_cast(importDevice)); + break; +#else + qWarning("This platform has no Direct3D 11 support"); + break; +#endif + case QRhi::Metal: +#if QT_CONFIG(metal) + d = new QRhiMetal(static_cast(params), + static_cast(importDevice)); + break; +#else + qWarning("This platform has no Metal support"); + break; +#endif + case QRhi::D3D12: +#ifdef Q_OS_WIN +#ifdef QRHI_D3D12_AVAILABLE + d = new QRhiD3D12(static_cast(params), + static_cast(importDevice)); + break; +#else + qWarning("Qt was built without Direct3D 12 support. " + "This is likely due to having ancient SDK headers (such as d3d12.h) in the Qt build environment. " + "Rebuild Qt with an SDK supporting D3D12 features introduced in Windows 10 version 1703, " + "or use an MSVC build as those typically are built with more up-to-date SDKs."); + break; +#endif +#else + qWarning("This platform has no Direct3D 12 support"); + break; +#endif + } + + return d; +} + bool QRhiImplementation::rubLogEnabled = false; -void QRhiImplementation::prepareForCreate(QRhi *rhi, QRhi::Implementation impl, QRhi::Flags flags) +void QRhiImplementation::prepareForCreate(QRhi *rhi, QRhi::Implementation impl, QRhi::Flags flags, QRhiAdapter *adapter) { q = rhi; @@ -8785,6 +8852,13 @@ void QRhiImplementation::prepareForCreate(QRhi *rhi, QRhi::Implementation impl, implType = impl; implThread = QThread::currentThread(); + + requestedRhiAdapter = adapter; +} + +QRhi::AdapterList QRhiImplementation::enumerateAdaptersBeforeCreate(QRhiNativeHandles *) const +{ + return {}; } /*! @@ -8816,79 +8890,31 @@ void QRhiImplementation::prepareForCreate(QRhi *rhi, QRhi::Implementation impl, QRhiMetalNativeHandles, QRhiGles2NativeHandles. The exact details and semantics depend on the backand and the underlying graphics API. + Specifying a QRhiAdapter in \a adapter offers a transparent, cross-API + alternative to passing in a \c VkPhysicalDevice via QRhiVulkanNativeHandles, + or an adapter LUID via QRhiD3D12NativeHandles. The ownership of \a adapter + is not taken. See enumerateAdapters() for more information on this approach. + + \note \a importDevice and \a adapter cannot be both specified. + \sa probe() */ -QRhi *QRhi::create(Implementation impl, QRhiInitParams *params, Flags flags, QRhiNativeHandles *importDevice) +QRhi *QRhi::create(Implementation impl, QRhiInitParams *params, Flags flags, QRhiNativeHandles *importDevice, QRhiAdapter *adapter) { + if (adapter && importDevice) + qWarning("adapter and importDevice should not both be non-null in QRhi::create()"); + + std::unique_ptr rd(QRhiImplementation::newInstance(impl, params, importDevice)); + if (!rd) + return nullptr; + std::unique_ptr r(new QRhi); + r->d = rd.release(); + r->d->prepareForCreate(r.get(), impl, flags, adapter); + if (!r->d->create(flags)) + return nullptr; - switch (impl) { - case Null: - r->d = new QRhiNull(static_cast(params)); - break; - case Vulkan: -#if QT_CONFIG(vulkan) - r->d = new QRhiVulkan(static_cast(params), - static_cast(importDevice)); - break; -#else - Q_UNUSED(importDevice); - qWarning("This build of Qt has no Vulkan support"); - break; -#endif - case OpenGLES2: -#ifndef QT_NO_OPENGL - r->d = new QRhiGles2(static_cast(params), - static_cast(importDevice)); - break; -#else - qWarning("This build of Qt has no OpenGL support"); - break; -#endif - case D3D11: -#ifdef Q_OS_WIN - r->d = new QRhiD3D11(static_cast(params), - static_cast(importDevice)); - break; -#else - qWarning("This platform has no Direct3D 11 support"); - break; -#endif - case Metal: -#if QT_CONFIG(metal) - r->d = new QRhiMetal(static_cast(params), - static_cast(importDevice)); - break; -#else - qWarning("This platform has no Metal support"); - break; -#endif - case D3D12: -#ifdef Q_OS_WIN -#ifdef QRHI_D3D12_AVAILABLE - r->d = new QRhiD3D12(static_cast(params), - static_cast(importDevice)); - break; -#else - qWarning("Qt was built without Direct3D 12 support. " - "This is likely due to having ancient SDK headers (such as d3d12.h) in the Qt build environment. " - "Rebuild Qt with an SDK supporting D3D12 features introduced in Windows 10 version 1703, " - "or use an MSVC build as those typically are built with more up-to-date SDKs."); - break; -#endif -#else - qWarning("This platform has no Direct3D 12 support"); - break; -#endif - } - - if (r->d) { - r->d->prepareForCreate(r.get(), impl, flags); - if (r->d->create(flags)) - return r.release(); - } - - return nullptr; + return r.release(); } /*! @@ -8925,6 +8951,111 @@ bool QRhi::probe(QRhi::Implementation impl, QRhiInitParams *params) return ok; } +/*! + \typedef QRhi::AdapterList + \relates QRhi + \since 6.10 + + Synonym for QVector. +*/ + +/*! + \return the list of adapters (physical devices) present, or an empty list + when such control is not available with a given graphics API. + + Backends where such level of control is not available, the returned list is + always empty. Thus an empty list does not indicate there are no graphics + devices in the system, but that fine-grained control over selecting which + one to use is not available. + + Backends for Direct 3D 11, Direct 3D 12, and Vulkan can be expected to fully + support enumerating adapters. Others may not. The backend is specified by \a + impl. A QRhiAdapter returned from this function must only be used in a + create() call with the same \a impl. Some underlying APIs may present + further limitations, with Vulkan in particular the QRhiAdapter is specified + to the QVulkanInstance (\c VkInstance). + + The caller is expected to destroy the QRhiAdapter objects in the list. Apart + from querying \l{QRhiAdapter::}info(), the only purpose of these objects is + to be passed on to create(), or the corresponding functions in higher layers + such as Qt Quick. + + The following snippet, written specifically for Vulkan, shows how to + enumerate the available physical devices and request to create a QRhi for + the chosen one. This in practice is equivalent to passing in a \c + VkPhysicalDevice via a QRhiVulkanNativeHandles to create(), but it involves + less API-specific code on the application side: + + \code + QRhiVulkanInitParams initParams; + initParams.inst = &vulkanInstance; + QRhi::AdapterList adapters = QRhi::enumerateAdapters(QRhi::Vulkan, &initParams); + QRhiAdapter *chosenAdapter = nullptr; + for (QRhiAdapter *adapter : adapters) { + if (looksGood(adapter->info())) { + chosenAdapter = adapter; + break; + } + } + QRhi *rhi = QRhi::create(QRhi::Vulkan, &initParams, {}, nullptr, chosenAdapter); + qDeleteAll(adapters); + \endcode + + Passing in \a params is required due to some of the underlying graphics + APIs' design. With Vulkan in particular, the QVulkanInstance must be + provided, since enumerating is not possible without it. Other fields in the + backend-specific \a params will not actually be used by this function. + + \a nativeHandles is optional. When specified, it must be a valid + QRhiD3D11NativeHandles, QRhiD3D12NativeHandles, or QRhiVulkanNativeHandles, + similarly to create(). However, unlike create(), only the physical device + (in case of Vulkan) or the adapter LUID (in case of D3D) fields are used, + all other fields are ignored. This can be used the restrict the results to a + given adapter. The returned list will contain 1 or 0 elements in this case. + + Note how in the previous code snippet the looksGood() function + implementation cannot perform any platform-specific filtering based on the + true adapter / physical device identity, such as the adapter LUID on Windows + or the VkPhysicalDevice with Vulkan. This is because QRhiDriverInfo does not + contain platform-specific data. Instead, use \a nativeHandles to get the + results filtered already inside enumerateAdapters(). + + The following two snippets, using Direct 3D 12 as an example, are equivalent + in practice: + + \code + // enumerateAdapters-based approach from Qt 6.10 on + QRhiD3D12InitParams initParams; + QRhiD3D12NativeHandles nativeHandles; + nativeHandles.adapterLuidLow = luid.LowPart; // retrieved a LUID from somewhere, now pass it on to Qt + nativeHandles.adapterLuidHigh = luid.HighPart; + QRhi::AdapterList adapters = QRhi::enumerateAdapters(QRhi::D3D12, &initParams, &nativeHandles); + if (adapters.isEmpty()) { qWarning("Requested adapter was not found"); } + QRhi *rhi = QRhi::create(QRhi::D3D12, &initParams, {}, nullptr, adapters[0]); + qDeleteAll(adapters); + \endcode + + \code + // traditional approach, more lightweight + QRhiD3D12InitParams initParams; + QRhiD3D12NativeHandles nativeHandles; + nativeHandles.adapterLuidLow = luid.LowPart; // retrieved a LUID from somewhere, now pass it on to Qt + nativeHandles.adapterLuidHigh = luid.HighPart; + QRhi *rhi = QRhi::create(QRhi::D3D12, &initParams, {}, &nativeHandles, nullptr); + \endcode + + \since 6.10 + \sa create() + */ +QRhi::AdapterList QRhi::enumerateAdapters(Implementation impl, QRhiInitParams *params, QRhiNativeHandles *nativeHandles) +{ + std::unique_ptr rd(QRhiImplementation::newInstance(impl, params, nullptr)); + if (!rd) + return {}; + + return rd->enumerateAdaptersBeforeCreate(nativeHandles); +} + /*! \struct QRhiSwapChainProxyData \inmodule QtGuiPrivate @@ -9118,6 +9249,45 @@ QRhiDriverInfo QRhi::driverInfo() const return d->driverInfo(); } +/*! + \class QRhiAdapter + \inmodule QtGuiPrivate + \inheaderfile rhi/qrhi.h + \since 6.10 + + \brief Represents a physical graphics device. + + Some QRhi backends target graphics APIs that expose the concept of \c + adapters or \c{physical devices}. Call the static \l + {QRhi::}enumerateAdapters() function to retrieve a list of the adapters + present in the system. Pass one of the returned QRhiAdapter objects to \l + {QRhi::}create() in order to request using the adapter or physical device + the QRhiAdapter corresponds to. Other than exposing the QRhiDriverInfo, + QRhiAdapter is to be treated as an opaque handle. + + \note With Vulkan, the QRhiAdapter is valid only as long as the + QVulkanInstance that was used for \l{QRhi::}enumerateAdapters() is valid. + This also means that a QRhiAdapter is tied to the Vulkan instance + (QVulkanInstance, \c VkInstance) and cannot be used in the context of + another Vulkan instance. + + \note This is a RHI API with limited compatibility guarantees, see \l QRhi + for details. + */ + +/*! + \fn virtual QRhiDriverInfo QRhiAdapter::info() const = 0 + + \return the corresponding QRhiDriverInfo. + */ + +/*! + \internal + */ +QRhiAdapter::~QRhiAdapter() +{ +} + /*! \return the thread on which the QRhi was \l{QRhi::create()}{initialized}. */ diff --git a/src/gui/rhi/qrhi.h b/src/gui/rhi/qrhi.h index 5fbe7873fe5..a84f68160a2 100644 --- a/src/gui/rhi/qrhi.h +++ b/src/gui/rhi/qrhi.h @@ -1832,6 +1832,13 @@ Q_DECLARE_TYPEINFO(QRhiStats, Q_RELOCATABLE_TYPE); Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiStats &); #endif +class Q_GUI_EXPORT QRhiAdapter +{ +public: + virtual ~QRhiAdapter(); + virtual QRhiDriverInfo info() const = 0; +}; + struct Q_GUI_EXPORT QRhiInitParams { }; @@ -1946,8 +1953,13 @@ public: static QRhi *create(Implementation impl, QRhiInitParams *params, Flags flags = {}, - QRhiNativeHandles *importDevice = nullptr); + QRhiNativeHandles *importDevice = nullptr, + QRhiAdapter *adapter = nullptr); static bool probe(Implementation impl, QRhiInitParams *params); + using AdapterList = QVector; + static AdapterList enumerateAdapters(Implementation impl, + QRhiInitParams *params, + QRhiNativeHandles *nativeHandles = nullptr); Implementation backend() const; const char *backendName() const; diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h index 186e00b8601..7b1f5233c46 100644 --- a/src/gui/rhi/qrhi_p.h +++ b/src/gui/rhi/qrhi_p.h @@ -37,6 +37,7 @@ public: virtual bool create(QRhi::Flags flags) = 0; virtual void destroy() = 0; + virtual QRhi::AdapterList enumerateAdaptersBeforeCreate(QRhiNativeHandles *nativeHandles) const; virtual QRhiGraphicsPipeline *createGraphicsPipeline() = 0; virtual QRhiComputePipeline *createComputePipeline() = 0; @@ -146,7 +147,8 @@ public: virtual QByteArray pipelineCacheData() = 0; virtual void setPipelineCacheData(const QByteArray &data) = 0; - void prepareForCreate(QRhi *rhi, QRhi::Implementation impl, QRhi::Flags flags); + static QRhiImplementation *newInstance(QRhi::Implementation impl, QRhiInitParams *params, QRhiNativeHandles *importDevice); + void prepareForCreate(QRhi *rhi, QRhi::Implementation impl, QRhi::Flags flags, QRhiAdapter *adapter); bool isCompressedFormat(QRhiTexture::Format format) const; void compressedFormatInfo(QRhiTexture::Format format, const QSize &size, @@ -247,6 +249,8 @@ public: int currentFrameSlot = 0; // for vk, mtl, and similar. unused by gl and d3d11. bool inFrame = false; + QRhiAdapter *requestedRhiAdapter = nullptr; + private: QRhi::Implementation implType; QThread *implThread; diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index 5ba590b56ca..9d679dd7e96 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -246,7 +246,10 @@ bool QRhiD3D11::create(QRhi::Flags flags) if (qEnvironmentVariableIsSet("QT_D3D_ADAPTER_INDEX")) requestedAdapterIndex = qEnvironmentVariableIntValue("QT_D3D_ADAPTER_INDEX"); - // The importParams may specify an adapter by the luid, take that into account. + if (requestedRhiAdapter) + adapterLuid = static_cast(requestedRhiAdapter)->luid; + + // importParams or requestedRhiAdapter may specify an adapter by the luid, use that in the absence of an env.var. override. if (requestedAdapterIndex < 0 && (adapterLuid.LowPart || adapterLuid.HighPart)) { for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) { DXGI_ADAPTER_DESC1 desc; @@ -466,6 +469,9 @@ void QRhiD3D11::destroy() dxgiFactory = nullptr; } + importedDeviceAndContext = false; + adapterLuid = {}; + QDxgiVSyncService::instance()->derefAdapter(adapterLuid); } @@ -479,6 +485,48 @@ void QRhiD3D11::reportLiveObjects(ID3D11Device *device) } } +QRhi::AdapterList QRhiD3D11::enumerateAdaptersBeforeCreate(QRhiNativeHandles *nativeHandles) const +{ + LUID requestedLuid = {}; + if (nativeHandles) { + QRhiD3D11NativeHandles *h = static_cast(nativeHandles); + const LUID adapterLuid = { h->adapterLuidLow, h->adapterLuidHigh }; + if (adapterLuid.LowPart || adapterLuid.HighPart) + requestedLuid = adapterLuid; + } + + IDXGIFactory1 *dxgi = createDXGIFactory2(); + if (!dxgi) + return {}; + + QRhi::AdapterList list; + IDXGIAdapter1 *adapter; + for (int adapterIndex = 0; dxgi->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) { + DXGI_ADAPTER_DESC1 desc; + adapter->GetDesc1(&desc); + adapter->Release(); + if (requestedLuid.LowPart || requestedLuid.HighPart) { + if (desc.AdapterLuid.LowPart != requestedLuid.LowPart + || desc.AdapterLuid.HighPart != requestedLuid.HighPart) + { + continue; + } + } + QD3D11Adapter *a = new QD3D11Adapter; + a->luid = desc.AdapterLuid; + QRhiD3D::fillDriverInfo(&a->adapterInfo, desc); + list.append(a); + } + + dxgi->Release(); + return list; +} + +QRhiDriverInfo QD3D11Adapter::info() const +{ + return adapterInfo; +} + QList QRhiD3D11::supportedSampleCounts() const { return { 1, 2, 4, 8 }; diff --git a/src/gui/rhi/qrhid3d11_p.h b/src/gui/rhi/qrhid3d11_p.h index 16077e2194e..703a3cc4499 100644 --- a/src/gui/rhi/qrhid3d11_p.h +++ b/src/gui/rhi/qrhid3d11_p.h @@ -628,6 +628,15 @@ struct QD3D11SwapChain : public QRhiSwapChain HANDLE frameLatencyWaitableObject = nullptr; }; +class QD3D11Adapter : public QRhiAdapter +{ +public: + QRhiDriverInfo info() const override; + + LUID luid; + QRhiDriverInfo adapterInfo; +}; + class QRhiD3D11 : public QRhiImplementation { public: @@ -635,6 +644,7 @@ public: bool create(QRhi::Flags flags) override; void destroy() override; + QRhi::AdapterList enumerateAdaptersBeforeCreate(QRhiNativeHandles *nativeHandles) const override; QRhiGraphicsPipeline *createGraphicsPipeline() override; QRhiComputePipeline *createComputePipeline() override; diff --git a/src/gui/rhi/qrhid3d12.cpp b/src/gui/rhi/qrhid3d12.cpp index 3499a2b44d4..80bcca699db 100644 --- a/src/gui/rhi/qrhid3d12.cpp +++ b/src/gui/rhi/qrhid3d12.cpp @@ -255,7 +255,10 @@ bool QRhiD3D12::create(QRhi::Flags flags) if (qEnvironmentVariableIsSet("QT_D3D_ADAPTER_INDEX")) requestedAdapterIndex = qEnvironmentVariableIntValue("QT_D3D_ADAPTER_INDEX"); - // The importParams may specify an adapter by the luid, take that into account. + if (requestedRhiAdapter) + adapterLuid = static_cast(requestedRhiAdapter)->luid; + + // importParams or requestedRhiAdapter may specify an adapter by the luid, use that in the absence of an env.var. override. if (requestedAdapterIndex < 0 && (adapterLuid.LowPart || adapterLuid.HighPart)) { for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) { DXGI_ADAPTER_DESC1 desc; @@ -610,9 +613,55 @@ void QRhiD3D12::destroy() dxgiFactory = nullptr; } + adapterLuid = {}; + importedDevice = false; + importedCommandQueue = false; + QDxgiVSyncService::instance()->derefAdapter(adapterLuid); } +QRhi::AdapterList QRhiD3D12::enumerateAdaptersBeforeCreate(QRhiNativeHandles *nativeHandles) const +{ + LUID requestedLuid = {}; + if (nativeHandles) { + QRhiD3D12NativeHandles *h = static_cast(nativeHandles); + const LUID adapterLuid = { h->adapterLuidLow, h->adapterLuidHigh }; + if (adapterLuid.LowPart || adapterLuid.HighPart) + requestedLuid = adapterLuid; + } + + IDXGIFactory2 *dxgi = nullptr; + if (FAILED(CreateDXGIFactory2(0, __uuidof(IDXGIFactory2), reinterpret_cast(&dxgi)))) + return {}; + + QRhi::AdapterList list; + IDXGIAdapter1 *adapter; + for (int adapterIndex = 0; dxgi->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) { + DXGI_ADAPTER_DESC1 desc; + adapter->GetDesc1(&desc); + adapter->Release(); + if (requestedLuid.LowPart || requestedLuid.HighPart) { + if (desc.AdapterLuid.LowPart != requestedLuid.LowPart + || desc.AdapterLuid.HighPart != requestedLuid.HighPart) + { + continue; + } + } + QD3D12Adapter *a = new QD3D12Adapter; + a->luid = desc.AdapterLuid; + QRhiD3D::fillDriverInfo(&a->adapterInfo, desc); + list.append(a); + } + + dxgi->Release(); + return list; +} + +QRhiDriverInfo QD3D12Adapter::info() const +{ + return adapterInfo; +} + QList QRhiD3D12::supportedSampleCounts() const { return { 1, 2, 4, 8 }; diff --git a/src/gui/rhi/qrhid3d12_p.h b/src/gui/rhi/qrhid3d12_p.h index c009b6dd26f..33b75fc6c6a 100644 --- a/src/gui/rhi/qrhid3d12_p.h +++ b/src/gui/rhi/qrhid3d12_p.h @@ -1088,6 +1088,15 @@ struct alignas(void*) QD3D12PipelineStateSubObject T object = {}; }; +class QD3D12Adapter : public QRhiAdapter +{ +public: + QRhiDriverInfo info() const override; + + LUID luid; + QRhiDriverInfo adapterInfo; +}; + class QRhiD3D12 : public QRhiImplementation { public: @@ -1101,6 +1110,7 @@ public: bool create(QRhi::Flags flags) override; void destroy() override; + QRhi::AdapterList enumerateAdaptersBeforeCreate(QRhiNativeHandles *nativeHandles) const override; QRhiGraphicsPipeline *createGraphicsPipeline() override; QRhiComputePipeline *createComputePipeline() override; diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index 283b475ad10..909d5623ad8 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -475,6 +475,14 @@ static inline QRhiDriverInfo::DeviceType toRhiDeviceType(VkPhysicalDeviceType ty } } +static inline void fillDriverInfo(QRhiDriverInfo *info, const VkPhysicalDeviceProperties &physDevProperties) +{ + info->deviceName = QByteArray(physDevProperties.deviceName); + info->deviceId = physDevProperties.deviceID; + info->vendorId = physDevProperties.vendorID; + info->deviceType = toRhiDeviceType(physDevProperties.deviceType); +} + template static inline void addToChain(T *head, void *entry) { @@ -539,6 +547,16 @@ bool QRhiVulkan::create(QRhi::Flags flags) if (qEnvironmentVariableIsSet("QT_VK_PHYSICAL_DEVICE_INDEX")) requestedPhysDevIndex = qEnvironmentVariableIntValue("QT_VK_PHYSICAL_DEVICE_INDEX"); + if (requestedPhysDevIndex < 0 && requestedRhiAdapter) { + VkPhysicalDevice requestedPhysDev = static_cast(requestedRhiAdapter)->physDev; + for (int i = 0; i < int(physDevCount); ++i) { + if (physDevs[i] == requestedPhysDev) { + requestedPhysDevIndex = i; + break; + } + } + } + if (requestedPhysDevIndex < 0 && flags.testFlag(QRhi::PreferSoftwareRenderer)) { for (int i = 0; i < int(physDevCount); ++i) { f->vkGetPhysicalDeviceProperties(physDevs[i], &physDevProperties); @@ -605,10 +623,7 @@ bool QRhiVulkan::create(QRhi::Flags flags) caps.apiVersion = physDevApiVersion; } - driverInfoStruct.deviceName = QByteArray(physDevProperties.deviceName); - driverInfoStruct.deviceId = physDevProperties.deviceID; - driverInfoStruct.vendorId = physDevProperties.vendorID; - driverInfoStruct.deviceType = toRhiDeviceType(physDevProperties.deviceType); + fillDriverInfo(&driverInfoStruct, physDevProperties); QVulkanInfoVector devExts; uint32_t devExtCount = 0; @@ -1118,6 +1133,49 @@ void QRhiVulkan::destroy() f = nullptr; df = nullptr; + + importedDevice = false; + importedAllocator = false; +} + +QRhi::AdapterList QRhiVulkan::enumerateAdaptersBeforeCreate(QRhiNativeHandles *nativeHandles) const +{ + VkPhysicalDevice requestedPhysDev = VK_NULL_HANDLE; + if (nativeHandles) { + QRhiVulkanNativeHandles *h = static_cast(nativeHandles); + requestedPhysDev = h->physDev; + } + + QRhi::AdapterList list; + QVulkanFunctions *f = inst->functions(); + uint32_t physDevCount = 0; + f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, nullptr); + if (!physDevCount) + return {}; + + QVarLengthArray physDevs(physDevCount); + VkResult err = f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, physDevs.data()); + if (err != VK_SUCCESS || !physDevCount) + return {}; + + VkPhysicalDeviceProperties physDevProperties = {}; + for (uint32_t i = 0; i < physDevCount; ++i) { + if (requestedPhysDev && physDevs[i] != requestedPhysDev) + continue; + + f->vkGetPhysicalDeviceProperties(physDevs[i], &physDevProperties); + QVulkanAdapter *a = new QVulkanAdapter; + a->physDev = physDevs[i]; + fillDriverInfo(&a->adapterInfo, physDevProperties); + list.append(a); + } + + return list; +} + +QRhiDriverInfo QVulkanAdapter::info() const +{ + return adapterInfo; } VkResult QRhiVulkan::createDescriptorPool(VkDescriptorPool *pool) diff --git a/src/gui/rhi/qrhivulkan_p.h b/src/gui/rhi/qrhivulkan_p.h index c987dd2c501..cdd2b42ea34 100644 --- a/src/gui/rhi/qrhivulkan_p.h +++ b/src/gui/rhi/qrhivulkan_p.h @@ -672,6 +672,15 @@ struct QVkSwapChain : public QRhiSwapChain friend class QRhiVulkan; }; +class QVulkanAdapter : public QRhiAdapter +{ +public: + QRhiDriverInfo info() const override; + + VkPhysicalDevice physDev; + QRhiDriverInfo adapterInfo; +}; + class QRhiVulkan : public QRhiImplementation { public: @@ -679,6 +688,7 @@ public: bool create(QRhi::Flags flags) override; void destroy() override; + QRhi::AdapterList enumerateAdaptersBeforeCreate(QRhiNativeHandles *nativeHandles) const override; QRhiGraphicsPipeline *createGraphicsPipeline() override; QRhiComputePipeline *createComputePipeline() override; diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index 3912b9d2653..fb30658fd13 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -48,6 +48,8 @@ private slots: void rhiTestData(); void create_data(); void create(); + void adapters_data(); + void adapters(); void stats_data(); void stats(); void nativeHandles_data(); @@ -443,6 +445,76 @@ void tst_QRhi::create() } } +void tst_QRhi::adapters_data() +{ + rhiTestData(); +} + +void tst_QRhi::adapters() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QRhi::AdapterList adapters = QRhi::enumerateAdapters(impl, initParams); + for (QRhiAdapter *adapter : adapters) + qDebug() << adapter->info(); + + if (!adapters.isEmpty()) { + QRhi *rhi = QRhi::create(impl, initParams, QRhi::Flags(), nullptr, adapters[0]); + if (rhi) { + QCOMPARE(rhi->driverInfo().deviceName, adapters[0]->info().deviceName); + QCOMPARE(rhi->driverInfo().deviceId, adapters[0]->info().deviceId); + QCOMPARE(rhi->driverInfo().vendorId, adapters[0]->info().vendorId); + QCOMPARE(rhi->driverInfo().deviceType, adapters[0]->info().deviceType); + } + delete rhi; + + // test filtering based on luid/physdev + if (impl == QRhi::D3D11) { +#ifdef TST_D3D11 + QRhi *rhi = QRhi::create(impl, initParams); + if (rhi) { + QRhiD3D11NativeHandles h = *static_cast(rhi->nativeHandles()); + delete rhi; + if (h.adapterLuidLow || h.adapterLuidHigh) { + QRhi::AdapterList filteredList = QRhi::enumerateAdapters(impl, initParams, &h); + QCOMPARE(filteredList.count(), 1); + qDeleteAll(filteredList); + } + } +#endif + } else if (impl == QRhi::D3D12) { +#ifdef TST_D3D12 + QRhi *rhi = QRhi::create(impl, initParams); + if (rhi) { + QRhiD3D12NativeHandles h = *static_cast(rhi->nativeHandles()); + delete rhi; + if (h.adapterLuidLow || h.adapterLuidHigh) { + QRhi::AdapterList filteredList = QRhi::enumerateAdapters(impl, initParams, &h); + QCOMPARE(filteredList.count(), 1); + qDeleteAll(filteredList); + } + } +#endif + } else if (impl == QRhi::Vulkan) { +#ifdef TST_VK + QRhi *rhi = QRhi::create(impl, initParams); + if (rhi) { + QRhiVulkanNativeHandles h = *static_cast(rhi->nativeHandles()); + delete rhi; + QVERIFY(h.physDev); + QRhi::AdapterList filteredList = QRhi::enumerateAdapters(impl, initParams, &h); + QCOMPARE(filteredList.count(), 1); + qDeleteAll(filteredList); + } +#endif + } + } + + qDeleteAll(adapters); + adapters.clear(); +} + void tst_QRhi::stats_data() { rhiTestData(); diff --git a/tests/manual/rhi/shared/examplefw.h b/tests/manual/rhi/shared/examplefw.h index cc7fe9f7760..2ccbd6bea3e 100644 --- a/tests/manual/rhi/shared/examplefw.h +++ b/tests/manual/rhi/shared/examplefw.h @@ -224,6 +224,8 @@ bool Window::event(QEvent *e) void Window::init() { + QRhi::AdapterList adapters; + if (graphicsApi == Null) { QRhiNullInitParams params; m_r = QRhi::create(QRhi::Null, ¶ms, rhiFlags); @@ -244,6 +246,7 @@ void Window::init() QRhiVulkanInitParams params; params.inst = vulkanInstance(); params.window = this; + adapters = QRhi::enumerateAdapters(QRhi::Vulkan, ¶ms); m_r = QRhi::create(QRhi::Vulkan, ¶ms, rhiFlags); } #endif @@ -254,12 +257,14 @@ void Window::init() if (debugLayer) qDebug("Enabling D3D11 debug layer"); params.enableDebugLayer = debugLayer; + adapters = QRhi::enumerateAdapters(QRhi::D3D11, ¶ms); m_r = QRhi::create(QRhi::D3D11, ¶ms, rhiFlags); } else if (graphicsApi == D3D12) { QRhiD3D12InitParams params; if (debugLayer) qDebug("Enabling D3D12 debug layer"); params.enableDebugLayer = debugLayer; + adapters = QRhi::enumerateAdapters(QRhi::D3D12, ¶ms); m_r = QRhi::create(QRhi::D3D12, ¶ms, rhiFlags); } #endif @@ -271,6 +276,12 @@ void Window::init() } #endif + if (!adapters.isEmpty()) { + qDebug() << "For information, enumerateAdapters() reports:"; + for (qsizetype i = 0; i < adapters.count(); ++i) + qDebug() << " QRhiAdapter #" << i << ":" << adapters[i]->info(); + } + if (!m_r) qFatal("Failed to create RHI backend");