Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions src/interfaces/dxvk_interfaces.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
32 changes: 31 additions & 1 deletion src/nvapi/nvapi_d3d11_device.cpp
Original file line number Diff line number Diff line change
@@ -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<IUnknown*, std::shared_ptr<NvapiD3d11Device>> NvapiD3d11Device::m_nvapiDeviceMap;
Expand Down Expand Up @@ -60,7 +62,7 @@ namespace dxvk {
}

NvapiD3d11Device::NvapiD3d11Device(ID3D11VkExtDevice* dxvkDevice, ID3D11VkExtContext* dxvkContext)
: m_dxvkDevice(static_cast<ID3D11VkExtDevice1*>(dxvkDevice)), m_dxvkContext(static_cast<ID3D11VkExtContext1*>(dxvkContext)) { // NOLINT(*-pro-type-static-cast-downcast)
: m_dxvkDevice(static_cast<ID3D11VkExtDevice2*>(dxvkDevice)), m_dxvkContext(static_cast<ID3D11VkExtContext2*>(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);
Expand All @@ -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 {
Expand Down
10 changes: 8 additions & 2 deletions src/nvapi/nvapi_d3d11_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<IUnknown*, std::shared_ptr<NvapiD3d11Device>> 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;
Expand All @@ -46,5 +50,7 @@ namespace dxvk {
bool m_supportsExtMultiDrawIndirect;
bool m_supportsExtDevice1;
bool m_supportsExtContext1;
bool m_supportsExtDevice2;
bool m_supportsExtContext2;
};
}
201 changes: 201 additions & 0 deletions src/nvapi_d3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<ID3D11Device> device;
if (FAILED(pDeviceOrContext->QueryInterface(IID_PPV_ARGS(&device)))) {
Com<ID3D11DeviceContext> 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<IDXGIDevice> dxgiDevice;
if (FAILED(device->QueryInterface(IID_PPV_ARGS(&dxgiDevice))))
return nullptr;

Com<IDXGIAdapter> 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<NV_QUERY_SINGLE_PASS_STEREO_SUPPORT_PARAMS_V1*>(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);
}
Loading