From 6f67b0641b940be5400efe458b682426f4948924 Mon Sep 17 00:00:00 2001 From: DanFraser <11152006+DanFraserUK@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:06:11 +0100 Subject: [PATCH] nvapi: Forward NVAPI multiview calls to DXVK This PR is to enable SMP/MVP support in iRacing. With triple monitors a user really needs to enable Render scene using 3 projections to remove warping on all monitors due to one very wide camera. But this incurs a large performance loss due to drawing a scene three times per frame (one per screen). SMP/MVP changes how the game makes the calls, compiling the shaders directly through NVIDIA creation calls rather than DirectX with extra position data for each viewport/screen. This means a scene draw only needs to be done once instead of three times reducing the cpu work meaning the GPU is not waiting as much leading to higher framerates. Before the work in this PR, neither DXVK or dxvk-nvapi had any idea SMP existed and the toggle in iRacing was greyed out because when the game asked if the feature was available the answer was no. The capability query now correctly enables the toggle by exposing the feature when the graphics card is known to support SMP/MVP in its various feature levels, which means it won't magically activate on an AMD card for example. Enabling the toggle was not the end of the work, dxvk-nvapi needed to know how to forward the multiview calls to DXVK which then did the actual work needed to display the graphics correctly when using SMP. ID3D11VkExtDevice2 and ID3D11VkExtContext2 are the transition from the game to DXVK. Understandably, SMP/MVP has never been implemented or even stubbed due to the less than a handful list of games that use it. A previous request from myself (issue #233) a few years ago became a "fine I'll do it myself" and resulted in heavy research to do this work. Testing required getting the SMP toggle functional then using RenderDoc to review viewport and location data to align them correctly. Operational testing became toggling settings on and off and the data (admittedly due to my weak cpu) showed a substantial increase in fps as one would expect with SMP being enabled. The viewport masking was inferred from constants as the NVIDIA documentation appears to have no description of it, but that's more on the DXVK side. As far as I am aware, iRacing is the only game to fully utilise SMP but I have noticed some of the work in the PR may overlap with some VR technology as NVIDIA's own documented cases for this API are VR, single-pass stereo on Pascal and other parts which could be useful. --- src/interfaces/dxvk_interfaces.h | 63 ++++++++++ src/nvapi/nvapi_d3d11_device.cpp | 32 ++++- src/nvapi/nvapi_d3d11_device.h | 10 +- src/nvapi_d3d.cpp | 201 +++++++++++++++++++++++++++++++ src/nvapi_d3d11.cpp | 148 +++++++++++++++++++++++ src/nvapi_interface.cpp | 8 ++ tests/nvapi_d3d11.cpp | 4 + 7 files changed, 463 insertions(+), 3 deletions(-) diff --git a/src/interfaces/dxvk_interfaces.h b/src/interfaces/dxvk_interfaces.h index 3e03ec48..9f8e085e 100644 --- a/src/interfaces/dxvk_interfaces.h +++ b/src/interfaces/dxvk_interfaces.h @@ -159,6 +159,67 @@ ID3D11VkExtContext1 : public ID3D11VkExtContext { uint32_t numWriteResources) = 0; }; +/** + * \brief NVAPI custom shader semantic (SMP / multi-view) + * + * Mirrors the information from NVAPI's NV_CUSTOM_SEMANTIC without + * depending on NVIDIA headers. Type values match NV_CUSTOM_SEMANTIC_TYPE + * (2 = viewport mask, 4 = viewport mask 2, 5 = per-view position). + * + * Name[256] matches NVIDIA's own NVCustomSemanticNameString buffer size + * exactly. Every semantic name observed fits comfortably in far less, + * but matching NVIDIA's maximum removes any truncation risk and lets the + * compiler prove the strncpy calls below cannot truncate, which silences + * -Wstringop-truncation properly rather than suppressing it. + */ +struct D3D11_VK_NV_CUSTOM_SEMANTIC { + uint32_t Type; + char Name[256]; + BOOL RegisterSpecified; + uint32_t RegisterNum; + uint32_t RegisterMask; +}; + +/** + * \brief Extended D3D11 device, revision 2 + * + * Adds NVAPI-style extended shader creation for + * SMP / multi-view rendering (dxvk-nvapi interop). + */ +MIDL_INTERFACE("1d5c6a10-9f4b-4e0a-b6a4-2c8e13d70f51") +ID3D11VkExtDevice2 : public ID3D11VkExtDevice1 { + + virtual HRESULT STDMETHODCALLTYPE CreateVertexShaderNvSemantics( + const void* pShaderBytecode, + SIZE_T BytecodeLength, + ID3D11ClassLinkage* pClassLinkage, + const D3D11_VK_NV_CUSTOM_SEMANTIC* pSemantics, + uint32_t NumSemantics, + ID3D11VertexShader** ppVertexShader) = 0; + + virtual HRESULT STDMETHODCALLTYPE CreateGeometryShaderNvSemantics( + const void* pShaderBytecode, + SIZE_T BytecodeLength, + ID3D11ClassLinkage* pClassLinkage, + const D3D11_VK_NV_CUSTOM_SEMANTIC* pSemantics, + uint32_t NumSemantics, + BOOL UseViewportMask, + ID3D11GeometryShader** ppGeometryShader) = 0; +}; + +/** + * \brief Extended D3D11 context, revision 2 + * + * Adds the SMP multi-view mode toggle (dxvk-nvapi interop). + */ +MIDL_INTERFACE("7e2c1b9f-4d38-4a11-9b6e-0f5a8c42d3e7") +ID3D11VkExtContext2 : public ID3D11VkExtContext1 { + + virtual void STDMETHODCALLTYPE SetMultiviewModeNV( + uint32_t NumViews, + BOOL IndependentViewportMask) = 0; +}; + MIDL_INTERFACE("2a289dbd-2d0a-4a51-89f7-f2adce465cd6") IDXGIVkInteropFactory1 : public IDXGIVkInteropFactory { virtual HRESULT STDMETHODCALLTYPE GetGlobalHDRState( @@ -176,6 +237,8 @@ __CRT_UUID_DECL(IDXGIVkInteropFactory1, 0x2a289dbd, 0x2d0a, 0x4a51, 0x89, 0xf7, __CRT_UUID_DECL(IDXGIVkInteropAdapter, 0x3a6d8f2c, 0xb0e8, 0x4ab4, 0xb4, 0xdc, 0x4f, 0xd2, 0x48, 0x91, 0xbf, 0xa5); __CRT_UUID_DECL(ID3D11VkExtDevice, 0x8a6e3c42, 0xf74c, 0x45b7, 0x82, 0x65, 0xa2, 0x31, 0xb6, 0x77, 0xca, 0x17); __CRT_UUID_DECL(ID3D11VkExtDevice1, 0xcfcf64ef, 0x9586, 0x46d0, 0xbc, 0xa4, 0x97, 0xcf, 0x2c, 0xa6, 0x1b, 0x06); +__CRT_UUID_DECL(ID3D11VkExtDevice2, 0x1d5c6a10, 0x9f4b, 0x4e0a, 0xb6, 0xa4, 0x2c, 0x8e, 0x13, 0xd7, 0x0f, 0x51); __CRT_UUID_DECL(ID3D11VkExtContext, 0xfd0bca13, 0x5cb6, 0x4c3a, 0x98, 0x7e, 0x47, 0x50, 0xde, 0x2c, 0xa7, 0x91); __CRT_UUID_DECL(ID3D11VkExtContext1, 0x874b09b2, 0xae0b, 0x41d8, 0x84, 0x76, 0x5f, 0x3b, 0x7a, 0x0e, 0x87, 0x9d); +__CRT_UUID_DECL(ID3D11VkExtContext2, 0x7e2c1b9f, 0x4d38, 0x4a11, 0x9b, 0x6e, 0x0f, 0x5a, 0x8c, 0x42, 0xd3, 0xe7); #endif diff --git a/src/nvapi/nvapi_d3d11_device.cpp b/src/nvapi/nvapi_d3d11_device.cpp index a96e1d2f..9960c476 100644 --- a/src/nvapi/nvapi_d3d11_device.cpp +++ b/src/nvapi/nvapi_d3d11_device.cpp @@ -1,5 +1,7 @@ #include "nvapi_d3d11_device.h" #include "../util/com_pointer.h" +#include "../util/util_string.h" +#include "../util/util_log.h" namespace dxvk { std::unordered_map> NvapiD3d11Device::m_nvapiDeviceMap; @@ -60,7 +62,7 @@ namespace dxvk { } NvapiD3d11Device::NvapiD3d11Device(ID3D11VkExtDevice* dxvkDevice, ID3D11VkExtContext* dxvkContext) - : m_dxvkDevice(static_cast(dxvkDevice)), m_dxvkContext(static_cast(dxvkContext)) { // NOLINT(*-pro-type-static-cast-downcast) + : m_dxvkDevice(static_cast(dxvkDevice)), m_dxvkContext(static_cast(dxvkContext)) { // NOLINT(*-pro-type-static-cast-downcast) m_supportsExtDepthBounds = m_dxvkDevice->GetExtensionSupport(D3D11_VK_EXT_DEPTH_BOUNDS); m_supportsNvxBinaryImport = m_dxvkDevice->GetExtensionSupport(D3D11_VK_NVX_BINARY_IMPORT); m_supportsExtBarrierControl = m_dxvkDevice->GetExtensionSupport(D3D11_VK_EXT_BARRIER_CONTROL); @@ -69,6 +71,34 @@ namespace dxvk { m_supportsExtDevice1 = probeInterfaceChain(dxvkDevice, {__uuidof(ID3D11VkExtDevice1)}) >= 1; m_supportsExtContext1 = probeInterfaceChain(dxvkContext, {__uuidof(ID3D11VkExtContext1)}) >= 1; + + // Revision-2 interfaces exist only on a DXVK that implements the + // multi-view entry points. Without them, shader creation falls back + // to the plain path. + m_supportsExtDevice2 = probeInterfaceChain(dxvkDevice, {__uuidof(ID3D11VkExtDevice1), __uuidof(ID3D11VkExtDevice2)}) >= 2; + m_supportsExtContext2 = probeInterfaceChain(dxvkContext, {__uuidof(ID3D11VkExtContext1), __uuidof(ID3D11VkExtContext2)}) >= 2; + } + + HRESULT NvapiD3d11Device::CreateVertexShaderNvSemantics(const void* pShaderBytecode, size_t bytecodeLength, ID3D11ClassLinkage* pClassLinkage, const D3D11_VK_NV_CUSTOM_SEMANTIC* pSemantics, uint32_t numSemantics, ID3D11VertexShader** ppVertexShader) const { + if (!m_supportsExtDevice2) + return E_NOTIMPL; + + return m_dxvkDevice->CreateVertexShaderNvSemantics(pShaderBytecode, bytecodeLength, pClassLinkage, pSemantics, numSemantics, ppVertexShader); + } + + HRESULT NvapiD3d11Device::CreateGeometryShaderNvSemantics(const void* pShaderBytecode, size_t bytecodeLength, ID3D11ClassLinkage* pClassLinkage, const D3D11_VK_NV_CUSTOM_SEMANTIC* pSemantics, uint32_t numSemantics, bool useViewportMask, ID3D11GeometryShader** ppGeometryShader) const { + if (!m_supportsExtDevice2) + return E_NOTIMPL; + + return m_dxvkDevice->CreateGeometryShaderNvSemantics(pShaderBytecode, bytecodeLength, pClassLinkage, pSemantics, numSemantics, useViewportMask, ppGeometryShader); + } + + bool NvapiD3d11Device::SetMultiviewMode(uint32_t numViews, bool independentViewportMask) const { + if (!m_supportsExtContext2) + return false; + + m_dxvkContext->SetMultiviewModeNV(numViews, independentViewportMask ? TRUE : FALSE); + return true; } HRESULT NvapiD3d11Device::SetDepthBoundsTest(const bool enable, const float minDepth, const float maxDepth) const { diff --git a/src/nvapi/nvapi_d3d11_device.h b/src/nvapi/nvapi_d3d11_device.h index fddb7b41..6d422aa2 100644 --- a/src/nvapi/nvapi_d3d11_device.h +++ b/src/nvapi/nvapi_d3d11_device.h @@ -30,14 +30,18 @@ namespace dxvk { [[nodiscard]] HRESULT CreateSamplerStateAndGetDriverHandle(const D3D11_SAMPLER_DESC* pSamplerDesc, ID3D11SamplerState** ppSamplerState, uint32_t* pDriverHandle) const; [[nodiscard]] bool IsFatbinPTXSupported() const; + [[nodiscard]] HRESULT CreateVertexShaderNvSemantics(const void* pShaderBytecode, size_t bytecodeLength, ID3D11ClassLinkage* pClassLinkage, const D3D11_VK_NV_CUSTOM_SEMANTIC* pSemantics, uint32_t numSemantics, ID3D11VertexShader** ppVertexShader) const; + [[nodiscard]] HRESULT CreateGeometryShaderNvSemantics(const void* pShaderBytecode, size_t bytecodeLength, ID3D11ClassLinkage* pClassLinkage, const D3D11_VK_NV_CUSTOM_SEMANTIC* pSemantics, uint32_t numSemantics, bool useViewportMask, ID3D11GeometryShader** ppGeometryShader) const; + bool SetMultiviewMode(uint32_t numViews, bool independentViewportMask) const; + private: [[nodiscard]] static NvapiD3d11Device* Get(IUnknown* deviceOrContext); static std::unordered_map> m_nvapiDeviceMap; static std::mutex m_mutex; - ID3D11VkExtDevice1* m_dxvkDevice{}; - ID3D11VkExtContext1* m_dxvkContext{}; + ID3D11VkExtDevice2* m_dxvkDevice{}; + ID3D11VkExtContext2* m_dxvkContext{}; bool m_supportsExtDepthBounds; bool m_supportsNvxBinaryImport; @@ -46,5 +50,7 @@ namespace dxvk { bool m_supportsExtMultiDrawIndirect; bool m_supportsExtDevice1; bool m_supportsExtContext1; + bool m_supportsExtDevice2; + bool m_supportsExtContext2; }; } diff --git a/src/nvapi_d3d.cpp b/src/nvapi_d3d.cpp index 09fc3843..caf84a93 100644 --- a/src/nvapi_d3d.cpp +++ b/src/nvapi_d3d.cpp @@ -3,6 +3,7 @@ #include "nvapi/nvapi_d3d_low_latency_device.h" #include "util/util_statuscode.h" #include "util/util_env.h" +#include "nvapi/nvapi_d3d11_device.h" using namespace dxvk; @@ -378,3 +379,203 @@ NVAPI_FUNCTION NvAPI_D3D_SetLatencyMarker(IUnknown* pDev, NV_LATENCY_MARKER_PARA return Error(n, alreadyLoggedError); } } + +// --------------------------------------------------------------------------- +// Simultaneous Multi-Projection (SMP) / Multi-View support +// +// These entry points let a game ask whether the GPU can do multi-view +// rendering, and set the mode. iRacing uses them for its "Nvidia Simultaneous +// Multi-Projection" option on triple screens. Capability queries are answered +// from the GPU generation. Mode changes are forwarded into DXVK when it +// implements the extended interfaces, and accepted but ignored otherwise. +// +// Hardware capability rules (same as NVIDIA's Windows driver): +// - Single-Pass Stereo (2 views): Pascal (GTX 10xx) or newer +static NvapiAdapter* GetMultiViewAdapter(IUnknown* pDeviceOrContext) { + if (!pDeviceOrContext || !nvapiAdapterRegistry) + return nullptr; + + // The game may hand us an ID3D11Device or an ID3D11DeviceContext, + // normalize to the device first + Com device; + if (FAILED(pDeviceOrContext->QueryInterface(IID_PPV_ARGS(&device)))) { + Com context; + if (FAILED(pDeviceOrContext->QueryInterface(IID_PPV_ARGS(&context)))) + return nullptr; + + context->GetDevice(&device); + } + + // Walk D3D11 device -> DXGI device -> DXGI adapter -> adapter LUID, + // then look that LUID up in dxvk-nvapi's adapter registry + Com dxgiDevice; + if (FAILED(device->QueryInterface(IID_PPV_ARGS(&dxgiDevice)))) + return nullptr; + + Com dxgiAdapter; + if (FAILED(dxgiDevice->GetAdapter(&dxgiAdapter))) + return nullptr; + + DXGI_ADAPTER_DESC adapterDesc{}; + if (FAILED(dxgiAdapter->GetDesc(&adapterDesc))) + return nullptr; + + return nvapiAdapterRegistry->FindAdapter(adapterDesc.AdapterLuid); +} + +NVAPI_FUNCTION NvAPI_D3D_QueryMultiViewSupport(IUnknown* pDevice, NV_QUERY_MULTIVIEW_SUPPORT_PARAMS* pQueryMultiViewSupportedParams) { + constexpr auto n = __func__; + thread_local bool alreadyLogged = false; + + if (log::tracing()) + log::trace(n, log::fmt::ptr(pDevice), log::fmt::ptr(pQueryMultiViewSupportedParams)); + + if (!nvapiAdapterRegistry) + return ApiNotInitialized(n); + + if (!pDevice || !pQueryMultiViewSupportedParams) + return InvalidArgument(n); + + if (pQueryMultiViewSupportedParams->version != NV_QUERY_MULTIVIEW_SUPPORT_PARAMS_VER1) + return IncompatibleStructVersion(n, pQueryMultiViewSupportedParams->version); + + auto pascalOrNewer = false; + auto turingOrNewer = false; + if (auto adapter = GetMultiViewAdapter(pDevice)) { + auto architectureId = adapter->GetArchitectureId(); + pascalOrNewer = architectureId >= NV_GPU_ARCHITECTURE_GP100; + turingOrNewer = architectureId >= NV_GPU_ARCHITECTURE_TU100; + } + + pQueryMultiViewSupportedParams->bMultiViewSupported = turingOrNewer; + pQueryMultiViewSupportedParams->bSinglePassStereoSupported = pascalOrNewer; + pQueryMultiViewSupportedParams->bSinglePassStereoXYZWSupported = turingOrNewer; + + return Ok(str::format(n, " (MultiView=", turingOrNewer ? "supported" : "unsupported", "/SPS=", pascalOrNewer ? "supported" : "unsupported", ")"), alreadyLogged); +} + +NVAPI_FUNCTION NvAPI_D3D_SetMultiViewMode(IUnknown* pDevOrContext, NV_MULTIVIEW_PARAMS* pMultiViewParams) { + constexpr auto n = __func__; + thread_local bool alreadyLogged = false; + + if (log::tracing()) + log::trace(n, log::fmt::ptr(pDevOrContext), log::fmt::ptr(pMultiViewParams)); + + if (!pDevOrContext || !pMultiViewParams) + return InvalidArgument(n); + + if (pMultiViewParams->version != NV_MULTIVIEW_PARAMS_VER1) + return IncompatibleStructVersion(n, pMultiViewParams->version); + + if (pMultiViewParams->numViews == 0 || pMultiViewParams->numViews > NV_MULTIVIEW_MAX_SUPPORTED_VIEWS) + return InvalidArgument(n); + + // Forward the toggle to DXVK when it implements the extended interfaces + if (auto device = NvapiD3d11Device::GetOrCreate(pDevOrContext); + device && device->SetMultiviewMode(pMultiViewParams->numViews, pMultiViewParams->independentViewportMaskEnable != 0)) + return Ok(str::format(n, " (numViews=", pMultiViewParams->numViews, ") (forwarded)"), alreadyLogged); + + // Otherwise accept and log the request without acting on it. NVIDIA + // documents this call as asynchronous, so returning OK only means the + // arguments were valid. + return Ok(str::format(n, " (numViews=", pMultiViewParams->numViews, ") (not implemented, ignoring)"), alreadyLogged); +} + +NVAPI_FUNCTION NvAPI_D3D_QuerySinglePassStereoSupport(IUnknown* pDevice, NV_QUERY_SINGLE_PASS_STEREO_SUPPORT_PARAMS* pQuerySinglePassStereoSupportedParams) { + constexpr auto n = __func__; + thread_local bool alreadyLogged = false; + + if (log::tracing()) + log::trace(n, log::fmt::ptr(pDevice), log::fmt::ptr(pQuerySinglePassStereoSupportedParams)); + + if (!nvapiAdapterRegistry) + return ApiNotInitialized(n); + + if (!pDevice || !pQuerySinglePassStereoSupportedParams) + return InvalidArgument(n); + + auto pascalOrNewer = false; + auto turingOrNewer = false; + if (auto adapter = GetMultiViewAdapter(pDevice)) { + auto architectureId = adapter->GetArchitectureId(); + pascalOrNewer = architectureId >= NV_GPU_ARCHITECTURE_GP100; + turingOrNewer = architectureId >= NV_GPU_ARCHITECTURE_TU100; + } + + switch (pQuerySinglePassStereoSupportedParams->version) { + case NV_QUERY_SINGLE_PASS_STEREO_SUPPORT_PARAMS_VER1: { + auto pParamsV1 = reinterpret_cast(pQuerySinglePassStereoSupportedParams); + pParamsV1->bSinglePassStereoSupported = pascalOrNewer; + break; + } + case NV_QUERY_SINGLE_PASS_STEREO_SUPPORT_PARAMS_VER2: + pQuerySinglePassStereoSupportedParams->bSinglePassStereoSupported = pascalOrNewer; + pQuerySinglePassStereoSupportedParams->bSinglePassStereoXYZWSupported = turingOrNewer; + break; + default: + return IncompatibleStructVersion(n, pQuerySinglePassStereoSupportedParams->version); + } + + return Ok(str::format(n, " (SPS=", pascalOrNewer ? "supported" : "unsupported", ")"), alreadyLogged); +} + +NVAPI_FUNCTION NvAPI_D3D_SetSinglePassStereoMode(IUnknown* pDevOrContext, NvU32 numViews, NvU32 renderTargetIndexOffset, NvU8 independentViewportMaskEnable) { + constexpr auto n = __func__; + thread_local bool alreadyLogged = false; + + if (log::tracing()) + log::trace(n, log::fmt::ptr(pDevOrContext), numViews, renderTargetIndexOffset, independentViewportMaskEnable); + + if (!pDevOrContext || numViews == 0) + return InvalidArgument(n); + + // Accept and log the request, see NvAPI_D3D_SetMultiViewMode + return Ok(str::format(n, " (numViews=", numViews, ") (not implemented, ignoring)"), alreadyLogged); +} + +NVAPI_FUNCTION NvAPI_D3D_QueryModifiedWSupport(IUnknown* pDev, NV_QUERY_MODIFIED_W_SUPPORT_PARAMS* pQueryModifiedWSupportedParams) { + constexpr auto n = __func__; + thread_local bool alreadyLogged = false; + + if (log::tracing()) + log::trace(n, log::fmt::ptr(pDev), log::fmt::ptr(pQueryModifiedWSupportedParams)); + + if (!nvapiAdapterRegistry) + return ApiNotInitialized(n); + + if (!pDev || !pQueryModifiedWSupportedParams) + return InvalidArgument(n); + + if (pQueryModifiedWSupportedParams->version != NV_QUERY_MODIFIED_W_SUPPORT_PARAMS_VER1) + return IncompatibleStructVersion(n, pQueryModifiedWSupportedParams->version); + + // Modified-W (Lens Matched Shading) needs Pascal or newer + auto pascalOrNewer = false; + if (auto adapter = GetMultiViewAdapter(pDev)) + pascalOrNewer = adapter->GetArchitectureId() >= NV_GPU_ARCHITECTURE_GP100; + + pQueryModifiedWSupportedParams->bModifiedWSupported = pascalOrNewer; + + // Accept and log the request, see NvAPI_D3D_SetMultiViewMode + return Ok(str::format(n, " (ModifiedW=", pascalOrNewer ? "supported" : "unsupported", ")"), alreadyLogged); +} + +NVAPI_FUNCTION NvAPI_D3D_SetModifiedWMode(IUnknown* pDevOrContext, NV_MODIFIED_W_PARAMS* psModifiedWParams) { + constexpr auto n = __func__; + thread_local bool alreadyLogged = false; + + if (log::tracing()) + log::trace(n, log::fmt::ptr(pDevOrContext), log::fmt::ptr(psModifiedWParams)); + + if (!pDevOrContext || !psModifiedWParams) + return InvalidArgument(n); + + if (psModifiedWParams->version != NV_MODIFIED_W_PARAMS_VER1) + return IncompatibleStructVersion(n, psModifiedWParams->version); + + if (psModifiedWParams->numEntries > NV_MODIFIED_W_MAX_VIEWPORTS) + return InvalidArgument(n); + + // Accept and log the request, see NvAPI_D3D_SetMultiViewMode + return Ok(str::format(n, " (numEntries=", psModifiedWParams->numEntries, ") (not implemented, ignoring)"), alreadyLogged); +} \ No newline at end of file diff --git a/src/nvapi_d3d11.cpp b/src/nvapi_d3d11.cpp index 6851e8e8..9e8c72df 100644 --- a/src/nvapi_d3d11.cpp +++ b/src/nvapi_d3d11.cpp @@ -549,3 +549,151 @@ NVAPI_FUNCTION NvAPI_D3D11_GetCudaTextureObject(ID3D11Device* pDevice, NvU32 srv return Error(n, alreadyLoggedError); } } + +// --------------------------------------------------------------------------- +// Extended shader creation +// +// A game calls these to create vertex and geometry shaders carrying +// NVIDIA-specific extensions: custom semantics such as NV_POSITION_VIEW_1 and +// NV_VIEWPORT_MASK, and viewport-broadcast flags. Without these entry points +// the game gets a null pointer, shader setup fails, and it may hang at +// session load. +// +// Where DXVK implements the extended interfaces, the request is forwarded +// with the semantic list intact. Otherwise the shader is created through the +// plain D3D11 path: the bytecode is ordinary DXBC and the shader is valid, but +// the NVIDIA-specific outputs are not acted on, so multi-view rendering will +// not work. +// --------------------------------------------------------------------------- + +// Renders the game's custom-semantic requests as a readable string for the log, +// e.g. "NV_VIEWPORT_MASK(type=2,reg=auto)". These names/types are the exact +// wiring a real SMP implementation will need to honor. +static std::string DescribeCustomSemantics(NvU32 numCustomSemantics, const NV_CUSTOM_SEMANTIC* pCustomSemantics) { + if (numCustomSemantics == 0 || !pCustomSemantics) + return "none"; + + std::string result; + for (auto i = 0U; i < numCustomSemantics; i++) { + if (i > 0) + result += ", "; + + auto& semantic = pCustomSemantics[i]; + result += str::format( + semantic.NVCustomSemanticNameString, + "(type=", semantic.NVCustomSemanticType, + ",reg=", semantic.RegisterSpecified ? str::format(semantic.RegisterNum) : std::string("auto"), + ")"); + } + + return result; +} + +NVAPI_FUNCTION NvAPI_D3D11_CreateVertexShaderEx(ID3D11Device* pDevice, const void* pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage* pClassLinkage, const NvAPI_D3D11_CREATE_VERTEX_SHADER_EX* pCreateVertexShaderExArgs, ID3D11VertexShader** ppVertexShader) { + constexpr auto n = __func__; + thread_local bool alreadyLoggedError = false; + thread_local bool alreadyLoggedOk = false; + + if (log::tracing()) + log::trace(n, log::fmt::ptr(pDevice), log::fmt::ptr(pShaderBytecode), BytecodeLength, log::fmt::ptr(pClassLinkage), log::fmt::ptr(pCreateVertexShaderExArgs), log::fmt::ptr(ppVertexShader)); + + if (!pDevice || !pShaderBytecode || BytecodeLength == 0 || !pCreateVertexShaderExArgs || !ppVertexShader) + return InvalidArgument(n); + + auto version = pCreateVertexShaderExArgs->version; + if (version != NVAPI_D3D11_CREATEVERTEXSHADEREX_VER_1 + && version != NVAPI_D3D11_CREATEVERTEXSHADEREX_VER_2 + && version != NVAPI_D3D11_CREATEVERTEXSHADEREX_VER_3) + return IncompatibleStructVersion(n, version); + + if (pCreateVertexShaderExArgs->NumCustomSemantics > NV_CUSTOM_SEMANTIC_MAX_LIMIT) + return InvalidArgument(n); + + // forward to an SMP-capable DXVK when present + if (auto device = NvapiD3d11Device::GetOrCreate(pDevice)) { + std::array semantics{}; + for (auto i = 0U; i < pCreateVertexShaderExArgs->NumCustomSemantics; i++) { + auto& src = pCreateVertexShaderExArgs->pCustomSemantics[i]; + auto& dst = semantics[i]; + dst.Type = src.NVCustomSemanticType; + std::strncpy(dst.Name, src.NVCustomSemanticNameString, sizeof(dst.Name) - 1); + dst.RegisterSpecified = src.RegisterSpecified; + dst.RegisterNum = src.RegisterNum; + dst.RegisterMask = src.RegisterMask; + } + + if (SUCCEEDED(device->CreateVertexShaderNvSemantics(pShaderBytecode, BytecodeLength, pClassLinkage, semantics.data(), pCreateVertexShaderExArgs->NumCustomSemantics, ppVertexShader))) + return Ok(str::format(n, " (forwarded to DXVK)"), alreadyLoggedOk); + } + // Fallback: the shader is created as a plain VS when DXVK does not + // support the extended entry point, or when forwarding failed. + if (pCreateVertexShaderExArgs->NumCustomSemantics > 0) + log::info(str::format(n, ": semantics [", + DescribeCustomSemantics(pCreateVertexShaderExArgs->NumCustomSemantics, pCreateVertexShaderExArgs->pCustomSemantics), + "] - created as plain VS, extensions ignored")); + + if (FAILED(pDevice->CreateVertexShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppVertexShader))) + return Error(n, alreadyLoggedError); + + return Ok(str::format(n, " (NumCustomSemantics=", pCreateVertexShaderExArgs->NumCustomSemantics, ") (pass-through)"), alreadyLoggedOk); +} + +NVAPI_FUNCTION NvAPI_D3D11_CreateGeometryShaderEx_2(ID3D11Device* pDevice, const void* pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage* pClassLinkage, const NvAPI_D3D11_CREATE_GEOMETRY_SHADER_EX* pCreateGeometryShaderExArgs, ID3D11GeometryShader** ppGeometryShader) { + constexpr auto n = __func__; + thread_local bool alreadyLoggedError = false; + thread_local bool alreadyLoggedOk = false; + + if (log::tracing()) + log::trace(n, log::fmt::ptr(pDevice), log::fmt::ptr(pShaderBytecode), BytecodeLength, log::fmt::ptr(pClassLinkage), log::fmt::ptr(pCreateGeometryShaderExArgs), log::fmt::ptr(ppGeometryShader)); + + if (!pDevice || !pShaderBytecode || BytecodeLength == 0 || !pCreateGeometryShaderExArgs || !ppGeometryShader) + return InvalidArgument(n); + + if (pCreateGeometryShaderExArgs->version != NVAPI_D3D11_CREATEGEOMETRYSHADEREX_2_VERSION) + return IncompatibleStructVersion(n, pCreateGeometryShaderExArgs->version); + + if (pCreateGeometryShaderExArgs->NumCustomSemantics > NV_CUSTOM_SEMANTIC_MAX_LIMIT) + return InvalidArgument(n); + + // GS descriptors carry the SMP-relevant flags, so log every call's flags. + // (ForceFastGS note: NVIDIA requires such a GS to be a trivial pass-through, + // so running it as a NORMAL geometry shader is functionally identical - + // we only lose the hardware fast path, not correctness.) + log::info(str::format(n, ": flags [ViewportMask=", pCreateGeometryShaderExArgs->UseViewportMask, + ", OffsetRtIndexByVpIndex=", pCreateGeometryShaderExArgs->OffsetRtIndexByVpIndex, + ", ForceFastGS=", pCreateGeometryShaderExArgs->ForceFastGS, + ", DontUseViewportOrder=", pCreateGeometryShaderExArgs->DontUseViewportOrder, + ", CoordinateSwizzle=", pCreateGeometryShaderExArgs->UseCoordinateSwizzle, + ", SpecificShaderExt=", pCreateGeometryShaderExArgs->UseSpecificShaderExt, + "] semantics [", + DescribeCustomSemantics(pCreateGeometryShaderExArgs->NumCustomSemantics, pCreateGeometryShaderExArgs->pCustomSemantics), + "] - created as plain GS, extensions ignored")); + + // Forward to DXVK when it implements the extended interfaces + if (auto device = NvapiD3d11Device::GetOrCreate(pDevice)) { + std::array semantics{}; + for (auto i = 0U; i < pCreateGeometryShaderExArgs->NumCustomSemantics; i++) { + auto& src = pCreateGeometryShaderExArgs->pCustomSemantics[i]; + auto& dst = semantics[i]; + dst.Type = src.NVCustomSemanticType; + std::strncpy(dst.Name, src.NVCustomSemanticNameString, sizeof(dst.Name) - 1); + dst.RegisterSpecified = src.RegisterSpecified; + dst.RegisterNum = src.RegisterNum; + dst.RegisterMask = src.RegisterMask; + } + + if (SUCCEEDED(device->CreateGeometryShaderNvSemantics(pShaderBytecode, BytecodeLength, pClassLinkage, semantics.data(), pCreateGeometryShaderExArgs->NumCustomSemantics, pCreateGeometryShaderExArgs->UseViewportMask != 0, ppGeometryShader))) + return Ok(str::format(n, " (forwarded to DXVK)"), alreadyLoggedOk); + } + + // Fallback: create the shader without the extensions + if (pCreateGeometryShaderExArgs->NumCustomSemantics > 0) + log::info(str::format(n, ": flags [ViewportMask=", pCreateGeometryShaderExArgs->UseViewportMask, + "] semantics [", DescribeCustomSemantics(pCreateGeometryShaderExArgs->NumCustomSemantics, pCreateGeometryShaderExArgs->pCustomSemantics), + "] - created as plain GS, extensions ignored")); + + if (FAILED(pDevice->CreateGeometryShader(pShaderBytecode, BytecodeLength, pClassLinkage, ppGeometryShader))) + return Error(n, alreadyLoggedError); + + return Ok(str::format(n, " (NumCustomSemantics=", pCreateGeometryShaderExArgs->NumCustomSemantics, ") (pass-through fallback)"), alreadyLoggedOk); +} \ No newline at end of file diff --git a/src/nvapi_interface.cpp b/src/nvapi_interface.cpp index 3e455589..b96d4be2 100644 --- a/src/nvapi_interface.cpp +++ b/src/nvapi_interface.cpp @@ -98,6 +98,8 @@ NVAPI_QUERY_INTERFACE nvapi_QueryInterface(NvU32 id) { INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D11_GetResourceGPUVirtualAddressEx) INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D11_CreateDevice) INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D11_CreateDeviceAndSwapChain) + INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D11_CreateVertexShaderEx) + INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D11_CreateGeometryShaderEx_2) INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D1x_GetGraphicsCapabilities) INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D1x_Present) INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D11_MultiGPU_Init) @@ -144,6 +146,12 @@ NVAPI_QUERY_INTERFACE nvapi_QueryInterface(NvU32 id) { INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D_Sleep) INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D_GetLatency) INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D_SetLatencyMarker) + INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D_QueryMultiViewSupport) + INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D_SetMultiViewMode) + INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D_QuerySinglePassStereoSupport) + INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D_SetSinglePassStereoMode) + INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D_QueryModifiedWSupport) + INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D_SetModifiedWMode) INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_Vulkan_InitLowLatencyDevice) INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_Vulkan_DestroyLowLatencyDevice) INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_Vulkan_GetSleepStatus) diff --git a/tests/nvapi_d3d11.cpp b/tests/nvapi_d3d11.cpp index 5cb4d0c6..3314edc7 100644 --- a/tests/nvapi_d3d11.cpp +++ b/tests/nvapi_d3d11.cpp @@ -27,6 +27,8 @@ TEST_CASE("D3D11 methods succeed", "[.d3d11]") { .LR_SIDE_EFFECT(*_2 = static_cast(&device)) .LR_SIDE_EFFECT(deviceRefCount++) .RETURN(S_OK); + ALLOW_CALL(device, QueryInterface(__uuidof(ID3D11VkExtDevice2), _)) + .RETURN(E_NOINTERFACE); ALLOW_CALL(device, AddRef()) .LR_SIDE_EFFECT(deviceRefCount++) .RETURN(deviceRefCount); @@ -57,6 +59,8 @@ TEST_CASE("D3D11 methods succeed", "[.d3d11]") { .LR_SIDE_EFFECT(*_2 = static_cast(&context)) .LR_SIDE_EFFECT(contextRefCount++) .RETURN(S_OK); + ALLOW_CALL(context, QueryInterface(__uuidof(ID3D11VkExtContext2), _)) + .RETURN(E_NOINTERFACE); ALLOW_CALL(context, AddRef()) .LR_SIDE_EFFECT(contextRefCount++) .RETURN(contextRefCount);