vulkan: run the engine on the game's device, without staging

Native Vulkan NGX callers now get d4r's own network for presets K, M and L,
recorded straight into the caller's command buffer on the game's VkDevice with
the same GameUpscaler/GameUpscalerM adapters the D3D12 path uses through
vkd3d-proton. No CPU staging, CUDA evaluation or host wait. D4R_VULKAN_ENGINE=0
keeps the staging path, and anything the engine does not cover still goes there
with a logged reason.

- Inputs are sampled through the caller's views (SHADER_READ_ONLY_OPTIMAL, or
  GENERAL with D4R_VULKAN_INPUT_GENERAL=1); depth/stencil views get a depth-only
  view. The output must be an RGBA16F (or RGB10A2 with an RGB10 model) storage
  image the engine writes directly, since the resource contract gives STORAGE
  usage only.
- Every recording ends with an event; engine objects of a released feature are
  destroyed once all recorded events fired, or at shutdown.
- GetFeatureDeviceExtensionRequirements reports VK_KHR_cooperative_matrix and
  VK_EXT_shader_float8 where available. It applies d4r.ini first: games ask
  before NGX init, and engine_requested() would otherwise cache "off".
- The engine accepts R16G16_SNORM motion vectors (Enshrouded).
- Engine model loaders accept a native device (VulkanInterop::native).
- Harness: D4R_HARNESS_ENGINE_DEVICE=1 (1.3 device with the engine's
  features), D4R_HARNESS_ALLOW_BLACK=1 (staging K's black reset frame).

Harness vs the staging path (RX 9070, GE-Proton11-7, 640x360 -> 1280x720):
K 99.6 % bit-identical, rest within one half-float ulp; M 52.7 dB, L 54.3 dB
PSNR; dynamic, recreate and discard pass for K and M. In game, Enshrouded runs
preset M on this path (docs/vulkan.md, SUPPORTED_GAMES.md).

Signed-off-by: itchyfeetleech <[email protected]>
This commit is contained in:
itchyfeetleech committed 2026-10-18 14:45:41 +13:00
1 parent 0e851b7f90
commit 60da7b9019
6 files changed
+508 -29

No files matched your search

+11
View File
@@ -45,3 +45,14 @@ Game-specific setup:
- **Dying Light: The Beast:** use DLSS inputs; with FSR or XeSS inputs the screen stays black. The game passes its motion vectors as `R16G16B16A16_TYPELESS`, which needs a d4r build from 2026-09-28 or later (older builds reject every frame and show a black screen). - **Dying Light: The Beast:** use DLSS inputs; with FSR or XeSS inputs the screen stays black. The game passes its motion vectors as `R16G16B16A16_TYPELESS`, which needs a d4r build from 2026-09-28 or later (older builds reject every frame and show a black screen).
Choose a model with `Model = E`, `Model = K`, or `Model = M` under `[DLSS]` in the game's `d4r/d4r.ini`. Model compatibility does not imply equal performance; see the [Townfall measurements](docs/performance.md) for measured frame rates. Choose a model with `Model = E`, `Model = K`, or `Model = M` under `[DLSS]` in the game's `d4r/d4r.ini`. Model compatibility does not imply equal performance; see the [Townfall measurements](docs/performance.md) for measured frame rates.
Vulkan games (native Vulkan frontend, engine path; see docs/vulkan.md):
- **Enshrouded** (Vulkan only): preset M verified in game on RX 9070 (gfx1201), GE-Proton11-7, through the game's own DLSS
inputs. Install OptiScaler as `dbghelp.dll` (the exe imports it before it creates its Vulkan device; `dxgi.dll` loads
too late) with `WINEDLLOVERRIDES="dbghelp=n,b"`, and set in `OptiScaler.ini`: `[Upscalers] VulkanUpscaler=dlss`,
`[Spoofing] Vulkan=true`, `VulkanExtensionSpoofing=false`, `Dxgi=false` (a Vulkan game needs no DXGI spoofing; the
verified setup ran without it), and `[InitFlags] AutoExposure=true` (the game sends no exposure; needed for K). The game's motion vectors are `R16G16_SNORM`, which the staging path rejects. Launch options as for
D3D12 games (`PROTON_FORCE_NVAPI=1 DXVK_NVAPI_GPU_ARCH=AD100`). The lsfg-vk layer made the game's swap chain creation
fail under GE-Proton11-7 even without d4r; disable it (`DISABLE_LSFGVK=1`) if the game exits with
`Graphics_InitializationFailed`.
+37
View File
@@ -126,6 +126,43 @@ and the NGX core signatures used by
Host event signaling follows the Host event signaling follows the
[Vulkan event synchronization contract](https://docs.vulkan.org/refpages/latest/refpages/source/vkCmdWaitEvents.html). [Vulkan event synchronization contract](https://docs.vulkan.org/refpages/latest/refpages/source/vkCmdWaitEvents.html).
## Engine path (no staging)
With the engine on (`[Engine] Enabled`, `D4R_ENGINE=1`, which `auto` sets when `d4r\engine` holds models), the
Vulkan frontend records d4r's own network for presets K, M and L straight into the caller's command buffer on the
game's own `VkDevice`: the same `GameUpscaler` / `GameUpscalerM` adapters the D3D12 path uses through vkd3d-proton.
There is no CPU staging, no CUDA evaluation and no host wait; the result belongs to the frame being recorded.
`D4R_VULKAN_ENGINE=0` keeps the staging path. Anything the engine does not cover returns to the staging path,
logged once as `native Vulkan engine (K|M|L): <reason>; feature N uses the staging path`.
- Inputs are sampled through the caller's views in `SHADER_READ_ONLY_OPTIMAL` (`D4R_VULKAN_INPUT_GENERAL=1`
for `GENERAL`); depth/stencil views get a depth-only view of their own. The output must be an RGBA16F (or,
with an RGB10 model, A2B10G10R10) image the engine writes directly in `GENERAL`, because the resource contract
gives it `STORAGE` usage only.
- Each recording ends with a `vkCmdSetEvent`; a released feature's engine objects are destroyed once every
recorded event has fired, or at shutdown after the application's device-idle point. Unsubmitted recordings are
never waited on.
- Device features: the engine's shaders use `VK_KHR_cooperative_matrix` (and `VK_EXT_shader_float8` for M and L).
`NVSDK_NGX_VULKAN_GetFeatureDeviceExtensionRequirements` now reports both where the adapter has them, so games
that honour NGX's requirements enable the extensions. Their feature bits are still up to the game; RADV compiles
and runs the engine without them (bit-identical harness output on a plain 1.1 device), but that is outside the
Vulkan specification, and a layer that enables the features is the clean fix.
- Not covered: Ray Reconstruction, bias-colour and transparency masks, and K's game exposure texture in a
non-float format. With AutoExposure off and no exposure texture, K is refused; `[InitFlags] AutoExposure=true` in
`OptiScaler.ini` covers games that send no exposure (M and L ignore exposure).
Harness checks (RX 9070, gfx1201, GE-Proton11-7, 640x360 -> 1280x720, frame 8, against the staging path):
K 99.6 % bit-identical, the rest within one half-float ulp; M 52.7 dB and L 54.3 dB PSNR (FP8 rounding) with equal
means; `dynamic`, `recreate` and `discard` pass for K and M. `D4R_HARNESS_ENGINE_DEVICE=1` creates a 1.3 device with
the engine's features; `D4R_HARNESS_ALLOW_BLACK=1` tolerates NGX's black reset frame for K on the staging path.
```sh
D4R_ENGINE=1 D4R_DLSS_PRESET=13 D4R_HARNESS_ENGINE_DEVICE=1 D4R_ENGINE_MODEL_M_DIR=PORTABLE_D4R_DIR/engine/m \
scripts/run_vulkan_dlss_harness_proton.sh PROTON_DIR PORTABLE_D4R_DIR build/engine-m.rgba16f 8
```
In game: Enshrouded (Vulkan only) runs preset M on this path at 1505x847 -> 2560x1440; see SUPPORTED_GAMES.md.
## Synthetic harness ## Synthetic harness
The harness creates an AMD Vulkan device without NGX-specific device extensions, The harness creates an AMD Vulkan device without NGX-specific device extensions,
+2 -1
View File
@@ -16,7 +16,8 @@ bool gameColorFormat(VkFormat format) {
} }
} }
bool gameMotionFormat(VkFormat format) { bool gameMotionFormat(VkFormat format) {
return format == VK_FORMAT_R16G16_SFLOAT || format == VK_FORMAT_R32G32_SFLOAT || // R16G16_SNORM (Enshrouded): sampled as floats in [-1, 1]; MV.Scale carries them to pixels as for float vectors.
return format == VK_FORMAT_R16G16_SFLOAT || format == VK_FORMAT_R32G32_SFLOAT || format == VK_FORMAT_R16G16_SNORM ||
format == VK_FORMAT_R16G16B16A16_SFLOAT || format == VK_FORMAT_R32G32B32A32_SFLOAT; format == VK_FORMAT_R16G16B16A16_SFLOAT || format == VK_FORMAT_R32G32B32A32_SFLOAT;
} }
bool gameDepthFormat(VkFormat format, VkImageAspectFlags aspect) { bool gameDepthFormat(VkFormat format, VkImageAspectFlags aspect) {
+6 -4
View File
@@ -3385,6 +3385,8 @@ struct VulkanInterop
// VK_EXT_shader_float8 (vkd3d-proton enables it when the GPU has it): M's and L's engine layers are FP8 // VK_EXT_shader_float8 (vkd3d-proton enables it when the GPU has it): M's and L's engine layers are FP8
// cooperative-matrix shaders, which RADV's compiler crashes on without it (RDNA3) // cooperative-matrix shaders, which RADV's compiler crashes on without it (RDNA3)
bool float8 = false; bool float8 = false;
// The engine runs on the game's own Vulkan device (native Vulkan NGX frontend): no vkd3d interop objects.
bool native = false;
PFN_vkGetPhysicalDeviceFormatProperties formatProperties = nullptr; PFN_vkGetPhysicalDeviceFormatProperties formatProperties = nullptr;
PFN_vkCreateBuffer createBuffer = nullptr; PFN_vkCreateBuffer createBuffer = nullptr;
PFN_vkDestroyBuffer destroyBuffer = nullptr; PFN_vkDestroyBuffer destroyBuffer = nullptr;
@@ -9332,7 +9334,7 @@ static void init_engine_backend()
WideCharToMultiByte(CP_ACP, 0, (g_portable.dir + L"\\engine\\k").c_str(), -1, folder, sizeof(folder), nullptr, nullptr); WideCharToMultiByte(CP_ACP, 0, (g_portable.dir + L"\\engine\\k").c_str(), -1, folder, sizeof(folder), nullptr, nullptr);
directory = folder; directory = folder;
} }
if (g_vk.interop == nullptr || g_vk.lifetime == nullptr || g_vk.device == VK_NULL_HANDLE || d4r::vkTable.CreateBuffer == nullptr) if ((!g_vk.native && (g_vk.interop == nullptr || g_vk.lifetime == nullptr)) || g_vk.device == VK_NULL_HANDLE || d4r::vkTable.CreateBuffer == nullptr)
return; return;
// A Unix path names the same file through Wine's Z: drive. // A Unix path names the same file through Wine's Z: drive.
const std::string path = directory[0] == '/' ? "Z:" + directory : directory; const std::string path = directory[0] == '/' ? "Z:" + directory : directory;
@@ -9361,7 +9363,7 @@ static void init_engine_backend_ldr()
WideCharToMultiByte(CP_ACP, 0, (g_portable.dir + L"\\engine\\k-ldr").c_str(), -1, folder, sizeof(folder), nullptr, nullptr); WideCharToMultiByte(CP_ACP, 0, (g_portable.dir + L"\\engine\\k-ldr").c_str(), -1, folder, sizeof(folder), nullptr, nullptr);
directory = folder; directory = folder;
} }
if (g_vk.interop == nullptr || g_vk.lifetime == nullptr || g_vk.device == VK_NULL_HANDLE || d4r::vkTable.CreateBuffer == nullptr) if ((!g_vk.native && (g_vk.interop == nullptr || g_vk.lifetime == nullptr)) || g_vk.device == VK_NULL_HANDLE || d4r::vkTable.CreateBuffer == nullptr)
return; return;
const std::string path = directory[0] == '/' ? "Z:" + directory : directory; const std::string path = directory[0] == '/' ? "Z:" + directory : directory;
try try
@@ -9457,7 +9459,7 @@ static void init_engine_backend_m_variant(bool presetL, bool ldr)
WideCharToMultiByte(CP_ACP, 0, (g_portable.dir + sub).c_str(), -1, folder, sizeof(folder), nullptr, nullptr); WideCharToMultiByte(CP_ACP, 0, (g_portable.dir + sub).c_str(), -1, folder, sizeof(folder), nullptr, nullptr);
directory = folder; directory = folder;
} }
if (g_vk.interop == nullptr || g_vk.lifetime == nullptr || g_vk.device == VK_NULL_HANDLE || d4r::vkTable.CreateBuffer == nullptr) if ((!g_vk.native && (g_vk.interop == nullptr || g_vk.lifetime == nullptr)) || g_vk.device == VK_NULL_HANDLE || d4r::vkTable.CreateBuffer == nullptr)
{ {
logf("engine backend (%s): Vulkan interop is unavailable", name); logf("engine backend (%s): Vulkan interop is unavailable", name);
return; return;
@@ -10303,7 +10305,7 @@ static std::string rr_model_directory(int slot)
static void init_engine_backend_rr(int slot) static void init_engine_backend_rr(int slot)
{ {
const std::string path = rr_model_directory(slot); const std::string path = rr_model_directory(slot);
if (g_vk.interop == nullptr || g_vk.lifetime == nullptr || g_vk.device == VK_NULL_HANDLE || d4r::vkTable.CreateBuffer == nullptr) if ((!g_vk.native && (g_vk.interop == nullptr || g_vk.lifetime == nullptr)) || g_vk.device == VK_NULL_HANDLE || d4r::vkTable.CreateBuffer == nullptr)
{ {
logf("engine backend (Ray Reconstruction): Vulkan interop is unavailable"); logf("engine backend (Ray Reconstruction): Vulkan interop is unavailable");
g_engineR.state[slot] = 3; g_engineR.state[slot] = 3;
+423 -21
View File
@@ -9,6 +9,12 @@
struct NativeVkApi struct NativeVkApi
{ {
VkDevice device = VK_NULL_HANDLE; VkDevice device = VK_NULL_HANDLE;
// The game's instance and adapter: d4r's engine (engine/) loads its entry points on this device.
VkInstance instance = VK_NULL_HANDLE;
VkPhysicalDevice physical = VK_NULL_HANDLE;
PFN_vkGetInstanceProcAddr instanceProc = nullptr;
PFN_vkCreateImageView createView = nullptr;
PFN_vkDestroyImageView destroyView = nullptr;
VkPhysicalDeviceMemoryProperties memory = {}; VkPhysicalDeviceMemoryProperties memory = {};
PFN_vkCreateBuffer createBuffer = nullptr; PFN_vkCreateBuffer createBuffer = nullptr;
PFN_vkDestroyBuffer destroyBuffer = nullptr; PFN_vkDestroyBuffer destroyBuffer = nullptr;
@@ -70,12 +76,31 @@ struct NativeVkFrame
uint32_t number = 0; uint32_t number = 0;
VkCommandBuffer commandBuffer = VK_NULL_HANDLE; VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
}; };
// The engine path (D4R_ENGINE): d4r's own network recorded straight into the game's command buffer, no staging.
// Each recorded evaluation sets an event at its end, so the engine objects are destroyed only after the GPU has
// finished every submitted recording (or at shutdown, after the application's device idle).
struct NativeVkEngineView
{
VkImage image = VK_NULL_HANDLE;
VkFormat format = VK_FORMAT_UNDEFINED;
VkImageAspectFlags aspect = 0;
VkImageView view = VK_NULL_HANDLE;
};
struct NativeVkEngineState
{
std::unique_ptr<d4r::GameUpscaler> k;
std::unique_ptr<d4r::GameUpscalerM> m, mDynamic;
std::vector<VkEvent> pending, idle; // set at the end of a recording / reusable after it was set
std::vector<NativeVkEngineView> views; // depth-only views the engine samples where the game's view has stencil
};
struct NativeVkFeature struct NativeVkFeature
{ {
Feature* feature; Feature* feature;
std::vector<NativeVkFrame*> frames; std::vector<NativeVkFrame*> frames;
std::vector<NativeVkFrame*> reusable; std::vector<NativeVkFrame*> reusable;
std::shared_ptr<NativeVkEngineState> engine; // null until the engine records a frame
}; };
static std::vector<std::shared_ptr<NativeVkEngineState>> g_nativeVkEngineRetired;
static std::vector<NativeVkFeature> g_nativeVkFeatures; static std::vector<NativeVkFeature> g_nativeVkFeatures;
// Release can happen while recordings are still pending. Their GPU objects // Release can happen while recordings are still pending. Their GPU objects
// outlive CUDA feature state and are reclaimed at completion or shutdown. // outlive CUDA feature state and are reclaimed at completion or shutdown.
@@ -566,6 +591,9 @@ static NgxResult native_vk_init(unsigned long long id, const wchar_t* path, VkIn
if (!deviceProc || !memory) return NGX_FAIL_PLATFORM_ERROR; if (!deviceProc || !memory) return NGX_FAIL_PLATFORM_ERROR;
NativeVkApi api; NativeVkApi api;
api.device = device; api.device = device;
api.instance = instance;
api.physical = physical;
api.instanceProc = instanceProc;
memory(physical, &api.memory); memory(physical, &api.memory);
bool ok = true; bool ok = true;
auto load = [&](auto& fn, const char* name) { auto load = [&](auto& fn, const char* name) {
@@ -582,6 +610,7 @@ static NgxResult native_vk_init(unsigned long long id, const wchar_t* path, VkIn
load(api.cmdSetEvent, "vkCmdSetEvent"); load(api.waitEvents, "vkCmdWaitEvents"); load(api.cmdSetEvent, "vkCmdSetEvent"); load(api.waitEvents, "vkCmdWaitEvents");
load(api.barrier, "vkCmdPipelineBarrier"); load(api.idle, "vkDeviceWaitIdle"); load(api.barrier, "vkCmdPipelineBarrier"); load(api.idle, "vkDeviceWaitIdle");
load(api.fill, "vkCmdFillBuffer"); load(api.fill, "vkCmdFillBuffer");
load(api.createView, "vkCreateImageView"); load(api.destroyView, "vkDestroyImageView");
if (!ok) return NGX_FAIL_PLATFORM_ERROR; if (!ok) return NGX_FAIL_PLATFORM_ERROR;
// Match NGX's CUDA adapter identity to the supplied Vulkan physical device. // Match NGX's CUDA adapter identity to the supplied Vulkan physical device.
auto properties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(instanceProc(instance, "vkGetPhysicalDeviceProperties2")); auto properties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(instanceProc(instance, "vkGetPhysicalDeviceProperties2"));
@@ -701,9 +730,40 @@ D4R_EXPORT NgxResult NVSDK_NGX_VULKAN_GetFeatureInstanceExtensionRequirements(co
if (p) *p = nullptr; if (p) *p = nullptr;
return NGX_SUCCESS; return NGX_SUCCESS;
} }
D4R_EXPORT NgxResult NVSDK_NGX_VULKAN_GetFeatureDeviceExtensionRequirements(VkInstance, VkPhysicalDevice, // With the engine on, ask for the extensions its shaders use (VK_KHR_cooperative_matrix, VK_EXT_shader_float8), where
// the adapter has them: a game enables what NGX asks for. Their feature bits still need a layer (RADV compiles and runs
// the shaders without them; the harness's D4R_HARNESS_ENGINE_DEVICE enables them properly).
D4R_EXPORT NgxResult NVSDK_NGX_VULKAN_GetFeatureDeviceExtensionRequirements(VkInstance instance, VkPhysicalDevice physical,
const void* info, uint32_t* count, VkExtensionProperties** p) const void* info, uint32_t* count, VkExtensionProperties** p)
{ return NVSDK_NGX_VULKAN_GetFeatureInstanceExtensionRequirements(info, count, p); } {
if (!count) return NGX_FAIL_INVALID_PARAMETER;
static VkExtensionProperties wanted[2] = {};
static uint32_t wantedCount = 0;
static std::once_flag once;
// Games ask before NGX init: d4r.ini (which sets D4R_ENGINE) must be applied first, or engine_requested()
// caches "off" for the whole process.
ensure_portable_config();
if (engine_requested() && env_uint("D4R_VULKAN_ENGINE", 1) != 0 && instance && physical)
std::call_once(once, [&] {
HMODULE module = LoadLibraryA("vulkan-1.dll");
PFN_vkGetInstanceProcAddr instanceProc = nullptr;
if (!module || !load_export(module, "vkGetInstanceProcAddr", instanceProc)) return;
auto enumerate = reinterpret_cast<PFN_vkEnumerateDeviceExtensionProperties>(
instanceProc(instance, "vkEnumerateDeviceExtensionProperties"));
uint32_t n = 0;
if (!enumerate || enumerate(physical, nullptr, &n, nullptr) != VK_SUCCESS) return;
std::vector<VkExtensionProperties> available(n);
if (enumerate(physical, nullptr, &n, available.data()) != VK_SUCCESS) return;
for (const char* name : {"VK_KHR_cooperative_matrix", "VK_EXT_shader_float8"})
for (const auto& extension : available)
if (std::strcmp(extension.extensionName, name) == 0) wanted[wantedCount++] = extension;
logf("native Vulkan engine: device extension requirements: %u", wantedCount);
});
*count = wantedCount;
if (p) *p = wantedCount ? wanted : nullptr;
(void)info;
return NGX_SUCCESS;
}
D4R_EXPORT NgxResult NVSDK_NGX_VULKAN_GetFeatureRequirements(VkInstance, VkPhysicalDevice, const void*, NgxFeatureRequirement* p) D4R_EXPORT NgxResult NVSDK_NGX_VULKAN_GetFeatureRequirements(VkInstance, VkPhysicalDevice, const void*, NgxFeatureRequirement* p)
{ return p ? NVSDK_NGX_D3D12_GetFeatureRequirements(nullptr, nullptr, p) : NGX_FAIL_INVALID_PARAMETER; } { return p ? NVSDK_NGX_D3D12_GetFeatureRequirements(nullptr, nullptr, p) : NGX_FAIL_INVALID_PARAMETER; }
@@ -721,6 +781,355 @@ D4R_EXPORT NgxResult NVSDK_NGX_VULKAN_CreateFeature1(VkDevice device, VkCommandB
return NVSDK_NGX_VULKAN_CreateFeature(cmd, id, p, handle); return NVSDK_NGX_VULKAN_CreateFeature(cmd, id, p, handle);
} }
static void native_vk_read_params(const Feature* feature, void* p, FrameParams& params)
{
params.jitterX = get_float_or(p, "Jitter.Offset.X", 0); params.jitterY = get_float_or(p, "Jitter.Offset.Y", 0);
params.mvScaleX = get_float_or(p, "MV.Scale.X", 1); params.mvScaleY = get_float_or(p, "MV.Scale.Y", 1);
params.sharpness = get_float_or(p, "Sharpness", 0); params.reset = get_int_or(p, "Reset", 0);
params.preExposure = get_float_or(p, "DLSS.Pre.Exposure", 1); params.exposureScale = get_float_or(p, "DLSS.Exposure.Scale", 1);
params.frameTime = get_float_or(p, "FrameTimeDeltaInMsec", 16.6f);
params.renderWidth = get_uint_or(p, "DLSS.Render.Subrect.Dimensions.Width", feature->width);
params.renderHeight = get_uint_or(p, "DLSS.Render.Subrect.Dimensions.Height", feature->height);
if (!params.renderWidth) params.renderWidth = feature->width;
if (!params.renderHeight) params.renderHeight = feature->height;
params.colorBaseX = get_uint_or(p, "DLSS.Input.Color.Subrect.Base.X", 0);
params.colorBaseY = get_uint_or(p, "DLSS.Input.Color.Subrect.Base.Y", 0);
params.depthBaseX = get_uint_or(p, "DLSS.Input.Depth.Subrect.Base.X", 0);
params.depthBaseY = get_uint_or(p, "DLSS.Input.Depth.Subrect.Base.Y", 0);
params.mvBaseX = get_uint_or(p, "DLSS.Input.MV.Subrect.Base.X", 0);
params.mvBaseY = get_uint_or(p, "DLSS.Input.MV.Subrect.Base.Y", 0);
params.outputBaseX = get_uint_or(p, "DLSS.Output.Subrect.Base.X", 0);
params.outputBaseY = get_uint_or(p, "DLSS.Output.Subrect.Base.Y", 0);
params.invertX = get_int_or(p, "DLSS.Indicator.Invert.X.Axis", 0);
params.invertY = get_int_or(p, "DLSS.Indicator.Invert.Y.Axis", 0);
}
// --- Engine path: d4r's Vulkan network on the game's device, recorded into the game's command buffer ----------
// D4R_ENGINE (d4r.ini [Engine]) selects it; D4R_VULKAN_ENGINE=0 keeps the staging path. It needs the engine's
// device features on the game's device (cooperative matrix, and shader float8 for M/L): a game does not enable
// those itself, so a layer or the harness (D4R_HARNESS_ENGINE_DEVICE) must. Frames the engine does not cover
// return false and take the CUDA staging path.
static std::once_flag g_nativeVkEngineOnce;
static bool g_nativeVkEngineReady = false;
static void native_vk_engine_init()
{
if (!d4r::loadVulkan(g_nativeVk.instanceProc, g_nativeVk.instance, g_nativeVk.device))
{
logf("native Vulkan engine: Vulkan entry points are unavailable");
return;
}
g_vk.native = true;
g_vk.physical = g_nativeVk.physical;
g_vk.device = g_nativeVk.device;
auto enumerate = reinterpret_cast<PFN_vkEnumerateDeviceExtensionProperties>(
g_nativeVk.instanceProc(g_nativeVk.instance, "vkEnumerateDeviceExtensionProperties"));
uint32_t count = 0;
bool cooperative = false;
if (enumerate && enumerate(g_nativeVk.physical, nullptr, &count, nullptr) == VK_SUCCESS)
{
std::vector<VkExtensionProperties> extensions(count);
if (enumerate(g_nativeVk.physical, nullptr, &count, extensions.data()) == VK_SUCCESS)
for (const auto& extension : extensions)
{
g_vk.float8 |= std::strcmp(extension.extensionName, "VK_EXT_shader_float8") == 0;
cooperative |= std::strcmp(extension.extensionName, "VK_KHR_cooperative_matrix") == 0;
}
}
if (!cooperative)
{
logf("native Vulkan engine: the GPU has no VK_KHR_cooperative_matrix");
return;
}
g_nativeVkEngineReady = true;
logf("native Vulkan engine: ready on device %p (VK_EXT_shader_float8 %s); the device must have the engine's "
"features enabled", static_cast<void*>(g_nativeVk.device), g_vk.float8 ? "present" : "absent");
}
static bool native_vk_engine_idle(NativeVkEngineState& state)
{
for (auto it = state.pending.begin(); it != state.pending.end();)
if (g_nativeVk.eventStatus(g_nativeVk.device, *it) == VK_EVENT_SET &&
g_nativeVk.resetEvent(g_nativeVk.device, *it) == VK_SUCCESS)
{ state.idle.push_back(*it); it = state.pending.erase(it); }
else ++it;
return state.pending.empty();
}
static void native_vk_engine_destroy(NativeVkEngineState& state)
{
state.k.reset(); state.m.reset(); state.mDynamic.reset();
for (auto& view : state.views) g_nativeVk.destroyView(g_nativeVk.device, view.view, nullptr);
for (VkEvent event : state.pending) g_nativeVk.destroyEvent(g_nativeVk.device, event, nullptr);
for (VkEvent event : state.idle) g_nativeVk.destroyEvent(g_nativeVk.device, event, nullptr);
state.views.clear(); state.pending.clear(); state.idle.clear();
}
static void native_vk_engine_reap()
{
for (auto it = g_nativeVkEngineRetired.begin(); it != g_nativeVkEngineRetired.end();)
if (native_vk_engine_idle(**it)) { native_vk_engine_destroy(**it); it = g_nativeVkEngineRetired.erase(it); }
else ++it;
}
// A sampled view of `resource` the engine can read: the game's own, or a depth-only one for depth/stencil views.
static bool native_vk_engine_image(NativeVkEngineState& state, const NgxVkResource* resource, bool output,
d4r::GameImage& image)
{
if (!resource || resource->type != 0) return false;
const NgxVkImage& source = resource->resource.image;
if (!source.image || !source.view || source.range.levelCount > 1 || source.range.layerCount > 1) return false;
image.image = source.image;
image.format = source.format;
image.view = source.view;
image.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
static const bool generalInputs = env_uint("D4R_VULKAN_INPUT_GENERAL", 0) != 0;
image.layout = output || generalInputs ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
const bool depth = source.format == VK_FORMAT_D32_SFLOAT || source.format == VK_FORMAT_D32_SFLOAT_S8_UINT ||
source.format == VK_FORMAT_D16_UNORM || source.format == VK_FORMAT_D24_UNORM_S8_UINT ||
source.format == VK_FORMAT_X8_D24_UNORM_PACK32 || source.format == VK_FORMAT_D16_UNORM_S8_UINT;
if (!depth) return true;
image.aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
if (source.range.aspectMask == VK_IMAGE_ASPECT_DEPTH_BIT) return true;
auto found = std::find_if(state.views.begin(), state.views.end(), [&](const auto& view) {
return view.image == source.image && view.format == source.format;
});
if (found == state.views.end())
{
if (state.views.size() >= 16) return false;
VkImageViewCreateInfo info = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
info.image = source.image; info.viewType = VK_IMAGE_VIEW_TYPE_2D; info.format = source.format;
info.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, source.range.baseMipLevel, 1, source.range.baseArrayLayer, 1};
NativeVkEngineView view{source.image, source.format, VK_IMAGE_ASPECT_DEPTH_BIT};
if (g_nativeVk.createView(g_nativeVk.device, &info, nullptr, &view.view) != VK_SUCCESS) return false;
state.views.push_back(view);
found = state.views.end() - 1;
}
image.view = found->view;
return true;
}
// True: the engine took this evaluation (result says how it went). False: use the staging path.
static bool native_vk_engine(NativeVkFeature& owner, VkCommandBuffer cmd, void* p, NgxResult& result)
{
ensure_portable_config();
static const bool enabled = engine_requested() && env_uint("D4R_VULKAN_ENGINE", 1) != 0;
Feature& feature = *owner.feature;
if (!enabled || feature.engineRefused || feature.rayReconstruction) return false;
const uint32_t frame = feature.frame + 1;
const bool presetM = feature.preset == 13 || feature.preset == 12, presetL = feature.preset == 12;
const char* const name = feature.preset == 12 ? "L" : feature.preset == 13 ? "M" : "K";
auto refuse = [&](const char* reason) {
feature.engineRefused = true;
logf("native Vulkan engine (%s): %s; feature %u uses the staging path from frame %u", name, reason,
feature.handle.Id, frame);
return false;
};
std::call_once(g_nativeVkEngineOnce, native_vk_engine_init);
if (!g_nativeVkEngineReady) return refuse("unavailable");
if (!presetM && feature.preset != 11) return refuse("only presets K, L and M are implemented");
// NVSDK_NGX_DLSS_Feature_Flags: IsHDR 1, MVLowRes 2, MVJittered 4, DepthInverted 8, AutoExposure 0x40.
const bool ldr = (feature.flags & 0x1) == 0;
const bool displayMotion = (feature.flags & 0x2) == 0;
const bool autoExposure = (feature.flags & 0x40) != 0;
d4r::Model* model = nullptr;
d4r::MModel* modelM = nullptr;
if (presetM)
{
if (!g_vk.float8) return refuse("the GPU has no VK_EXT_shader_float8 for the FP8 layers");
std::call_once(g_engineM.once[presetL][ldr], [presetL, ldr] { init_engine_backend_m_variant(presetL, ldr); });
modelM = g_engineM.models[presetL][ldr].get();
if (!modelM) return refuse(ldr ? "no LDR model for a game without the HDR flag" : "no model");
if (displayMotion) return refuse("needs render-resolution motion vectors");
}
else
{
if (ldr) std::call_once(g_engine.ldrOnce, init_engine_backend_ldr);
else std::call_once(g_engine.once, init_engine_backend);
model = ldr ? g_engine.modelLdr.get() : g_engine.model.get();
if (!model) return refuse(ldr ? "no LDR model (engine\\k-ldr) for a game without the HDR flag" : "no model");
if (displayMotion && !model->hasDisplayMotion()) return refuse("display-resolution motion needs a newer model");
if ((feature.flags & 0x8) == 0 && !model->hasRegularDepth()) return refuse("regular depth needs a newer model");
if (!ldr && autoExposure && !model->hasAutoExposure()) return refuse("automatic exposure needs exposure shaders");
}
FrameParams params;
native_vk_read_params(&feature, p, params);
void* mask = nullptr; d4r_ngx_get_void(p, "DLSS.Input.Bias.Current.Color.Mask", &mask);
void* transparency = nullptr; d4r_ngx_get_void(p, "TransparencyMask", &transparency);
if (mask || transparency) return refuse("bias colour and transparency masks are not implemented");
if (!owner.engine) owner.engine = std::make_shared<NativeVkEngineState>();
NativeVkEngineState& state = *owner.engine;
const char* names[] = {"Color", "Depth", "MotionVectors", "ExposureTexture", "Output"};
const NgxVkResource* resources[5] = {};
for (int i = 0; i < 5; ++i)
{
void* resource = nullptr;
d4r_ngx_get_void(p, names[i], &resource);
resources[i] = static_cast<const NgxVkResource*>(resource);
}
d4r::GameImage color, depth, motion, exposure, output;
if (!native_vk_engine_image(state, resources[0], false, color) || !native_vk_engine_image(state, resources[1], false, depth) ||
!native_vk_engine_image(state, resources[2], false, motion) || !native_vk_engine_image(state, resources[4], true, output))
return refuse("a resource is not a single-mip, single-layer 2D image view");
const bool gameExposure = !presetM && !ldr && !autoExposure;
if (gameExposure && !native_vk_engine_image(state, resources[3], false, exposure))
return refuse("exposure without AutoExposure needs an exposure texture");
color.x = params.colorBaseX; color.y = params.colorBaseY;
depth.x = params.depthBaseX; depth.y = params.depthBaseY;
motion.x = params.mvBaseX; motion.y = params.mvBaseY;
output.x = params.outputBaseX; output.y = params.outputBaseY;
if (output.image == color.image || output.image == depth.image || output.image == motion.image)
return refuse("input/output texture aliasing is not supported");
if (!d4r::gameColorFormat(color.format) || !d4r::gameMotionFormat(motion.format) ||
!d4r::gameDepthFormat(depth.format, depth.aspect) || !d4r::gameOutputFormat(output.format))
{
logf("native Vulkan engine (%s): formats colour %d, depth %d, motion %d, output %d", name, color.format,
depth.format, motion.format, output.format);
return refuse("a texture format is not implemented");
}
// The Vulkan resource contract gives the output STORAGE usage only: the engine's store must write it directly.
const bool rgb10 = presetM ? modelM->hasRgb10Output() : model->hasRgb10Output();
const bool direct = output.view != VK_NULL_HANDLE && output.layout == VK_IMAGE_LAYOUT_GENERAL &&
(output.format == VK_FORMAT_R16G16B16A16_SFLOAT || (rgb10 && output.format == VK_FORMAT_A2B10G10R10_UNORM_PACK32));
if (!direct)
return refuse("the output must be an RGBA16F (or RGB10A2) storage image the engine writes directly");
if (resources[4]->resource.image.width != feature.outWidth || resources[4]->resource.image.height != feature.outHeight)
return refuse("the output does not match the feature's output size");
// An event per recording marks GPU completion; reclaim the ones that have fired.
native_vk_engine_idle(state);
VkEvent done = VK_NULL_HANDLE;
if (!state.idle.empty()) { done = state.idle.back(); state.idle.pop_back(); }
else
{
if (state.pending.size() >= 16) { result = NGX_FAIL_PLATFORM_ERROR; return true; }
VkEventCreateInfo info = {VK_STRUCTURE_TYPE_EVENT_CREATE_INFO};
if (g_nativeVk.createEvent(g_nativeVk.device, &info, nullptr, &done) != VK_SUCCESS)
{ result = NGX_FAIL_PLATFORM_ERROR; return true; }
}
auto releaseEvent = [&] { state.idle.push_back(done); };
VkMemoryBarrier before = {VK_STRUCTURE_TYPE_MEMORY_BARRIER};
before.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
before.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT;
VkMemoryBarrier after = {VK_STRUCTURE_TYPE_MEMORY_BARRIER};
after.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
after.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
try
{
if (presetM)
{
d4r::GameFrameM game;
game.color = color; game.depth = depth; game.motion = motion; game.output = output;
if (color.x || color.y || depth.x || depth.y || motion.x || motion.y)
return releaseEvent(), refuse("input rectangles must start at the origin");
game.jitter[0] = params.jitterX; game.jitter[1] = params.jitterY;
game.motionToRender[0] = params.mvScaleX; game.motionToRender[1] = params.mvScaleY;
if ((feature.flags & 0x4) != 0 && params.mvScaleX != 0.0f && params.mvScaleY != 0.0f)
{
game.motionOffset[0] = (params.jitterX - feature.engineJitter[0]) / params.mvScaleX;
game.motionOffset[1] = (params.jitterY - feature.engineJitter[1]) / params.mvScaleY;
}
game.renderWidth = params.renderWidth; game.renderHeight = params.renderHeight;
game.reset = params.reset != 0 || !state.m || params.renderWidth != feature.engineMLastWidth ||
params.renderHeight != feature.engineMLastHeight;
game.depthInverted = (feature.flags & 0x8) != 0;
if (!state.m)
{
const bool dynamic = modelM->hasDynamicResolution();
const uint32_t width = dynamic ? std::min(feature.outWidth, std::max(feature.width, params.renderWidth)) : params.renderWidth;
const uint32_t height = dynamic ? std::min(feature.outHeight, std::max(feature.height, params.renderHeight)) : params.renderHeight;
state.m = std::make_unique<d4r::GameUpscalerM>(*modelM, g_vk.physical, g_vk.device, feature.outWidth,
feature.outHeight, width, height);
feature.engineMRenderWidth = width; feature.engineMRenderHeight = height;
logf("native Vulkan engine (%s): feature %u runs preset %s in the game's command buffer (%ux%u -> %ux%u)",
name, feature.handle.Id, name, params.renderWidth, params.renderHeight, feature.outWidth, feature.outHeight);
}
d4r::GameUpscalerM* adapter = state.m.get();
if (params.renderWidth > feature.engineMRenderWidth || params.renderHeight > feature.engineMRenderHeight)
{
if (!state.mDynamic)
state.mDynamic = std::make_unique<d4r::GameUpscalerM>(*modelM, g_vk.physical, g_vk.device,
feature.outWidth, feature.outHeight, feature.outWidth, feature.outHeight);
adapter = state.mDynamic.get();
}
d4r::vkTable.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, 1, &before, 0, nullptr, 0, nullptr);
adapter->record(cmd, game);
feature.engineMLastWidth = params.renderWidth; feature.engineMLastHeight = params.renderHeight;
if (frame <= 3 || frame % 600 == 0)
logf("native Vulkan engine frame %u recorded, preset %s (jitter %.6f,%.6f, reset %d, mvScale %.3f,%.3f, render %ux%u)",
frame, name, params.jitterX, params.jitterY, game.reset, params.mvScaleX, params.mvScaleY,
params.renderWidth, params.renderHeight);
}
else
{
if (gameExposure && (exposure.format != VK_FORMAT_R32_SFLOAT && exposure.format != VK_FORMAT_R16_SFLOAT &&
exposure.format != VK_FORMAT_R32G32B32A32_SFLOAT && exposure.format != VK_FORMAT_R16G16B16A16_SFLOAT))
return releaseEvent(), refuse("the exposure texture must be a float texture");
if (!ldr && !(std::isfinite(params.exposureScale) && params.exposureScale > 0.0f))
return releaseEvent(), refuse("the exposure scale is not a positive number");
if (!ldr && !(std::isfinite(params.preExposure) && params.preExposure > 0.0f))
return releaseEvent(), refuse("the pre-exposure is not a positive number");
if ((color.x || color.y || depth.x || depth.y || motion.x || motion.y || output.x || output.y) &&
!model->hasDirectOrigins())
return releaseEvent(), refuse("texture rectangles need a model with direct origins");
d4r::GameFrame game;
game.color = color; game.depth = depth; game.motion = motion; game.output = output;
if (gameExposure) game.exposure = exposure;
auto& settings = game.settings;
settings.renderWidth = params.renderWidth; settings.renderHeight = params.renderHeight;
settings.jitter[0] = params.jitterX; settings.jitter[1] = params.jitterY;
settings.displayMotion = displayMotion;
const float toOutput = displayMotion ? 1.0f : float(feature.outWidth) / float(params.renderWidth);
const float toOutputY = displayMotion ? 1.0f : float(feature.outHeight) / float(params.renderHeight);
settings.motionScale[0] = params.mvScaleX * toOutput; settings.motionScale[1] = params.mvScaleY * toOutputY;
if ((feature.flags & 0x4) != 0 && params.mvScaleX != 0.0f && params.mvScaleY != 0.0f)
{
settings.motionOffset[0] = (params.jitterX - feature.engineJitter[0]) / params.mvScaleX;
settings.motionOffset[1] = (params.jitterY - feature.engineJitter[1]) / params.mvScaleY;
}
settings.reset = params.reset != 0 || !state.k;
settings.depthInverted = (feature.flags & 0x8) != 0;
settings.autoExposure = !ldr && autoExposure;
settings.exposureScale = ldr ? 1.0f : params.exposureScale;
settings.preExposure = ldr ? 1.0f : params.preExposure;
if (!state.k)
{
state.k = std::make_unique<d4r::GameUpscaler>(*model, g_vk.physical, g_vk.device, feature.outWidth,
feature.outHeight, feature.outWidth, feature.outHeight);
logf("native Vulkan engine (K): feature %u runs preset K in the game's command buffer (%ux%u -> %ux%u)",
feature.handle.Id, params.renderWidth, params.renderHeight, feature.outWidth, feature.outHeight);
}
d4r::vkTable.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, 1, &before, 0, nullptr, 0, nullptr);
state.k->record(cmd, game);
if (frame <= 3 || frame % 600 == 0)
logf("native Vulkan engine frame %u recorded, preset K (jitter %.6f,%.6f, reset %d, mvScale %.3f,%.3f, render %ux%u)",
frame, params.jitterX, params.jitterY, settings.reset, params.mvScaleX, params.mvScaleY,
params.renderWidth, params.renderHeight);
}
}
catch (const std::exception& error)
{
// Something may already be in the command buffer: the caller must not submit a half recording.
logf("native Vulkan engine (%s): %s", name, error.what());
releaseEvent();
feature.engineRefused = true;
result = NGX_FAIL_PLATFORM_ERROR;
return true;
}
d4r::vkTable.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, 1, &after, 0, nullptr, 0, nullptr);
g_nativeVk.cmdSetEvent(cmd, done, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
state.pending.push_back(done);
feature.engineJitter[0] = params.jitterX; feature.engineJitter[1] = params.jitterY;
feature.frame = frame;
result = NGX_SUCCESS;
return true;
}
D4R_EXPORT NgxResult NVSDK_NGX_VULKAN_EvaluateFeature(VkCommandBuffer cmd, const NgxHandle* handle, void* p, void*) D4R_EXPORT NgxResult NVSDK_NGX_VULKAN_EvaluateFeature(VkCommandBuffer cmd, const NgxHandle* handle, void* p, void*)
{ {
std::lock_guard<std::mutex> lock(g_nativeVkMutex); std::lock_guard<std::mutex> lock(g_nativeVkMutex);
@@ -731,6 +1140,9 @@ D4R_EXPORT NgxResult NVSDK_NGX_VULKAN_EvaluateFeature(VkCommandBuffer cmd, const
if (!cmd || !p || it == g_nativeVkFeatures.end()) return NGX_FAIL_INVALID_PARAMETER; if (!cmd || !p || it == g_nativeVkFeatures.end()) return NGX_FAIL_INVALID_PARAMETER;
auto& owner = *it; auto& owner = *it;
if (owner.feature->retiring) return NGX_FAIL_INVALID_PARAMETER; if (owner.feature->retiring) return NGX_FAIL_INVALID_PARAMETER;
native_vk_engine_reap();
if (NgxResult engineResult; owner.frames.empty() && native_vk_engine(owner, cmd, p, engineResult))
return engineResult;
for (auto* frame : owner.frames) for (auto* frame : owner.frames)
if (frame->done && frame->result != NGX_SUCCESS) return frame->result; if (frame->done && frame->result != NGX_SUCCESS) return frame->result;
native_vk_reap(g_nativeVkRetired); native_vk_reap(g_nativeVkRetired);
@@ -781,26 +1193,8 @@ D4R_EXPORT NgxResult NVSDK_NGX_VULKAN_EvaluateFeature(VkCommandBuffer cmd, const
if (frame->planes[4].image.width != feature->outWidth || frame->planes[4].image.height != feature->outHeight) if (frame->planes[4].image.width != feature->outWidth || frame->planes[4].image.height != feature->outHeight)
{ native_vk_free_frame(frame); return NGX_FAIL_INVALID_PARAMETER; } { native_vk_free_frame(frame); return NGX_FAIL_INVALID_PARAMETER; }
auto& params = frame->params; auto& params = frame->params;
params.jitterX = get_float_or(p, "Jitter.Offset.X", 0); params.jitterY = get_float_or(p, "Jitter.Offset.Y", 0); native_vk_read_params(feature, p, params);
params.mvScaleX = get_float_or(p, "MV.Scale.X", 1); params.mvScaleY = get_float_or(p, "MV.Scale.Y", 1);
params.sharpness = get_float_or(p, "Sharpness", 0); params.reset = get_int_or(p, "Reset", 0);
params.preExposure = get_float_or(p, "DLSS.Pre.Exposure", 1); params.exposureScale = get_float_or(p, "DLSS.Exposure.Scale", 1);
params.frameTime = get_float_or(p, "FrameTimeDeltaInMsec", 16.6f);
params.renderWidth = get_uint_or(p, "DLSS.Render.Subrect.Dimensions.Width", feature->width);
params.renderHeight = get_uint_or(p, "DLSS.Render.Subrect.Dimensions.Height", feature->height);
if (!params.renderWidth) params.renderWidth = feature->width;
if (!params.renderHeight) params.renderHeight = feature->height;
params.colorBaseX = get_uint_or(p, "DLSS.Input.Color.Subrect.Base.X", 0);
params.colorBaseY = get_uint_or(p, "DLSS.Input.Color.Subrect.Base.Y", 0);
params.depthBaseX = get_uint_or(p, "DLSS.Input.Depth.Subrect.Base.X", 0);
params.depthBaseY = get_uint_or(p, "DLSS.Input.Depth.Subrect.Base.Y", 0);
params.mvBaseX = get_uint_or(p, "DLSS.Input.MV.Subrect.Base.X", 0);
params.mvBaseY = get_uint_or(p, "DLSS.Input.MV.Subrect.Base.Y", 0);
params.outputBaseX = get_uint_or(p, "DLSS.Output.Subrect.Base.X", 0);
params.outputBaseY = get_uint_or(p, "DLSS.Output.Subrect.Base.Y", 0);
params.hasExposure = frame->planes[3].buffer != VK_NULL_HANDLE; params.hasExposure = frame->planes[3].buffer != VK_NULL_HANDLE;
params.invertX = get_int_or(p, "DLSS.Indicator.Invert.X.Axis", 0);
params.invertY = get_int_or(p, "DLSS.Indicator.Invert.Y.Axis", 0);
// Output subrect conversion and auxiliary masks are not implemented yet. // Output subrect conversion and auxiliary masks are not implemented yet.
void* mask = nullptr; void* mask = nullptr;
d4r_ngx_get_void(p, "DLSS.Input.Bias.Current.Color.Mask", &mask); d4r_ngx_get_void(p, "DLSS.Input.Bias.Current.Color.Mask", &mask);
@@ -876,6 +1270,12 @@ static NgxResult native_vk_release(std::vector<NativeVkFeature>::iterator it)
if (native_vk_consumed(*frame)) native_vk_free_frame(frame); if (native_vk_consumed(*frame)) native_vk_free_frame(frame);
else g_nativeVkRetired.push_back(frame); else g_nativeVkRetired.push_back(frame);
for (auto* frame : it->reusable) native_vk_free_frame(frame); for (auto* frame : it->reusable) native_vk_free_frame(frame);
if (it->engine)
{
if (native_vk_engine_idle(*it->engine)) native_vk_engine_destroy(*it->engine);
else g_nativeVkEngineRetired.push_back(it->engine);
it->engine.reset();
}
logf("native Vulkan feature %u released after %u completed frames", logf("native Vulkan feature %u released after %u completed frames",
it->feature->handle.Id, it->feature->completedFrames.load()); it->feature->handle.Id, it->feature->completedFrames.load());
it->feature->retiring = true; it->feature->retiring = true;
@@ -903,6 +1303,8 @@ D4R_EXPORT NgxResult NVSDK_NGX_VULKAN_Shutdown()
if (g_nativeVk.idle(g_nativeVk.device) != VK_SUCCESS) return NGX_FAIL_PLATFORM_ERROR; if (g_nativeVk.idle(g_nativeVk.device) != VK_SUCCESS) return NGX_FAIL_PLATFORM_ERROR;
for (auto* frame : g_nativeVkRetired) native_vk_free_frame(frame); for (auto* frame : g_nativeVkRetired) native_vk_free_frame(frame);
g_nativeVkRetired.clear(); g_nativeVkRetired.clear();
for (auto& state : g_nativeVkEngineRetired) native_vk_engine_destroy(*state);
g_nativeVkEngineRetired.clear();
for (auto& plane : g_nativeVkBufferPool) native_vk_free_plane(plane, true); for (auto& plane : g_nativeVkBufferPool) native_vk_free_plane(plane, true);
g_nativeVkBufferPool.clear(); g_nativeVkExternalAllocations = 0; g_nativeVkBufferPool.clear(); g_nativeVkExternalAllocations = 0;
g_nativeVkStaging.destroy(); g_nativeVkStaging.destroy();
+29 -3
View File
@@ -28,7 +28,7 @@ using Shutdown = Result (*)();
#define VK_FUNCTIONS(X) \ #define VK_FUNCTIONS(X) \
X(CreateInstance) X(EnumeratePhysicalDevices) X(GetPhysicalDeviceProperties) X(EnumerateDeviceExtensionProperties) \ X(CreateInstance) X(EnumeratePhysicalDevices) X(GetPhysicalDeviceProperties) X(EnumerateDeviceExtensionProperties) \
X(GetPhysicalDeviceQueueFamilyProperties) X(GetPhysicalDeviceMemoryProperties) X(CreateDevice) \ X(GetPhysicalDeviceQueueFamilyProperties) X(GetPhysicalDeviceMemoryProperties) X(GetPhysicalDeviceFeatures2) X(CreateDevice) \
X(GetDeviceProcAddr) X(GetDeviceQueue) X(CreateCommandPool) X(AllocateCommandBuffers) \ X(GetDeviceProcAddr) X(GetDeviceQueue) X(CreateCommandPool) X(AllocateCommandBuffers) \
X(BeginCommandBuffer) X(EndCommandBuffer) X(ResetCommandPool) X(QueueSubmit) X(QueueWaitIdle) X(CreateFence) X(WaitForFences) X(ResetFences) X(DestroyFence) \ X(BeginCommandBuffer) X(EndCommandBuffer) X(ResetCommandPool) X(QueueSubmit) X(QueueWaitIdle) X(CreateFence) X(WaitForFences) X(ResetFences) X(DestroyFence) \
X(CreateImage) X(GetImageMemoryRequirements) X(AllocateMemory) X(BindImageMemory) X(CreateImageView) \ X(CreateImage) X(GetImageMemoryRequirements) X(AllocateMemory) X(BindImageMemory) X(CreateImageView) \
@@ -102,7 +102,10 @@ int main(int argc, char** argv)
#define LOAD(name) vk##name = reinterpret_cast<PFN_vk##name>(ip(VK_NULL_HANDLE, "vk" #name)); #define LOAD(name) vk##name = reinterpret_cast<PFN_vk##name>(ip(VK_NULL_HANDLE, "vk" #name));
LOAD(CreateInstance) LOAD(CreateInstance)
#undef LOAD #undef LOAD
VkApplicationInfo app = {VK_STRUCTURE_TYPE_APPLICATION_INFO}; app.apiVersion = VK_API_VERSION_1_1; // D4R_HARNESS_ENGINE_DEVICE=1: a Vulkan 1.3 device with every supported 1.1-1.3 feature plus cooperative matrix
// and shader float8, as d4r's engine (and a layer in a game) needs.
const bool engineDevice = std::getenv("D4R_HARNESS_ENGINE_DEVICE") != nullptr;
VkApplicationInfo app = {VK_STRUCTURE_TYPE_APPLICATION_INFO}; app.apiVersion = engineDevice ? VK_API_VERSION_1_3 : VK_API_VERSION_1_1;
VkInstanceCreateInfo ci = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; ci.pApplicationInfo = &app; VkInstanceCreateInfo ci = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; ci.pApplicationInfo = &app;
VkInstance instance; VkInstance instance;
check(vkCreateInstance(&ci,nullptr,&instance),"instance"); check(vkCreateInstance(&ci,nullptr,&instance),"instance");
@@ -142,6 +145,28 @@ int main(int argc, char** argv)
di.enabledExtensionCount=1; di.ppEnabledExtensionNames=&externalExtension; di.enabledExtensionCount=1; di.ppEnabledExtensionNames=&externalExtension;
std::puts("enabling external-memory VRAM interop"); std::puts("enabling external-memory VRAM interop");
} }
VkPhysicalDeviceShaderFloat8FeaturesEXT f8 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT8_FEATURES_EXT};
VkPhysicalDeviceCooperativeMatrixFeaturesKHR cm = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_KHR, &f8};
VkPhysicalDeviceVulkan13Features v13 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, &cm};
VkPhysicalDeviceVulkan12Features v12 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, &v13};
VkPhysicalDeviceVulkan11Features v11 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES, &v12};
VkPhysicalDeviceFeatures2 f2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &v11};
std::vector<const char*> engineExtensions;
if (engineDevice) {
vkGetPhysicalDeviceFeatures2(physical,&f2);
check(vkEnumerateDeviceExtensionProperties(physical,nullptr,&count,nullptr),"extensions");
std::vector<VkExtensionProperties> extensions(count);
check(vkEnumerateDeviceExtensionProperties(physical,nullptr,&count,extensions.data()),"extensions");
bool float8 = false;
for (const auto& e : extensions) float8 |= std::strcmp(e.extensionName,"VK_EXT_shader_float8")==0;
if (!float8) { f8.shaderFloat8 = f8.shaderFloat8CooperativeMatrix = VK_FALSE; cm.pNext = nullptr; }
if (cm.cooperativeMatrix) engineExtensions.push_back("VK_KHR_cooperative_matrix"); else v13.pNext = nullptr;
if (float8) engineExtensions.push_back("VK_EXT_shader_float8");
for (uint32_t i = 0; i < di.enabledExtensionCount; ++i) engineExtensions.push_back(di.ppEnabledExtensionNames[i]);
di.pNext = &f2; di.enabledExtensionCount = static_cast<uint32_t>(engineExtensions.size());
di.ppEnabledExtensionNames = engineExtensions.data();
std::printf("engine device: cooperative matrix %u, float8 %u\n", cm.cooperativeMatrix, f8.shaderFloat8);
}
check(vkCreateDevice(physical,&di,nullptr,&device),"device"); check(vkCreateDevice(physical,&di,nullptr,&device),"device");
auto dp = vkGetDeviceProcAddr; auto dp = vkGetDeviceProcAddr;
#define LOAD(name) vk##name = reinterpret_cast<PFN_vk##name>(dp(device, "vk" #name)); #define LOAD(name) vk##name = reinterpret_cast<PFN_vk##name>(dp(device, "vk" #name));
@@ -247,7 +272,8 @@ int main(int argc, char** argv)
unsigned nonzero=0, nonfinite=0; unsigned nonzero=0, nonfinite=0;
for (unsigned j=0;j<OW*OH*4;++j) { nonzero += (values[j]&0x7fff)!=0; nonfinite += (values[j]&0x7c00)==0x7c00; } for (unsigned j=0;j<OW*OH*4;++j) { nonzero += (values[j]&0x7fff)!=0; nonfinite += (values[j]&0x7c00)==0x7c00; }
std::printf("frame %u: nonzero=%u nonfinite=%u center=%04x,%04x,%04x,%04x\n",n,nonzero,nonfinite,values[((OH/2)*OW+OW/2)*4],values[((OH/2)*OW+OW/2)*4+1],values[((OH/2)*OW+OW/2)*4+2],values[((OH/2)*OW+OW/2)*4+3]); std::printf("frame %u: nonzero=%u nonfinite=%u center=%04x,%04x,%04x,%04x\n",n,nonzero,nonfinite,values[((OH/2)*OW+OW/2)*4],values[((OH/2)*OW+OW/2)*4+1],values[((OH/2)*OW+OW/2)*4+2],values[((OH/2)*OW+OW/2)*4+3]);
if (!nonzero || nonfinite) return 1; // D4R_HARNESS_ALLOW_BLACK=1: a black frame is reported but not fatal (NGX's K starts black on the reset frame here).
if (nonfinite || (!nonzero && !std::getenv("D4R_HARNESS_ALLOW_BLACK"))) return 1;
if (n+1==frames) { FILE* out=std::fopen(argv[2],"wb"); if (!out) return 1; const auto written=std::fwrite(data,1,OW*OH*8,out); std::fclose(out); if (written!=OW*OH*8) return 1; } if (n+1==frames) { FILE* out=std::fopen(argv[2],"wb"); if (!out) return 1; const auto written=std::fwrite(data,1,OW*OH*8,out); std::fclose(out); if (written!=OW*OH*8) return 1; }
vkUnmapMemory(device,readMemory); vkUnmapMemory(device,readMemory);
} }