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;