Add experimental preset L and fix DLSS resource lifetimes and waits

- Build L texture variants and complete older release kernel bundles.
- Add selective Swin encoder fallback to reduce L/M motion trails.
- Add opt-in GPU motion-vector dilation for thin geometry and sky gaps.
- Replace GPU input waits with cancellable readiness polling and use
  sleeping event queries for completion.
- Retain recorded resources through command allocator completion, defer
  shutdown until cleanup, and preserve unfinished output synchronization.
- Extend lifecycle harness checks and add event, model and motion tests.
- Update configuration defaults, build scripts and release documentation.
This commit is contained in:
ayo committed 2026-10-02 00:30:46 -04:00
1 parent b645ef9096
commit f66cccaf28
32 files changed
+1362 -249

No files matched your search

+110
View File
@@ -20,6 +20,9 @@
#include <cstring>
#include <string>
#include <vector>
#include <atomic>
#include <chrono>
#include "d4r_vkd3d_interop.h"
using NgxResult = unsigned int;
constexpr NgxResult NGX_SUCCESS = 1;
@@ -103,6 +106,90 @@ static void submit_and_wait()
g_list->Reset(g_allocator, nullptr);
}
class LifetimeProbe final : public IUnknown
{
public:
explicit LifetimeProbe(std::atomic<bool>& destroyed) : destroyed_(destroyed) {}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** out) override
{
if (!out) return E_POINTER;
*out = nullptr;
if (iid != __uuidof(IUnknown)) return E_NOINTERFACE;
*out = this; AddRef(); return S_OK;
}
ULONG STDMETHODCALLTYPE AddRef() override { return ++refs_; }
ULONG STDMETHODCALLTYPE Release() override
{
ULONG refs = --refs_;
if (!refs) { destroyed_ = true; delete this; }
return refs;
}
private:
std::atomic<ULONG> refs_{1};
std::atomic<bool>& destroyed_;
};
static void lifecycle_report(const char* message)
{
std::printf("%s\n", message);
std::fflush(stdout);
if (const char* path = std::getenv("D4R_HARNESS_TEST_REPORT"))
if (FILE* file = std::fopen(path, "a"))
{
std::fprintf(file, "%s\n", message);
std::fclose(file);
}
}
static bool test_external_lifetime()
{
ID3D12DXVKInteropDeviceD4R2* interop = nullptr;
if (!check(g_device->QueryInterface(__uuidof(ID3D12DXVKInteropDeviceD4R2),
reinterpret_cast<void**>(&interop)), "lifetime interface"))
return false;
for (bool submitted : {false, true})
{
std::atomic<bool> destroyed{false};
auto* owner = new LifetimeProbe(destroyed);
const HRESULT retained = interop->RetainExternalResources(g_list, owner);
const HRESULT repeated = interop->RetainExternalResources(g_list, owner);
owner->Release();
if (!check(retained, "retain resources") || !check(repeated, "deduplicate resources") || destroyed)
return false;
g_list->Close();
ID3D12Fence* gate = nullptr;
if (submitted)
{
if (!check(g_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence),
reinterpret_cast<void**>(&gate)), "lifetime gate")) return false;
g_queue->Wait(gate, 1);
ID3D12CommandList* lists[] = {g_list};
g_queue->ExecuteCommandLists(1, lists);
Sleep(20);
g_allocator->Reset(); // Pending submissions must keep their external owners.
if (destroyed) { gate->Signal(1); return false; }
gate->Signal(1);
g_queue->Signal(g_fence, ++g_fenceValue);
if (g_fence->GetCompletedValue() < g_fenceValue)
{
g_fence->SetEventOnCompletion(g_fenceValue, g_event);
if (WaitForSingleObject(g_event, 5000) != WAIT_OBJECT_0) return false;
}
gate->Release();
}
// The fence callback may take a moment to drop the submission's allocator ref.
const ULONGLONG start = GetTickCount64();
do { g_allocator->Reset(); if (!destroyed) Sleep(1); }
while (!destroyed && GetTickCount64() - start < 5000);
if (!destroyed) return false;
if (!check(g_list->Reset(g_allocator, nullptr), "lifetime list reset")) return false;
lifecycle_report(submitted ? "LIFETIME submitted: retained until safe allocator reset"
: "LIFETIME discarded: retained until safe allocator reset");
}
interop->Release();
return true;
}
static ID3D12Resource* create_buffer(D3D12_HEAP_TYPE type, UINT64 size, D3D12_RESOURCE_STATES state)
{
D3D12_HEAP_PROPERTIES heap = {};
@@ -606,6 +693,8 @@ int main(int argc, char** argv)
d4r_ngx_set_int(parameters, "DLSS.Feature.Create.Flags",
createFlags != nullptr ? static_cast<int>(std::strtol(createFlags, nullptr, 0)) : 0);
NgxHandle* feature = nullptr;
if (std::getenv("D4R_HARNESS_LIFETIME_TEST") != nullptr && !test_external_lifetime())
return 1;
result = createFeature(g_list, 1, parameters, &feature);
std::printf("CreateFeature -> 0x%08x handle=%p\n", result, static_cast<void*>(feature));
if (result != NGX_SUCCESS)
@@ -785,6 +874,8 @@ int main(int argc, char** argv)
d4r_ngx_set_int(parameters, "Reset", forceReset || frame == 1 ? 1 : 0);
const DWORD start = GetTickCount();
result = evaluate(g_list, feature, parameters, nullptr);
if (const char* delay = std::getenv("D4R_HARNESS_DELAY_SUBMIT_MS"))
Sleep(static_cast<DWORD>(std::clamp(std::atoi(delay), 0, 4000)));
submit_and_wait();
std::printf("frame %d: EvaluateFeature -> 0x%08x (%lu ms incl. submit)\n", frame, result, GetTickCount() - start);
Sleep(frameWaitMs); // let the CUDA worker finish before the next presentation
@@ -885,6 +976,25 @@ int main(int argc, char** argv)
nonzero += value != 0;
std::printf("output read back: %zu of %zu bytes nonzero, written to %s\n", nonzero, output.size(), argv[2]);
if (std::getenv("D4R_HARNESS_DISCARD_EVALUATION") != nullptr)
{
const NgxResult queued = evaluate(g_list, feature, parameters, nullptr);
const ULONGLONG start = GetTickCount64();
const NgxResult retired = release(feature);
feature = nullptr;
if (queued != NGX_SUCCESS || retired != NGX_SUCCESS || GetTickCount64() - start > 2000)
{
std::fprintf(stderr, "DISCARD evaluation release failed or blocked\n");
return 1;
}
g_list->Close();
g_allocator->Reset();
g_list->Reset(g_allocator, nullptr);
result = createFeature(g_list, 1, parameters, &feature);
if (result != NGX_SUCCESS) return 1;
lifecycle_report("DISCARD evaluation: release completed without submitting inputs");
}
// D4R_HARNESS_RECREATE=N: N more release/create cycles at alternating render sizes (as when a game's DLSS
// quality setting changes), a few evaluations each, logging this process's VRAM to find leaks per cycle.
const int recreateCycles = std::getenv("D4R_HARNESS_RECREATE") != nullptr ? std::atoi(std::getenv("D4R_HARNESS_RECREATE")) : 0;
+16
View File
@@ -0,0 +1,16 @@
#pragma once
// Query completion rather than trusting HIP event flags to put the CPU to
// sleep. Never treat NOT_READY as completion, including during retirement.
template <typename Query, typename Sleep>
int d4r_wait_event(Query query, Sleep sleep)
{
constexpr int cudaNotReady = 600;
for (;;)
{
const int result = query();
if (result != cudaNotReady)
return result;
sleep();
}
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <stdint.h>
/* Shared Wine/HIP ABI. Pitches are bytes; motion pixels are packed RG16F. */
typedef struct D4rMotionDilationParams
{
uint64_t depth, motion, output;
uint64_t depth_pitch, motion_pitch;
uint32_t render_width, render_height, motion_width, motion_height;
float jitter_x, jitter_y;
uint32_t radius, reversed_depth;
} D4rMotionDilationParams;
/* Low-resolution velocity and depth share the render raster grid. Allocation
padding is not part of that grid; retain the original NGX flags and MV scale.
Display-resolution velocity still needs the jittered depth-grid mapping. */
static inline int d4r_motion_dilation_geometry(D4rMotionDilationParams* p, uint32_t flags,
uint32_t output_width, uint32_t output_height)
{
if ((flags & 4u) != 0 || p->render_width == 0 || p->render_height == 0)
return 0; /* Jittered motion values remain unsupported. */
if ((flags & 2u) != 0)
{
if (p->motion_width < p->render_width || p->motion_height < p->render_height)
return 0;
p->motion_width = p->render_width;
p->motion_height = p->render_height;
p->jitter_x = p->jitter_y = 0;
return 1;
}
return p->motion_width == output_width && p->motion_height == output_height;
}
#ifdef __cplusplus
static_assert(sizeof(D4rMotionDilationParams) == 72, "motion dilation ABI");
#else
_Static_assert(sizeof(D4rMotionDilationParams) == 72, "motion dilation ABI");
#endif
+12
View File
@@ -5,6 +5,18 @@
#include <stdlib.h>
#include <string.h>
static int d4r_native_swin_encoders_enabled(void)
{
const char* value = getenv("D4R_NATIVE_SWIN_ENCODERS");
return value == NULL || strcmp(value, "0") != 0;
}
static int d4r_native_kernel_allowed(const char* name)
{
return d4r_native_swin_encoders_enabled() ||
(strcmp(name, "rrlite_enc1_4x4") != 0 && strcmp(name, "rrlite_enc2_4x4") != 0);
}
static int d4r_apply_accuracy_policy(void)
{
const char* prefer = getenv("D4R_PREFER_ACCURACY");
+332 -183
View File
@@ -38,6 +38,7 @@
#include <windows.h>
#include <d3d12.h>
#include <vulkan/vulkan_core.h>
#include "d4r_motion_dilation.h"
#include <algorithm>
#include <bit>
@@ -57,6 +58,9 @@
#include <string>
#include <thread>
#include <vector>
#include "d4r_event_wait.h"
#include "d4r_win32_wait.h"
#include "d4r_vkd3d_interop.h"
// --- NGX types (declared locally: the core's exports differ from the SDK) --
@@ -221,6 +225,7 @@ struct CudaApi
int(WINAPI* ctxSynchronize)() = nullptr;
int(WINAPI* eventCreate)(CudaEvent*, unsigned int) = nullptr;
int(WINAPI* eventRecord)(CudaEvent, void*) = nullptr;
int(WINAPI* eventQuery)(CudaEvent) = nullptr;
int(WINAPI* eventSynchronize)(CudaEvent) = nullptr;
int(WINAPI* eventElapsedTime)(float*, CudaEvent, CudaEvent) = nullptr;
int(WINAPI* eventDestroy)(CudaEvent) = nullptr;
@@ -241,6 +246,7 @@ struct CudaApi
int(WINAPI* outputKernelNative)() = nullptr;
// Optional (d4r nvcuda bridge): report a pitch-linear texture to NGX as an array of that size/format.
int(WINAPI* registerLinearTexture)(CudaObject, size_t, size_t, uint32_t, uint32_t) = nullptr;
int(WINAPI* dilateMotion)(const D4rMotionDilationParams*) = nullptr;
};
// --- official NGX core CUDA API --------------------------------------------
@@ -461,7 +467,7 @@ static std::string preset_number(const std::string& model)
for (const auto& [letter, number] : letters)
if (name == letter)
return std::to_string(number);
g_portable.notes.push_back("d4r.ini: [DLSS] Model '" + model + "' is not a preset (use K, E, M or a number); "
g_portable.notes.push_back("d4r.ini: [DLSS] Model '" + model + "' is not a preset (use K, E, L, M or a number); "
"leaving the preset to the game");
return std::string();
}
@@ -531,6 +537,7 @@ static void load_portable_config()
const std::string native = ascii_lower(ini_value(ini, "kernels", "NativeKernels"));
// The bridge selects accuracy variants and enforces their compiler/sync policy before cuInit.
portable_set_unix("D4R_PREFER_ACCURACY", ini_flag(ini, "kernels", "PreferAccuracy", 0) ? "1" : "0");
portable_set_unix("D4R_NATIVE_SWIN_ENCODERS", ini_flag(ini, "kernels", "NativeSwinEncoders", 1) ? "1" : "0");
const bool nativeOn = native.empty() || native == "on" || native == "true" || native == "1" || native == "fast";
if (!nativeOn && native != "off" && native != "false" && native != "0")
g_portable.notes.push_back("d4r.ini: [Kernels] NativeKernels must be on or off, not '" + native + "'");
@@ -550,7 +557,7 @@ static void load_portable_config()
const char* variable;
int fallback;
} interop[] = {{"VramInterop", "D4R_SHIM_VRAM_INTEROP", 1}, {"InputSync", "D4R_SHIM_INPUT_SYNC", 1},
{"GpuWait", "D4R_SHIM_GPU_WAIT", 1}, {"LinearInputs", "D4R_SHIM_LINEAR_INPUTS", 1},
{"LinearInputs", "D4R_SHIM_LINEAR_INPUTS", 1},
{"EvalSync", "D4R_SHIM_EVAL_SYNC", 0}};
for (const auto& setting : interop)
portable_set(setting.variable, ini_flag(ini, "interop", setting.key, setting.fallback) ? "1" : "0");
@@ -562,6 +569,11 @@ static void load_portable_config()
portable_set("D4R_SHIM_OUTPUT_DIRECT", ini_flag(ini, "interop", "DirectOutput", nativeOn) ? "1" : "0");
portable_set("D4R_SHIM_WATERMARK", ini_flag(ini, "dlss", "ShowWatermark", 0) ? "1" : "0");
const std::string dilation = ini_value(ini, "interop", "MotionVectorDilation");
if (dilation.empty() || dilation == "0" || dilation == "1" || dilation == "2")
portable_set("D4R_MOTION_DILATION", dilation.empty() ? "0" : dilation);
else
g_portable.notes.push_back("MotionVectorDilation must be 0, 1 or 2; using original motion vectors");
if (ini_flag(ini, "debug", "Profile", 0))
portable_set("D4R_PROFILE", "1");
if (const std::string level = ini_value(ini, "debug", "NgxLogLevel"); !level.empty())
@@ -1121,6 +1133,9 @@ struct Global
Worker worker; // every NGX-core and CUDA call
std::thread::id workerThread;
Worker prep, finish; // CPU-only pipeline stages around it
Worker cleanup; // deferred GPU resource destruction, never under allocator locks
std::atomic<unsigned int> resourceOwners{0};
bool shutdownPending = false; // under mutex
HMODULE core = nullptr;
CoreApi ngx;
HMODULE cuda = nullptr;
@@ -1135,6 +1150,7 @@ struct Global
};
static Global g;
static std::mutex g_featureApiMutex;
template <typename Function> static bool load_export(HMODULE module, const char* name, Function& function)
{
@@ -1305,17 +1321,20 @@ static bool load_libraries()
g.cu.outputKernelNative = nullptr;
if (!load_export(g.cuda, "d4rRegisterLinearTexture", g.cu.registerLinearTexture))
g.cu.registerLinearTexture = nullptr;
if (profile_enabled() || env_uint("D4R_SHIM_BLOCKING_SYNC", 0) != 0)
g.cu.dilateMotion = reinterpret_cast<decltype(g.cu.dilateMotion)>(
reinterpret_cast<void*>(GetProcAddress(g.cuda, "d4rDilateMotionVectors")));
if (profile_enabled() || env_uint("D4R_SHIM_BLOCKING_SYNC", 1) != 0)
{
const bool create = load_export(g.cuda, "cuEventCreate", g.cu.eventCreate);
const bool record = load_export(g.cuda, "cuEventRecord", g.cu.eventRecord);
const bool wait = env_uint("D4R_SHIM_BLOCKING_SYNC", 0) != 0
const bool wait = env_uint("D4R_SHIM_BLOCKING_SYNC", 1) != 0
? load_export(g.cuda, "d4rEventSynchronize", g.cu.eventSynchronize) : true;
const bool query = load_export(g.cuda, "cuEventQuery", g.cu.eventQuery);
const bool destroy = load_export(g.cuda, "cuEventDestroy", g.cu.eventDestroy);
const bool elapsed = profile_enabled() ? load_export(g.cuda, "cuEventElapsedTime", g.cu.eventElapsedTime) : true;
if (profile_enabled() && !(create && record && destroy && elapsed))
logf("D4R_PROFILE: CUDA event timing unavailable; CPU stage timings remain enabled");
if (env_uint("D4R_SHIM_BLOCKING_SYNC", 0) != 0 && !(create && record && wait && destroy))
if (env_uint("D4R_SHIM_BLOCKING_SYNC", 1) != 0 && !(create && record && wait && query && destroy))
logf("blocking output wait unavailable; using context sync");
}
logf("loaded NGX core %ls and nvcuda.dll: %s", corePath, ok ? "all exports present" : "exports missing");
@@ -1336,14 +1355,22 @@ static NgxResult initialize(unsigned long long applicationId, const wchar_t* dat
const ProjectIdentity* project = nullptr)
{
std::lock_guard<std::mutex> lock(g.mutex);
g.shutdownPending = false;
if (g.ngxInitialized)
return NGX_SUCCESS;
if (!g.started)
{
// Allocators can retain COM callbacks and detached workers past NGX
// shutdown. Keep their code loaded until process exit.
HMODULE pinned = nullptr;
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN,
reinterpret_cast<LPCWSTR>(g_selfModule), &pinned))
return NGX_FAIL_PLATFORM_ERROR;
g.worker.start();
g.workerThread = g.worker.call([] { return std::this_thread::get_id(); });
g.prep.start();
g.finish.start();
g.cleanup.start();
g.started = true;
}
ensure_portable_config();
@@ -1463,10 +1490,24 @@ struct Staging
UINT width = 0;
UINT height = 0;
std::vector<ID3D12Resource*> retired;
void retire()
{
if (buffer != nullptr)
retired.push_back(buffer);
buffer = nullptr;
mapped = nullptr;
width = height = 0;
format = DXGI_FORMAT_UNKNOWN;
}
void release()
{
if (buffer != nullptr)
buffer->Release();
for (ID3D12Resource* old : retired)
old->Release();
*this = Staging{};
}
};
@@ -1490,6 +1531,7 @@ struct FrameParams
bool hasExposure = false;
bool vram = false; // inputs and output stay in VRAM (VRAM interop)
bool split = false; // this frame presents its own result (split frames)
unsigned motionDilation = 0; // actually applied, worker only
};
struct FrameTiming
@@ -1501,6 +1543,7 @@ struct FrameTiming
double download = 0, outputConvert = 0;
double prep = 0, worker = 0, workerWait = 0, finishWait = 0, markerWait = 0;
double ngxHost = 0, ctxSync = 0, gpuEval = -1;
double motionDilationWall = 0;
bool gpuEventsRecorded = false;
double outputSyncWall = 0, outputSyncCpu = -1;
bool outputSyncBlocking = false;
@@ -1533,6 +1576,11 @@ struct InputSlot
CudaDevicePtr linearPointer[4] = {};
UINT linearWidth[4] = {}, linearHeight[4] = {};
size_t linearPitch[4] = {};
CudaDevicePtr dilatedMotion = 0;
size_t dilatedBytes = 0;
CudaObject dilatedTexture = 0;
UINT dilatedWidth = 0, dilatedHeight = 0;
size_t dilatedPitch = 0;
std::atomic<bool> busy{false}; // readback staging owned by the pipeline
HostPlane host[4];
std::atomic<bool> hostBusy{false}; // host planes not yet uploaded by the worker
@@ -1549,6 +1597,8 @@ struct OutputSlot
struct Feature
{
NgxHandle handle = {}; // returned to the caller
std::atomic<bool> retiring{false};
IUnknown* resources = nullptr; // main reference plus command-allocator references
NgxHandle* cudaHandle = nullptr;
void* cudaParams = nullptr;
CudaDevicePtr scratch = 0;
@@ -1586,26 +1636,20 @@ struct Feature
VramImage outputConversion;
// Split frames: the rest of frame N's command list is submitted only once
// splitSemaphore reaches N (signalled when N retires, or by the watchdog).
bool split = false;
std::atomic<bool> split{false};
VkSemaphore splitSemaphore = VK_NULL_HANDLE;
std::mutex splitMutex;
uint64_t splitSignalled = 0; // under splitMutex
// GPU-side start (D4R_SHIM_GPU_WAIT, split frames): the input copies end with a vkCmdFillBuffer of the
// frame number into gpuMarker, and the null stream waits for it on the GPU instead of the CPU polling
// the frame marker before any DLSS work is queued.
bool gpuWait = false;
VramBuffer gpuMarker;
// Frame the null stream currently waits for (0: none) and since when; the watchdog releases a wait for a
// frame whose command list never reaches the GPU (e.g. recorded but not executed), so DLSS cannot stall.
std::atomic<uint32_t> gpuWaitFrame{0};
std::atomic<int64_t> gpuWaitSince{0};
// Inputs become ready on the CPU before CUDA work is queued. GPU-side waits
// for recorded-but-discarded command lists can otherwise wedge the device.
// D4R_SHIM_OUTPUT_DIRECT: this frame's result is stored by the native output kernel straight into its
// destination buffer (worker only), so the array -> buffer copy is skipped.
bool outputRedirected = false;
bool outputDirectAllowed = false; // the forced preset's output kernel honours the redirect
// D4R_SHIM_LINEAR_INPUTS (with gpuWait): NGX samples the interop buffers directly through pitch-linear
// D4R_SHIM_LINEAR_INPUTS: NGX samples the interop buffers directly through pitch-linear
// texture objects (rows padded to 256 bytes), so the buffer -> array copies disappear.
bool linearInputs = false;
bool dilationReported = false;
std::vector<VramBuffer> retiredBuffers; // may still be read by queued command lists
std::vector<VramImage> retiredImages;
ID3D12Resource* marker = nullptr;
@@ -1666,7 +1710,7 @@ static bool ensure_staging(Staging& staging, ID3D12Resource* resource, D3D12_HEA
if (staging.buffer != nullptr && staging.format == desc.Format && staging.width == desc.Width &&
staging.height == desc.Height)
return true;
staging.release();
staging.retire();
UINT64 rowSize = 0;
g.device->GetCopyableFootprints(&desc, 0, 1, 0, &staging.layout, &staging.rows, &rowSize, &staging.total);
if (!create_buffer(heapType, staging.total, &staging.buffer, &staging.mapped))
@@ -2002,59 +2046,12 @@ static uint32_t plane_channels(Plane plane)
// worker then only moves data between them and the CUDA arrays on the GPU.
// Frames whose formats do not qualify use the readback/upload path.
MIDL_INTERFACE("39da4e09-bd1c-4198-9fae-86bbe3be41fd")
ID3D12DXVKInteropDevice : public IUnknown
{
virtual HRESULT STDMETHODCALLTYPE GetDXGIAdapter(REFIID iid, void** object) = 0;
virtual HRESULT STDMETHODCALLTYPE GetInstanceExtensions(UINT* count, const char** extensions) = 0;
virtual HRESULT STDMETHODCALLTYPE GetDeviceExtensions(UINT* count, const char** extensions) = 0;
virtual HRESULT STDMETHODCALLTYPE GetDeviceFeatures(const VkPhysicalDeviceFeatures2** features) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVulkanHandles(VkInstance* instance, VkPhysicalDevice* physical,
VkDevice* device) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVulkanQueueInfo(ID3D12CommandQueue* queue, VkQueue* vkQueue,
UINT32* family) = 0;
virtual void STDMETHODCALLTYPE GetVulkanImageLayout(ID3D12Resource* resource, D3D12_RESOURCE_STATES state,
VkImageLayout* layout) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVulkanResourceInfo(ID3D12Resource* resource, UINT64* handle,
UINT64* offset) = 0;
virtual HRESULT STDMETHODCALLTYPE LockCommandQueue(ID3D12CommandQueue* queue) = 0;
virtual HRESULT STDMETHODCALLTYPE UnlockCommandQueue(ID3D12CommandQueue* queue) = 0;
};
MIDL_INTERFACE("902d8115-59eb-4406-9518-fe00f991ee65")
ID3D12DXVKInteropDevice1 : public ID3D12DXVKInteropDevice
{
virtual HRESULT STDMETHODCALLTYPE GetVulkanResourceInfo1(ID3D12Resource* resource, UINT64* handle, UINT64* offset,
VkFormat* format) = 0;
virtual HRESULT STDMETHODCALLTYPE CreateInteropCommandQueue(const D3D12_COMMAND_QUEUE_DESC* desc, UINT32 family,
ID3D12CommandQueue** queue) = 0;
virtual HRESULT STDMETHODCALLTYPE CreateInteropCommandAllocator(D3D12_COMMAND_LIST_TYPE type, UINT32 family,
ID3D12CommandAllocator** allocator) = 0;
virtual HRESULT STDMETHODCALLTYPE BeginVkCommandBufferInterop(ID3D12CommandList* list, VkCommandBuffer* buffer) = 0;
virtual HRESULT STDMETHODCALLTYPE EndVkCommandBufferInterop(ID3D12CommandList* list) = 0;
};
__CRT_UUID_DECL(ID3D12DXVKInteropDevice1, 0x902d8115, 0x59eb, 0x4406, 0x95, 0x18, 0xfe, 0x00, 0xf9, 0x91, 0xee, 0x65)
// Added by the d4r vkd3d-proton patch (patches/vkd3d-proton/): ends the list's
// current Vulkan command buffer; what is recorded afterwards is submitted
// separately, with a queue-level wait for the timeline semaphore value.
MIDL_INTERFACE("5a7c8b3e-2f61-4d0e-9c1a-7e3b52d4a901")
ID3D12DXVKInteropDeviceD4R : public ID3D12DXVKInteropDevice1
{
virtual HRESULT STDMETHODCALLTYPE LockVulkanQueue(ID3D12CommandQueue* queue) = 0;
virtual HRESULT STDMETHODCALLTYPE UnlockVulkanQueue(ID3D12CommandQueue* queue) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVulkanHeapInfo(ID3D12Heap* heap, UINT64* memory, UINT64* offset,
UINT32* type) = 0;
virtual HRESULT STDMETHODCALLTYPE SplitCommandListForExternalWait(ID3D12CommandList* list, UINT64 semaphore,
UINT64 value) = 0;
};
__CRT_UUID_DECL(ID3D12DXVKInteropDeviceD4R, 0x5a7c8b3e, 0x2f61, 0x4d0e, 0x9c, 0x1a, 0x7e, 0x3b, 0x52, 0xd4, 0xa9, 0x01)
struct VulkanInterop
{
std::once_flag once;
bool ready = false;
ID3D12DXVKInteropDevice1* interop = nullptr;
ID3D12DXVKInteropDeviceD4R2* lifetime = nullptr;
ID3D12DXVKInteropDeviceD4R* split = nullptr; // only with the d4r vkd3d-proton patch
VkDevice device = VK_NULL_HANDLE;
VkPhysicalDeviceMemoryProperties memory = {};
@@ -2159,6 +2156,8 @@ static void init_vram_interop()
load(g_vk.signalSemaphore, "vkSignalSemaphore");
if (FAILED(g.device->QueryInterface(__uuidof(ID3D12DXVKInteropDeviceD4R), reinterpret_cast<void**>(&g_vk.split))))
g_vk.split = nullptr;
if (FAILED(g.device->QueryInterface(__uuidof(ID3D12DXVKInteropDeviceD4R2), reinterpret_cast<void**>(&g_vk.lifetime))))
g_vk.lifetime = nullptr;
g_vk.ready = ok;
logf("VRAM interop: %s (VkDevice %p), split frames %s", ok ? "ready" : "missing Vulkan entry points",
static_cast<void*>(g_vk.device), g_vk.split != nullptr ? "available" : "unavailable (stock vkd3d-proton)");
@@ -2492,7 +2491,7 @@ static VkBufferImageCopy full_region(const VramCopy& copy)
// Records the input copies into the slot's buffers. The resources are in
// COPY_SOURCE state already.
static bool record_vram_inputs(Feature& feature, ID3D12GraphicsCommandList* list, InputSlot& slot,
const VramCopy* copies, int count, uint32_t frame)
const VramCopy* copies, int count)
{
VkCommandBuffer cmd = VK_NULL_HANDLE;
if (FAILED(g_vk.interop->BeginVkCommandBufferInterop(list, &cmd)))
@@ -2548,9 +2547,6 @@ static bool record_vram_inputs(Feature& feature, ID3D12GraphicsCommandList* list
}
g_vk.barrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &kAfterTransfer, 0,
nullptr, 0, nullptr);
// After the barrier: the copies are complete and their writes available when the value lands.
if (feature.gpuWait)
g_vk.fill(cmd, feature.gpuMarker.buffer, 0, 4, frame);
return SUCCEEDED(g_vk.interop->EndVkCommandBufferInterop(list));
}
@@ -2623,7 +2619,8 @@ static void signal_split(Feature* feature, uint64_t value, const char* reason)
info.semaphore = feature->splitSemaphore;
info.value = value;
const VkResult result = g_vk.signalSemaphore(g_vk.device, &info);
feature->splitSignalled = value;
if (result == VK_SUCCESS)
feature->splitSignalled = value;
if (result != VK_SUCCESS || reason != nullptr)
logf("split frame %llu released%s%s (VkResult %d)", static_cast<unsigned long long>(value),
reason != nullptr ? " by " : "", reason != nullptr ? reason : "", result);
@@ -2661,22 +2658,17 @@ static void split_watchdog()
std::lock_guard<std::mutex> splitLock(feature->splitMutex);
signalled = feature->splitSignalled;
}
// GPU-side input wait for a frame the game's queue has not reached for over a second
const uint32_t waiting = feature->gpuWaitFrame.load();
if (feature->gpuWait && waiting != 0 && static_cast<int32_t>(marker - waiting) < 0 &&
now.time_since_epoch().count() - feature->gpuWaitSince.load() >
std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::seconds(1)).count() &&
[&] { uint32_t expected = waiting; return feature->gpuWaitFrame.compare_exchange_strong(expected, 0u); }())
{
logf("frame %u: its inputs never reached the GPU (marker %u); releasing the GPU-side wait", waiting, marker);
g.cu.writeValue32(feature->gpuMarker.device, waiting);
}
if (marker <= signalled)
continue; // nothing on the GPU is waiting on us
if (it->second.marker != marker)
it->second = Seen{marker, now};
else if (now - it->second.at > timeout)
signal_split(feature, marker, "the watchdog");
{
// A slow producer is not a completed producer. Releasing this
// wait would race the game's output copy with a late CUDA write.
logf("split frame %u still waiting for DLSS; preserving output synchronization", marker);
it->second.at = now;
}
}
// Forget released features.
seen.erase(std::remove_if(seen.begin(), seen.end(),
@@ -2735,7 +2727,6 @@ static void release_vram(Feature& feature)
recycle_vram_buffer(buffer);
for (OutputSlot& slot : feature.outputs)
recycle_vram_buffer(slot.vram);
recycle_vram_buffer(feature.gpuMarker);
for (VramBuffer& buffer : feature.retiredBuffers)
recycle_vram_buffer(buffer);
feature.retiredBuffers.clear();
@@ -3306,7 +3297,7 @@ static bool wait_for_release(const std::atomic<bool>& flag)
{
if (std::chrono::steady_clock::now() - start > std::chrono::seconds(2))
return false;
std::this_thread::sleep_for(std::chrono::microseconds(100));
d4r_sleep_us(100);
}
return true;
}
@@ -3516,6 +3507,91 @@ static bool ensure_linear_inputs(InputSlot& slot, bool hasExposure)
return true;
}
// VRAM interop, worker thread: moves the planes from the imported buffers
// Opt-in thin-feature motion coverage. Keep the original imported velocity buffer
// immutable; the new texture is owned by this input slot until its GPU work retires.
static CudaObject prepare_dilated_motion(Feature& feature, InputSlot& slot, const FrameParams& p)
{
const unsigned radius = env_uint("D4R_MOTION_DILATION", 0);
if (radius == 0)
return 0;
const auto& depth = slot.host[1];
const auto& motion = slot.host[2];
D4rMotionDilationParams args = {slot.vram[1].device, slot.vram[2].device, slot.dilatedMotion,
depth.rowBytes, motion.rowBytes, p.renderWidth, p.renderHeight, motion.width, motion.height,
p.jitterX, p.jitterY, radius, (feature.flags & 8u) != 0};
const bool motionGeometry = d4r_motion_dilation_geometry(&args, feature.flags,
feature.outWidth, feature.outHeight);
const bool supported = radius <= 2 && p.vram && feature.linearInputs && g.cu.dilateMotion != nullptr &&
motionGeometry && p.colorBaseX == 0 && p.colorBaseY == 0 && p.depthBaseX == 0 &&
p.depthBaseY == 0 && p.mvBaseX == 0 && p.mvBaseY == 0 && p.outputBaseX == 0 && p.outputBaseY == 0 &&
p.renderWidth > 0 && p.renderHeight > 0 && std::isfinite(p.jitterX) && std::isfinite(p.jitterY) &&
p.renderWidth <= depth.width && p.renderHeight <= depth.height;
if (!supported)
{
if (!feature.dilationReported)
logf("motion-vector dilation unavailable for this feature; original motion vectors retained");
feature.dilationReported = true;
return 0;
}
const size_t bytes = motion.size();
const bool recreate = slot.dilatedBytes < bytes || slot.dilatedWidth != motion.width ||
slot.dilatedHeight != motion.height || slot.dilatedPitch != motion.rowBytes;
if (recreate)
{
if (slot.dilatedTexture != 0)
g.cu.texObjectDestroy(slot.dilatedTexture), slot.dilatedTexture = 0;
if (slot.dilatedMotion != 0)
g.cu.memFree(slot.dilatedMotion), slot.dilatedMotion = 0;
slot.dilatedBytes = 0;
if (g.cu.memAlloc(&slot.dilatedMotion, bytes) != 0)
return 0;
slot.dilatedBytes = bytes;
struct Pitch2D { CudaDevicePtr pointer; uint32_t format, channels; size_t width, height, pitch; };
const Pitch2D pitch = {slot.dilatedMotion, plane_format(Plane::Motion), 2, motion.width, motion.height, motion.rowBytes};
CudaResourceDesc resource = {};
resource.resType = 3;
static_assert(sizeof(pitch) == 40);
std::memcpy(&resource.res, &pitch, sizeof(pitch));
const CudaTextureDesc sampler = input_sampler(motion.width, 2, false);
int result = g.cu.texObjectCreate(&slot.dilatedTexture, &resource, &sampler, nullptr);
if (result == 0)
result = g.cu.registerLinearTexture(slot.dilatedTexture, motion.width, motion.height, pitch.format, 2);
if (result != 0)
{
if (slot.dilatedTexture != 0)
g.cu.texObjectDestroy(slot.dilatedTexture), slot.dilatedTexture = 0;
return 0;
}
slot.dilatedWidth = motion.width, slot.dilatedHeight = motion.height, slot.dilatedPitch = motion.rowBytes;
}
if (slot.dilatedTexture == 0)
return 0;
args.output = slot.dilatedMotion;
// Preserve allocation padding, which DLSS may sample at the active edge.
// The dilation kernel only writes the active render grid for low-res MVs.
if (args.motion_width != motion.width || args.motion_height != motion.height)
{
CudaMemcpy2D copy = {};
copy.srcMemoryType = copy.dstMemoryType = CUDA_MEMORY_DEVICE;
copy.srcDevice = args.motion;
copy.dstDevice = args.output;
copy.srcPitch = copy.dstPitch = motion.rowBytes;
copy.WidthInBytes = motion.rowBytes;
copy.Height = motion.height;
if (copy_2d(copy) != 0)
return 0;
}
const int result = g.cu.dilateMotion(&args);
if (!feature.dilationReported)
logf("motion-vector dilation %s: radius=%u render pixels, GPU status=%d, %s active=%ux%u allocation=%ux%u",
result == 0 ? "enabled" : "unavailable", radius, result,
(feature.flags & 2u) != 0 ? "low-resolution" : "display-resolution",
args.motion_width, args.motion_height, motion.width, motion.height);
feature.dilationReported = true;
return result == 0 ? slot.dilatedTexture : 0;
}
// VRAM interop, worker thread: moves the planes from the imported buffers
// into the CUDA arrays on the GPU. With D4R_SHIM_VRAM_VERIFY also compares
// them with the host-staged planes of the same frame.
@@ -3608,6 +3684,32 @@ static bool upload_inputs_vram(Feature& feature, InputSlot& slot, bool hasExposu
return result == 0;
}
// Only called on the CUDA worker. A completion event covers queued work on
// the default stream; do not rely on HIP's blocking flag to release the CPU.
static int synchronize_default_stream(Feature& feature, bool* querySleep = nullptr)
{
if (feature.outputReadyEvent != nullptr)
{
const int recorded = g.cu.eventRecord(feature.outputReadyEvent, nullptr);
if (recorded == 0)
{
const int result = d4r_wait_event(
[&] { return g.cu.eventQuery(feature.outputReadyEvent); },
[] { d4r_sleep_us(200); });
if (result == 0)
{
if (querySleep != nullptr) *querySleep = true;
// The event is already complete; this also flushes bridge kernel profiles.
return g.cu.eventSynchronize(feature.outputReadyEvent);
}
logf("output event query failed (%d); using context synchronization", result);
}
else
logf("output event record failed (%d); using context synchronization", recorded);
}
return g.cu.ctxSynchronize();
}
// VRAM interop, worker thread: copies the finished result into a free output
// slot's imported buffer and publishes it; evaluate() copies it on to the game.
static void publish_vram(Feature* feature, uint32_t frame, const FrameParams& params, FrameTiming& timing,
@@ -3638,23 +3740,7 @@ static void publish_vram(Feature* feature, uint32_t frame, const FrameParams& pa
{
const auto syncStart = timing.enabled ? ProfileClock::now() : ProfileClock::time_point{};
const double cpuStart = timing.enabled ? profile_thread_cpu_ms() : -1.0;
if (feature->outputReadyEvent != nullptr)
{
const int recordResult = g.cu.eventRecord(feature->outputReadyEvent, nullptr);
const int waitResult = recordResult == 0 ? g.cu.eventSynchronize(feature->outputReadyEvent) : recordResult;
if (waitResult == 0)
timing.outputSyncBlocking = true;
else
{
logf("frame %u: blocking output wait failed (record %d, wait %d); using context sync", frame,
recordResult, waitResult);
result = g.cu.ctxSynchronize();
g.cu.eventDestroy(feature->outputReadyEvent);
feature->outputReadyEvent = nullptr;
}
}
else
result = g.cu.ctxSynchronize();
result = synchronize_default_stream(*feature, &timing.outputSyncBlocking);
if (timing.enabled)
{
timing.outputSyncWall = profile_ms(syncStart, ProfileClock::now());
@@ -3746,22 +3832,9 @@ static void run_evaluation(Feature* feature, int slotIndex, uint32_t frame, Fram
timing.workerWait = profile_ms(timing.workerQueued, start);
FrameTiming* stages = timing.enabled ? &timing : nullptr;
const bool overlapped = async_copies() && copy_streams() != nullptr;
const bool gpuWait = params.vram && feature->gpuWait;
if (params.vram && !gpuWait && env_uint("D4R_SHIM_VRAM_VERIFY", 0) == 0)
if (params.vram && env_uint("D4R_SHIM_VRAM_VERIFY", 0) == 0)
dump_vram_inputs(slot, frame, params, params.hasExposure ? 4 : 3);
if (gpuWait)
{
feature->gpuWaitSince = std::chrono::steady_clock::now().time_since_epoch().count();
feature->gpuWaitFrame = frame;
}
if (gpuWait && g.cu.streamWaitValue32(feature->gpuMarker.device, frame) != 0)
{
logf("frame %u: queueing the GPU-side input wait failed", frame);
slot.busy = false;
frame_retired(feature, frame);
return;
}
const bool linearInputs = gpuWait && feature->linearInputs;
const bool linearInputs = params.vram && feature->linearInputs;
bool ok = linearInputs ? ensure_linear_inputs(slot, params.hasExposure)
: params.vram ? upload_inputs_vram(*feature, slot, params.hasExposure, frame, stages)
: overlapped
@@ -3773,22 +3846,30 @@ static void run_evaluation(Feature* feature, int slotIndex, uint32_t frame, Fram
upload_plane(Plane::Exposure, slot.host[3], feature->exposure, frame, stages, 3));
ok = ok && ensure_cuda_image(feature->output, feature->outputWidth, feature->outputHeight, CUDA_FORMAT_HALF, 4,
true, false);
const auto dilationStart = timing.enabled ? ProfileClock::now() : ProfileClock::time_point{};
const CudaObject dilatedMotion = ok ? prepare_dilated_motion(*feature, slot, params) : 0;
// Device-to-array copies (VRAM interop, staged uploads) return before they
// finish, and NGX does not evaluate on the stream they were issued on, so
// without this DLSS can sample a mix of this frame's and the previous
// frame's colour/depth/motion: invisible when the camera is still, beaded
// thin geometry in motion. D4R_SHIM_INPUT_SYNC=0 restores the old behaviour.
static const bool inputSync = env_uint("D4R_SHIM_INPUT_SYNC", 1) != 0;
// With gpuWait everything up to the output copy is queued in order on the null stream.
if (ok && inputSync && !gpuWait)
// Linear inputs are already ready; other copies need completion before NGX samples them.
if (ok && (dilatedMotion != 0 || (inputSync && !linearInputs)))
{
const int syncResult = g.cu.ctxSynchronize();
const int syncResult = synchronize_default_stream(*feature);
if (syncResult != 0)
{
logf("frame %u: input copy synchronize failed: %d", frame, syncResult);
ok = false;
}
}
if (dilatedMotion != 0 && ok)
{
params.motionDilation = env_uint("D4R_MOTION_DILATION", 0);
if (timing.enabled)
timing.motionDilationWall = profile_ms(dilationStart, ProfileClock::now());
}
slot.hostBusy = false; // the host planes are no longer needed
// The previous frame's download ran alongside these uploads.
complete_pending_download();
@@ -3809,7 +3890,7 @@ static void run_evaluation(Feature* feature, int slotIndex, uint32_t frame, Fram
// the parameters point at these variables
feature->colorHandle = slot.linearTexture[0];
feature->depthHandle = slot.linearTexture[1];
feature->motionHandle = slot.linearTexture[2];
feature->motionHandle = dilatedMotion != 0 ? dilatedMotion : slot.linearTexture[2];
feature->exposureHandle = params.hasExposure ? slot.linearTexture[3] : feature->exposureHandle;
}
static const bool outputDirect = env_uint("D4R_SHIM_OUTPUT_DIRECT", 0) != 0;
@@ -3855,24 +3936,18 @@ static void run_evaluation(Feature* feature, int slotIndex, uint32_t frame, Fram
// D4R_SHIM_EVAL_SYNC=0 (VRAM interop): no wait between NGX's kernels and the output copy queued
// behind them on the same stream; publish_vram synchronises before the result is released.
static const bool evalSync = env_uint("D4R_SHIM_EVAL_SYNC", 1) != 0;
const int syncResult = params.vram && !evalSync ? 0 : g.cu.ctxSynchronize();
const int syncResult = params.vram && !evalSync ? 0 : synchronize_default_stream(*feature);
const auto evaluated = ProfileClock::now();
const bool gpuEventsRecorded = eventStartResult == 0 && eventEndResult == 0;
float gpuEvalMs = -1.0f;
if (gpuEventsRecorded && syncResult == 0 && (!params.vram || evalSync) &&
g.cu.eventElapsedTime(&gpuEvalMs, feature->profileStart, feature->profileEnd) != 0)
gpuEvalMs = -1.0f;
if (params.vram && !gpuWait)
slot.busy = false; // the input copies ran before the evaluation
// Keep the input slot owned until output completion, including linear reads.
if (result != NGX_SUCCESS || syncResult != 0)
{
logf("frame %u: CUDA_EvaluateFeature -> 0x%08x, sync %d", frame, result, syncResult);
if (gpuWait)
{
g.cu.ctxSynchronize();
feature->gpuWaitFrame = 0;
slot.busy = false;
}
slot.busy = false;
frame_retired(feature, frame);
return;
}
@@ -3886,11 +3961,7 @@ static void run_evaluation(Feature* feature, int slotIndex, uint32_t frame, Fram
if (params.vram)
{
publish_vram(feature, frame, params, timing, start);
if (gpuWait)
{
feature->gpuWaitFrame = 0;
slot.busy = false; // publish_vram waited for the whole frame, input copies included
}
slot.busy = false; // output synchronization completed all reads of this input slot
return;
}
@@ -3932,13 +4003,14 @@ static void prepare_inputs(Feature* feature, int slotIndex, uint32_t frame, Fram
{
InputSlot& slot = feature->inputs[slotIndex];
const auto start = ProfileClock::now();
// Wait until the game's command list with this frame's input copies ran (with gpuWait the GPU waits).
while (!(params.vram && feature->gpuWait) && static_cast<int32_t>(*feature->markerValue - frame) < 0)
// Wait for actual input readiness without queueing an unbounded GPU-side wait.
while (static_cast<int32_t>(*feature->markerValue - frame) < 0)
{
std::atomic_thread_fence(std::memory_order_acquire);
if (std::chrono::steady_clock::now() - start > std::chrono::seconds(5))
if (feature->retiring.load() || std::chrono::steady_clock::now() - start > std::chrono::seconds(5))
{
logf("frame %u: timed out waiting for GPU marker (at %u); dropping", frame, *feature->markerValue);
logf("frame %u: %s waiting for GPU marker (at %u); dropping", frame,
feature->retiring.load() ? "feature retired while" : "timed out", *feature->markerValue);
slot.busy = false;
frame_retired(feature, frame);
return;
@@ -3948,7 +4020,7 @@ static void prepare_inputs(Feature* feature, int slotIndex, uint32_t frame, Fram
if (markerPollUs == 0)
std::this_thread::yield();
else
std::this_thread::sleep_for(std::chrono::microseconds(markerPollUs));
d4r_sleep_us(markerPollUs);
}
const auto ready = ProfileClock::now();
// With VRAM interop the inputs are already on the GPU; host staging runs
@@ -4013,7 +4085,7 @@ static int claim_output_slot(Feature* feature)
}
if (std::chrono::steady_clock::now() - start > std::chrono::milliseconds(50))
return -1;
std::this_thread::sleep_for(std::chrono::microseconds(200));
d4r_sleep_us(200);
}
}
@@ -4046,10 +4118,10 @@ static void finish_publish(Feature* feature, int target, uint32_t frame, const F
"color_convert=%.3f depth_convert=%.3f motion_convert=%.3f exposure_convert=%.3f "
"color_h2d=%.3f depth_h2d=%.3f motion_h2d=%.3f exposure_h2d=%.3f "
"ngx_host=%.3f ctx_sync=%.3f gpu_eval=%.3f output_sync_wall=%.3f "
"output_sync_cpu=%.3f output_sync_blocking=%d d2h=%.3f output_convert=%.3f "
"output_sync_cpu=%.3f output_sync_blocking=%d output_sync_method=%s d2h=%.3f output_convert=%.3f "
"h2d_total=%.3f d2h_issue=%.3f d2h_wait=%.3f "
"prep=%.3f worker_wait=%.3f worker=%.3f finish_wait=%.3f finish=%.3f pipeline=%.3f "
"throughput_interval=%.3f presented=%u age=%d",
"throughput_interval=%.3f presented=%u age=%d motion_dilation=%u motion_dilation_wall=%.3f",
frame, params.renderWidth, params.renderHeight, feature->outWidth, feature->outHeight,
slot.host[0].width, slot.host[0].height, slot.host[1].width, slot.host[1].height,
slot.host[2].width, slot.host[2].height, params.hasExposure, feature->quality, feature->flags,
@@ -4060,11 +4132,13 @@ static void finish_publish(Feature* feature, int target, uint32_t frame, const F
timing.convert[0], timing.convert[1], timing.convert[2], timing.convert[3],
timing.upload[0], timing.upload[1], timing.upload[2], timing.upload[3],
timing.ngxHost, timing.ctxSync, timing.gpuEval, timing.outputSyncWall, timing.outputSyncCpu,
timing.outputSyncBlocking, timing.download, timing.outputConvert,
timing.outputSyncBlocking, timing.outputSyncBlocking ? "event_query_sleep" : "context",
timing.download, timing.outputConvert,
timing.h2dTotal, timing.d2hIssue, timing.d2hWait,
timing.prep, timing.workerWait, timing.worker, timing.finishWait, profile_ms(start, finished),
profile_ms(timing.prepStart, finished), interval,
timing.presentedFrame, timing.presentedFrame != 0 ? static_cast<int>(frame - timing.presentedFrame) : -1);
timing.presentedFrame, timing.presentedFrame != 0 ? static_cast<int>(frame - timing.presentedFrame) : -1,
params.motionDilation, timing.motionDilationWall);
}
if (completed <= 5 || completed % 120 == 0)
logf("frame %u done (slot %d)", frame, target);
@@ -4099,6 +4173,40 @@ static void drain_pipeline()
g.finish.call([] { return 0; });
}
static void destroy_recorded_resources(Feature* feature);
static void finish_shutdown();
class DeferredResources final : public IUnknown
{
public:
explicit DeferredResources(Feature* feature) : feature_(feature) { ++g.resourceOwners; }
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** out) override
{
if (out == nullptr) return E_POINTER;
*out = nullptr;
if (iid != __uuidof(IUnknown)) return E_NOINTERFACE;
*out = static_cast<IUnknown*>(this);
AddRef();
return S_OK;
}
ULONG STDMETHODCALLTYPE AddRef() override { return ++refs_; }
ULONG STDMETHODCALLTYPE Release() override
{
const ULONG remaining = --refs_;
if (remaining == 0)
g.cleanup.post([this] {
destroy_recorded_resources(feature_);
delete this;
--g.resourceOwners;
finish_shutdown();
});
return remaining;
}
private:
std::atomic<ULONG> refs_{1};
Feature* feature_;
};
// --- exported NGX D3D12 API ------------------------------------------------------
#define D4R_EXPORT extern "C" __declspec(dllexport)
@@ -4284,8 +4392,16 @@ static float get_float_or(void* parameters, const char* name, float fallback)
D4R_EXPORT NgxResult NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsCommandList*, unsigned int featureId, void* parameters,
NgxHandle** handle)
{
std::lock_guard<std::mutex> apiLock(g_featureApiMutex);
std::lock_guard<std::mutex> stateLock(g.mutex);
if (!g.ngxInitialized)
return NGX_FAIL_NOT_INITIALIZED;
std::call_once(g_vk.once, init_vram_interop);
if (g_vk.lifetime == nullptr)
{
logf("DLSS requires lifetime-aware d4r d3d12.dll/d3d12core.dll; update both files with the shim");
return NGX_FAIL_PLATFORM_ERROR;
}
if (featureId != NGX_FEATURE_SUPER_SAMPLING || parameters == nullptr || handle == nullptr)
{
logf("NVSDK_NGX_D3D12_CreateFeature(feature=%u) unsupported", featureId);
@@ -4355,12 +4471,12 @@ D4R_EXPORT NgxResult NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsCommandList*, u
else
feature->scratch = 0;
const NgxResult created = g.ngx.createFeature(NGX_FEATURE_SUPER_SAMPLING, p, &feature->cudaHandle);
if (created == NGX_SUCCESS && env_uint("D4R_SHIM_BLOCKING_SYNC", 0) != 0 &&
if (created == NGX_SUCCESS && env_uint("D4R_SHIM_BLOCKING_SYNC", 1) != 0 &&
g.cu.eventCreate != nullptr && g.cu.eventRecord != nullptr &&
g.cu.eventSynchronize != nullptr && g.cu.eventDestroy != nullptr)
g.cu.eventSynchronize != nullptr && g.cu.eventQuery != nullptr && g.cu.eventDestroy != nullptr)
{
// All VRAM-path input waits, NGX kernels and output copies use the null stream.
// A blocking event yields the CPU while the GPU finishes that stream.
// Explicit query/sleep waits avoid HIP's spinning event-sync path.
// Timing is unnecessary for this completion event.
constexpr unsigned int kBlockingSyncNoTiming = 0x1u | 0x2u;
const int eventResult = g.cu.eventCreate(&feature->outputReadyEvent, kBlockingSyncNoTiming);
if (eventResult != 0)
@@ -4369,7 +4485,7 @@ D4R_EXPORT NgxResult NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsCommandList*, u
feature->outputReadyEvent = nullptr;
}
else
logf("blocking output wait enabled for this feature");
logf("output event query/sleep wait enabled for this feature");
}
if (created == NGX_SUCCESS && profile_enabled() && g.cu.eventCreate != nullptr &&
g.cu.eventRecord != nullptr && g.cu.eventElapsedTime != nullptr && g.cu.eventDestroy != nullptr)
@@ -4402,6 +4518,7 @@ D4R_EXPORT NgxResult NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsCommandList*, u
return NGX_FAIL_PLATFORM_ERROR;
}
*feature->markerValue = 0;
feature->resources = new DeferredResources(feature);
feature->handle.Id = g.nextHandleId++;
{
std::lock_guard<std::mutex> lock(g_featuresMutex);
@@ -4415,7 +4532,7 @@ static Feature* find_feature(const NgxHandle* handle)
{
std::lock_guard<std::mutex> lock(g_featuresMutex);
for (Feature* feature : g_features)
if (&feature->handle == handle)
if (&feature->handle == handle && !feature->retiring.load())
return feature;
return nullptr;
}
@@ -4433,6 +4550,7 @@ static ID3D12Resource* get_resource(void* parameters, const char* name)
static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* handle, void* parameters)
{
std::lock_guard<std::mutex> apiLock(g_featureApiMutex);
FrameTiming timing;
timing.enabled = profile_enabled();
if (timing.enabled)
@@ -4543,11 +4661,18 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand
logf("frame %u: input slot %d busy or %d frames in flight; skipping evaluation", frame, slotIndex, feature->inFlight.load());
return NGX_SUCCESS;
}
std::this_thread::sleep_for(std::chrono::microseconds(200));
d4r_sleep_us(200);
}
if (timing.enabled)
timing.slotWait = profile_ms(waitStart, ProfileClock::now());
if (g_vk.lifetime == nullptr ||
FAILED(g_vk.lifetime->RetainExternalResources(list, feature->resources)))
{
logf("frame %u: unable to retain command-list resources; evaluation not recorded", frame);
return NGX_FAIL_PLATFORM_ERROR;
}
// VRAM interop when every resource qualifies (decided once per feature).
ID3D12Resource* const inputs[4] = {color, depth, motion, exposure};
const Plane planes[4] = {Plane::Color, Plane::Depth, Plane::Motion, Plane::Exposure};
@@ -4565,17 +4690,13 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand
feature->vram = p.vram;
feature->split = p.vram && env_uint("D4R_SHIM_SPLIT_FRAME", 0) != 0 && g_vk.split != nullptr &&
create_split_semaphore(*feature);
if (feature->split && env_uint("D4R_SHIM_GPU_WAIT", 0) != 0 && g.cu.streamWaitValue32 != nullptr &&
g.cu.writeValue32 != nullptr && g_vk.fill != nullptr)
feature->gpuWait = ensure_vram_buffer(*feature, feature->gpuMarker, 256) &&
g.worker.call([feature] { return g.cu.writeValue32(feature->gpuMarker.device, 0); }) == 0;
feature->linearInputs = feature->gpuWait && env_uint("D4R_SHIM_LINEAR_INPUTS", 0) != 0 &&
feature->linearInputs = feature->split && env_uint("D4R_SHIM_LINEAR_INPUTS", 0) != 0 &&
g.cu.registerLinearTexture != nullptr;
if (feature->linearInputs)
logf("linear inputs: NGX samples the interop buffers directly");
logf("VRAM interop %s for this feature%s%s", p.vram ? "on" : "off",
feature->split ? ", presenting each frame's own result (split frames)" : "",
feature->gpuWait ? ", DLSS queued behind a GPU-side wait for the inputs" : "");
p.vram ? ", waiting for input readiness with CPU sleeps" : "");
}
else if (feature->vram && !p.vram)
{
@@ -4586,7 +4707,6 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand
feature->split = false;
}
feature->vram = false;
feature->gpuWait = false;
feature->linearInputs = false;
feature->latestOutput = -1;
feature->outputFormat = DXGI_FORMAT_UNKNOWN; // recreates the output staging below
@@ -4640,7 +4760,7 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand
feature->outputHeight = outputDesc.Height;
for (OutputSlot& outputSlot : feature->outputs)
{
outputSlot.staging.release();
outputSlot.staging.retire();
if (p.vram ? !ensure_vram_buffer(*feature, outputSlot.vram, static_cast<size_t>(outputDesc.Width) * 8 *
outputDesc.Height)
: !ensure_staging(outputSlot.staging, output, D3D12_HEAP_TYPE_UPLOAD))
@@ -4657,7 +4777,7 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand
const D3D12_RESOURCE_STATES states[4] = {inputState, depthState, inputState, inputState};
for (int index = 0; index < inputCount; ++index)
transition(list, inputs[index], states[index], D3D12_RESOURCE_STATE_COPY_SOURCE);
const bool recorded = record_vram_inputs(*feature, list, slot, vramInputs, inputCount, frame);
const bool recorded = record_vram_inputs(*feature, list, slot, vramInputs, inputCount);
for (int index = 0; index < inputCount; ++index)
transition(list, inputs[index], D3D12_RESOURCE_STATE_COPY_SOURCE, states[index]);
if (!recorded)
@@ -4779,9 +4899,15 @@ static void release_feature(Feature* feature)
if (feature->cudaParams != nullptr)
g.ngx.destroyParameters(feature->cudaParams);
for (InputSlot& slot : feature->inputs)
{
for (CudaObject& texture : slot.linearTexture)
if (texture != 0)
g.cu.texObjectDestroy(texture), texture = 0;
if (slot.dilatedTexture != 0)
g.cu.texObjectDestroy(slot.dilatedTexture), slot.dilatedTexture = 0;
if (slot.dilatedMotion != 0)
g.cu.memFree(slot.dilatedMotion), slot.dilatedMotion = 0;
}
for (CudaImage* image : {&feature->color, &feature->depth, &feature->motion, &feature->exposure})
{
if (image->object != 0)
@@ -4813,38 +4939,40 @@ static void release_feature(Feature* feature)
release_host(host);
return 0;
});
{
std::lock_guard<std::mutex> lock(g_featuresMutex);
g_features.erase(std::remove(g_features.begin(), g_features.end(), feature), g_features.end());
}
// Recorded lists retain this owner through their allocator. Reset/discard
// or GPU completion followed by allocator destruction releases those refs.
feature->resources->Release();
}
static void destroy_recorded_resources(Feature* feature)
{
for (InputSlot& slot : feature->inputs)
for (Staging* staging : {&slot.color, &slot.depth, &slot.motion, &slot.exposure})
staging->release();
for (OutputSlot& slot : feature->outputs)
slot.staging.release();
if (g_vk.ready && feature->vramDecided)
{
// Queued command lists may still copy from or wait on these; the last
// marker lands just before the last output copy.
const auto start = std::chrono::steady_clock::now();
while (feature->markerValue != nullptr &&
static_cast<int32_t>(*feature->markerValue - feature->frame) < 0 &&
std::chrono::steady_clock::now() - start < std::chrono::seconds(2))
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::this_thread::sleep_for(std::chrono::milliseconds(50));
release_vram(*feature);
}
if (feature->marker != nullptr)
feature->marker->Release();
logf("feature %u recorded resources retired after allocator completion", feature->handle.Id);
delete feature;
}
D4R_EXPORT NgxResult NVSDK_NGX_D3D12_ReleaseFeature(NgxHandle* handle)
{
std::lock_guard<std::mutex> apiLock(g_featureApiMutex);
Feature* feature = nullptr;
{
std::lock_guard<std::mutex> lock(g_featuresMutex);
for (auto it = g_features.begin(); it != g_features.end(); ++it)
if (&(*it)->handle == handle)
for (Feature* candidate : g_features)
if (&candidate->handle == handle && !candidate->retiring.exchange(true))
{
feature = *it;
g_features.erase(it);
feature = candidate;
break;
}
}
@@ -4856,30 +4984,51 @@ D4R_EXPORT NgxResult NVSDK_NGX_D3D12_ReleaseFeature(NgxHandle* handle)
return NGX_SUCCESS;
}
D4R_EXPORT NgxResult NVSDK_NGX_D3D12_Shutdown()
static void finish_shutdown()
{
logf("NVSDK_NGX_D3D12_Shutdown");
std::vector<Feature*> features;
{
std::lock_guard<std::mutex> lock(g_featuresMutex);
features.swap(g_features);
}
for (Feature* feature : features)
release_feature(feature);
std::lock_guard<std::mutex> lock(g.mutex);
if (!g.shutdownPending || g.resourceOwners.load() != 0)
return;
if (g_vk.ready)
free_vram_pool();
std::lock_guard<std::mutex> lock(g.mutex);
if (g.ngxInitialized)
{
drain_pipeline();
g.worker.call([] { return g.ngx.shutdown(); });
}
g.ngxInitialized = false;
g.shutdownPending = false;
if (g.device != nullptr)
{
g.device->Release();
g.device = nullptr;
}
logf("NGX shutdown completed after recorded resources retired");
}
D4R_EXPORT NgxResult NVSDK_NGX_D3D12_Shutdown()
{
std::lock_guard<std::mutex> apiLock(g_featureApiMutex);
logf("NVSDK_NGX_D3D12_Shutdown");
std::vector<Feature*> features;
{
std::lock_guard<std::mutex> lock(g_featuresMutex);
features = g_features;
for (Feature* feature : features)
feature->retiring = true;
}
for (Feature* feature : features)
release_feature(feature);
{
std::lock_guard<std::mutex> lock(g.mutex);
if (!g.started)
return NGX_SUCCESS;
g.shutdownPending = true;
}
// Drain already queued cleanup before returning. If command allocators
// still own resources, finish_shutdown leaves NGX alive until their last
// reference retires; otherwise shutdown completes synchronously.
g.cleanup.call([] { finish_shutdown(); return 0; });
return NGX_SUCCESS;
}
+59
View File
@@ -0,0 +1,59 @@
#pragma once
#include <d3d12.h>
#include <vulkan/vulkan_core.h>
MIDL_INTERFACE("39da4e09-bd1c-4198-9fae-86bbe3be41fd")
ID3D12DXVKInteropDevice : public IUnknown
{
virtual HRESULT STDMETHODCALLTYPE GetDXGIAdapter(REFIID iid, void** object) = 0;
virtual HRESULT STDMETHODCALLTYPE GetInstanceExtensions(UINT* count, const char** extensions) = 0;
virtual HRESULT STDMETHODCALLTYPE GetDeviceExtensions(UINT* count, const char** extensions) = 0;
virtual HRESULT STDMETHODCALLTYPE GetDeviceFeatures(const VkPhysicalDeviceFeatures2** features) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVulkanHandles(VkInstance* instance, VkPhysicalDevice* physical,
VkDevice* device) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVulkanQueueInfo(ID3D12CommandQueue* queue, VkQueue* vkQueue,
UINT32* family) = 0;
virtual void STDMETHODCALLTYPE GetVulkanImageLayout(ID3D12Resource* resource, D3D12_RESOURCE_STATES state,
VkImageLayout* layout) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVulkanResourceInfo(ID3D12Resource* resource, UINT64* handle,
UINT64* offset) = 0;
virtual HRESULT STDMETHODCALLTYPE LockCommandQueue(ID3D12CommandQueue* queue) = 0;
virtual HRESULT STDMETHODCALLTYPE UnlockCommandQueue(ID3D12CommandQueue* queue) = 0;
};
MIDL_INTERFACE("902d8115-59eb-4406-9518-fe00f991ee65")
ID3D12DXVKInteropDevice1 : public ID3D12DXVKInteropDevice
{
virtual HRESULT STDMETHODCALLTYPE GetVulkanResourceInfo1(ID3D12Resource* resource, UINT64* handle, UINT64* offset,
VkFormat* format) = 0;
virtual HRESULT STDMETHODCALLTYPE CreateInteropCommandQueue(const D3D12_COMMAND_QUEUE_DESC* desc, UINT32 family,
ID3D12CommandQueue** queue) = 0;
virtual HRESULT STDMETHODCALLTYPE CreateInteropCommandAllocator(D3D12_COMMAND_LIST_TYPE type, UINT32 family,
ID3D12CommandAllocator** allocator) = 0;
virtual HRESULT STDMETHODCALLTYPE BeginVkCommandBufferInterop(ID3D12CommandList* list, VkCommandBuffer* buffer) = 0;
virtual HRESULT STDMETHODCALLTYPE EndVkCommandBufferInterop(ID3D12CommandList* list) = 0;
};
__CRT_UUID_DECL(ID3D12DXVKInteropDevice1, 0x902d8115, 0x59eb, 0x4406, 0x95, 0x18, 0xfe, 0x00, 0xf9, 0x91, 0xee, 0x65)
// Added by the d4r vkd3d-proton patch (patches/vkd3d-proton/): ends the list's
// current Vulkan command buffer; what is recorded afterwards is submitted
// separately, with a queue-level wait for the timeline semaphore value.
MIDL_INTERFACE("5a7c8b3e-2f61-4d0e-9c1a-7e3b52d4a901")
ID3D12DXVKInteropDeviceD4R : public ID3D12DXVKInteropDevice1
{
virtual HRESULT STDMETHODCALLTYPE LockVulkanQueue(ID3D12CommandQueue* queue) = 0;
virtual HRESULT STDMETHODCALLTYPE UnlockVulkanQueue(ID3D12CommandQueue* queue) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVulkanHeapInfo(ID3D12Heap* heap, UINT64* memory, UINT64* offset,
UINT32* type) = 0;
virtual HRESULT STDMETHODCALLTYPE SplitCommandListForExternalWait(ID3D12CommandList* list, UINT64 semaphore,
UINT64 value) = 0;
};
__CRT_UUID_DECL(ID3D12DXVKInteropDeviceD4R, 0x5a7c8b3e, 0x2f61, 0x4d0e, 0x9c, 0x1a, 0x7e, 0x3b, 0x52, 0xd4, 0xa9, 0x01)
MIDL_INTERFACE("4f8c9462-8197-4dc1-a584-cf44d9706db2")
ID3D12DXVKInteropDeviceD4R2 : public ID3D12DXVKInteropDeviceD4R
{
virtual HRESULT STDMETHODCALLTYPE RetainExternalResources(ID3D12CommandList* list, IUnknown* resources) = 0;
};
__CRT_UUID_DECL(ID3D12DXVKInteropDeviceD4R2, 0x4f8c9462, 0x8197, 0x4dc1, 0xa5, 0x84, 0xcf, 0x44, 0xd9, 0x70, 0x6d, 0xb2)
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <windows.h>
#include <cstdint>
// MinGW's Win32 std::sleep_for can truncate submillisecond durations to zero.
// NtDelayExecution retains 100 ns precision and actually sleeps under Wine.
inline void d4r_sleep_us(unsigned int microseconds)
{
using Delay = LONG(WINAPI*)(BOOLEAN, LARGE_INTEGER*);
static const auto delay = reinterpret_cast<Delay>(reinterpret_cast<void*>(
GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtDelayExecution")));
if (microseconds == 0)
{
SwitchToThread();
return;
}
LARGE_INTEGER interval;
interval.QuadPart = -static_cast<LONGLONG>(microseconds) * 10;
if (delay != nullptr && delay(FALSE, &interval) >= 0)
return;
// A rounded-up millisecond wait still releases the CPU if ntdll is absent.
Sleep(static_cast<DWORD>((static_cast<uint64_t>(microseconds) + 999) / 1000));
}
+80 -1
View File
@@ -12,6 +12,10 @@
#include <sys/stat.h>
#include <unistd.h>
#include "d4r_native_selection.h"
#include "d4r_motion_dilation.h"
#ifdef D4R_MOTION_KERNELS
#include "d4r_motion_kernels.h"
#endif
typedef int CUresult;
typedef int CUdevice;
@@ -457,6 +461,15 @@ static void prepare_native_kernels(const char* cache_home)
}
if (kind == 2)
{
/* A flat developer set has no per-kernel manifest to filter. Fail safely to
translated kernels rather than silently retaining the disabled encoders. */
if (!d4r_native_swin_encoders_enabled())
{
tracef("NativeSwinEncoders off: developer set needs d4r-kernels.txt for selective fallback; "
"using translated kernels for this set");
unsetenv("D4R_ZLUDA_NATIVE_DIR");
return;
}
setenv("D4R_ZLUDA_NATIVE_DIR", source, 1);
tracef("native kernels: %sdeveloper set in %s", accuracy ? "accuracy " : "", source);
return;
@@ -476,6 +489,8 @@ static void prepare_native_kernels(const char* cache_home)
unsigned long long hash = 0;
if (line[0] == '#' || sscanf(line, "%127s %llx", name, &hash) != 2)
continue;
if (!d4r_native_kernel_allowed(name))
continue;
if (native_kernel_count == capacity)
{
capacity = capacity != 0 ? capacity * 2 : 32;
@@ -490,6 +505,9 @@ static void prepare_native_kernels(const char* cache_home)
kernel->linked = 0;
}
fclose(manifest);
if (!d4r_native_swin_encoders_enabled())
tracef("NativeSwinEncoders off: rrlite_enc1_4x4 and rrlite_enc2_4x4 use original translated PTX; "
"other native kernels retained");
/* <cache>/d4r-native/<pid>; directories of processes that no longer exist are removed */
char base[1024];
@@ -843,6 +861,57 @@ static void* find_zluda_symbol(const char* name)
return cuda_library != NULL ? dlsym(cuda_library, name) : NULL;
}
/* d4r-owned, opt-in preprocessing. Runs on HIP's legacy/default stream, shared
with CUDA's null stream; the shim records and waits for completion before NGX. */
CUresult WINAPI d4rDilateMotionVectors(const D4rMotionDilationParams* parameters)
{
if (parameters == NULL || parameters->depth == 0 || parameters->motion == 0 || parameters->output == 0 ||
parameters->radius < 1 || parameters->radius > 2 || parameters->render_width == 0 ||
parameters->render_height == 0 || parameters->motion_width == 0 || parameters->motion_height == 0 ||
parameters->depth_pitch < (uint64_t)parameters->render_width * 4 ||
parameters->motion_pitch < (uint64_t)parameters->motion_width * 4 ||
(uint64_t)parameters->motion_width * parameters->motion_height > 0x7fffffffull)
return CUDA_ERROR_INVALID_VALUE;
#ifdef D4R_MOTION_KERNELS
typedef int(__attribute__((sysv_abi)) * HIP_LOAD)(void**, const void*);
typedef int(__attribute__((sysv_abi)) * HIP_FUNCTION)(void**, void*, const char*);
typedef int(__attribute__((sysv_abi)) * HIP_LAUNCH)(void*, unsigned, unsigned, unsigned,
unsigned, unsigned, unsigned, unsigned, void*, void**, void**);
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static void* module;
static void* function;
static HIP_LAUNCH launch;
static int initialized, result = CUDA_ERROR_NOT_SUPPORTED;
pthread_mutex_lock(&lock);
if (!initialized)
{
initialized = 1;
pthread_once(&load_once, load_zluda);
void* hip = dlopen("libamdhip64.so.7", RTLD_NOW | RTLD_LOCAL);
char architecture[32];
gpu_architecture(architecture, sizeof(architecture));
const void* image = d4r_motion_image(architecture);
HIP_LOAD load = hip != NULL ? (HIP_LOAD)dlsym(hip, "hipModuleLoadData") : NULL;
HIP_FUNCTION get = hip != NULL ? (HIP_FUNCTION)dlsym(hip, "hipModuleGetFunction") : NULL;
launch = hip != NULL ? (HIP_LAUNCH)dlsym(hip, "hipModuleLaunchKernel") : NULL;
if (image != NULL && load != NULL && get != NULL && launch != NULL &&
load(&module, image) == 0 && get(&function, module, "d4r_motion_dilate") == 0)
result = CUDA_SUCCESS;
tracef("motion-vector dilation: %s (%s)", result == 0 ? "GPU kernel ready" : "unavailable", architecture);
}
const int status = result;
pthread_mutex_unlock(&lock);
if (status != CUDA_SUCCESS)
return status;
D4rMotionDilationParams copy = *parameters;
void* arguments[] = {&copy};
const uint64_t pixels = (uint64_t)copy.motion_width * copy.motion_height;
return launch(function, (unsigned)((pixels + 127) / 128), 1, 1, 128, 1, 1, 0, NULL, arguments, NULL);
#else
return CUDA_ERROR_NOT_SUPPORTED;
#endif
}
static void tracef(const char* format, ...)
{
char line[2048];
@@ -2579,7 +2648,17 @@ CUresult WINAPI d4rEventSynchronize(D4rArg a0)
TRACE_CALL(result, "d4rEventSynchronize result=%d", result);
return result;
}
D4R_FORWARD(cuEventQuery, cuEventQuery, 1, (D4rArg a0), (a0))
CUresult WINAPI cuEventQuery(D4rArg event)
{
typedef CUresult(__attribute__((sysv_abi)) *function_type)(D4rArg);
function_type function = (function_type)find_zluda_symbol("cuEventQuery");
CUresult result = function != NULL ? function(event) : missing("cuEventQuery");
/* NOT_READY is the normal result while the shim sleeps between queries,
not an error to flush to the log on every poll. */
TRACE_CALL(result == CUDA_ERROR_NOT_READY ? CUDA_SUCCESS : result,
"cuEventQuery result=%d", result);
return result;
}
/* With elided event waits an event may still be pending: report 0 ms instead of CUDA_ERROR_NOT_READY. */
CUresult WINAPI cuEventElapsedTime(D4rArg a0, D4rArg a1, D4rArg a2)
{
+1
View File
@@ -100,3 +100,4 @@
@ stdcall d4rSetEnv(str str long) d4rSetEnv
@ stdcall d4rLoadError() d4rLoadError
@ stdcall d4rOutputKernelNative() d4rOutputKernelNative
@ stdcall d4rDilateMotionVectors(ptr) d4rDilateMotionVectors