Merge pull request #36 from itchyfeetleech/vulkan-engine
vulkan: run the engine on the game's device, without staging. Thank you for the PR!
This commit is contained in:
6 files changed
+508
-29
No files matched your search
@@ -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`.
|
||||||
@@ -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
@@ -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) {
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user