Files
d4r/tools/d3d12_dlss_harness.cpp
ayo 7c5d3c9157 feat: add d4r inference engines and Windows parity tooling
Add native K/M/RR inference engines, HIP kernels, replay and validation
harnesses, and Windows DLSS setup and runtime support. Include GPU
profiling tools, build and packaging scripts, platform integration
patches, and implementation documentation.
2026-10-16 08:54:47 -04:00

1046 lines
52 KiB
C++

// Drives d4r_nvngx.dll through the NGX D3D12 API the way a game (or
// OptiScaler's DLSS backend) does: a D3D12 device and command queue, input
// textures in NON_PIXEL_SHADER_RESOURCE, an output UAV, and one
// EvaluateFeature per recorded command list. The inputs reproduce the CUDA
// probe's synthetic frame (ngx_cuda_probe.cpp) so the D3D12 result can be
// compared with the direct CUDA result.
//
// usage: d3d12_dlss_harness.exe SHIM_DLL OUTPUT_RAW [FRAMES [IN_W IN_H OUT_W OUT_H]]
#define WIDL_EXPLICIT_AGGREGATE_RETURNS
#include <windows.h>
#include <d3d12.h>
#include <algorithm>
#include <array>
#include <bit>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <atomic>
#include <chrono>
#include "d4r_vkd3d_interop.h"
using NgxResult = unsigned int;
constexpr NgxResult NGX_SUCCESS = 1;
struct NgxHandle
{
unsigned int Id;
};
extern "C"
{
void d4r_ngx_set_float(void* parameters, const char* name, float value);
void d4r_ngx_set_uint(void* parameters, const char* name, unsigned int value);
void d4r_ngx_set_int(void* parameters, const char* name, int value);
void d4r_ngx_set_d3d12_resource(void* parameters, const char* name, ID3D12Resource* value);
NgxResult d4r_ngx_get_int(void* parameters, const char* name, int* value);
}
using PFN_Init_Ext = NgxResult (*)(unsigned long long, const wchar_t*, ID3D12Device*, unsigned int, const void*);
using PFN_Parameters = NgxResult (*)(void**);
using PFN_CreateFeature = NgxResult (*)(ID3D12GraphicsCommandList*, unsigned int, void*, NgxHandle**);
using PFN_Evaluate = NgxResult (*)(ID3D12GraphicsCommandList*, const NgxHandle*, void*, void*);
using PFN_Release = NgxResult (*)(NgxHandle*);
using PFN_Shutdown = NgxResult (*)();
static ID3D12Device* g_device;
static ID3D12CommandQueue* g_queue;
static ID3D12CommandAllocator* g_allocator;
static ID3D12GraphicsCommandList* g_list;
static ID3D12Fence* g_fence;
static HANDLE g_event;
static UINT64 g_fenceValue;
static unsigned long long process_vram_kib()
{
std::vector<unsigned long long> clients;
unsigned long long total = 0;
for (int fd = 0; fd < 4096; ++fd)
{
char path[64];
std::snprintf(path, sizeof(path), "Z:\\proc\\self\\fdinfo\\%d", fd);
FILE* file = std::fopen(path, "r");
if (file == nullptr)
continue;
unsigned long long client = 0, vram = 0;
bool drm = false;
char line[256];
while (std::fgets(line, sizeof(line), file) != nullptr)
if (std::sscanf(line, "drm-client-id: %llu", &client) == 1)
drm = true;
else
std::sscanf(line, "drm-memory-vram: %llu", &vram);
std::fclose(file);
if (drm && std::find(clients.begin(), clients.end(), client) == clients.end())
{
clients.push_back(client);
total += vram;
}
}
return total;
}
static bool check(HRESULT hr, const char* what)
{
if (FAILED(hr))
std::printf("%s failed: 0x%08lx\n", what, hr);
return SUCCEEDED(hr);
}
static void submit_and_wait()
{
g_list->Close();
ID3D12CommandList* lists[] = {g_list};
g_queue->ExecuteCommandLists(1, lists);
g_queue->Signal(g_fence, ++g_fenceValue);
if (g_fence->GetCompletedValue() < g_fenceValue)
{
g_fence->SetEventOnCompletion(g_fenceValue, g_event);
WaitForSingleObject(g_event, INFINITE);
}
g_allocator->Reset();
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 = {};
heap.Type = type;
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Width = size;
desc.Height = 1;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
ID3D12Resource* buffer = nullptr;
check(g_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, state, nullptr,
__uuidof(ID3D12Resource), reinterpret_cast<void**>(&buffer)),
"CreateCommittedResource(buffer)");
return buffer;
}
static ID3D12Resource* create_texture(UINT width, UINT height, DXGI_FORMAT format, D3D12_RESOURCE_FLAGS flags)
{
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = D3D12_HEAP_TYPE_DEFAULT;
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
desc.Width = width;
desc.Height = height;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.Format = format;
desc.SampleDesc.Count = 1;
desc.Flags = flags;
ID3D12Resource* texture = nullptr;
check(g_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COPY_DEST,
nullptr, __uuidof(ID3D12Resource), reinterpret_cast<void**>(&texture)),
"CreateCommittedResource(texture)");
return texture;
}
static void barrier(ID3D12Resource* resource, D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after)
{
D3D12_RESOURCE_BARRIER b = {};
b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
b.Transition.pResource = resource;
b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
b.Transition.StateBefore = before;
b.Transition.StateAfter = after;
g_list->ResourceBarrier(1, &b);
}
// Uploads tightly packed rows into a texture, leaving it in finalState.
static void upload(ID3D12Resource* texture, const void* data, UINT rowBytes, D3D12_RESOURCE_STATES finalState)
{
D3D12_RESOURCE_DESC desc;
texture->GetDesc(&desc);
D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout;
UINT rows;
UINT64 rowSize, total;
g_device->GetCopyableFootprints(&desc, 0, 1, 0, &layout, &rows, &rowSize, &total);
ID3D12Resource* staging = create_buffer(D3D12_HEAP_TYPE_UPLOAD, total, D3D12_RESOURCE_STATE_GENERIC_READ);
uint8_t* mapped = nullptr;
D3D12_RANGE none = {0, 0};
staging->Map(0, &none, reinterpret_cast<void**>(&mapped));
for (UINT y = 0; y < rows; ++y)
std::memcpy(mapped + layout.Offset + static_cast<size_t>(y) * layout.Footprint.RowPitch,
static_cast<const uint8_t*>(data) + static_cast<size_t>(y) * rowBytes, rowBytes);
staging->Unmap(0, nullptr);
D3D12_TEXTURE_COPY_LOCATION destination = {};
destination.pResource = texture;
destination.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
D3D12_TEXTURE_COPY_LOCATION source = {};
source.pResource = staging;
source.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
source.PlacedFootprint = layout;
g_list->CopyTextureRegion(&destination, 0, 0, 0, &source, nullptr);
barrier(texture, D3D12_RESOURCE_STATE_COPY_DEST, finalState);
submit_and_wait();
staging->Release();
}
static void update_texture(ID3D12Resource* texture, const void* data, UINT rowBytes,
D3D12_RESOURCE_STATES currentState)
{
barrier(texture, currentState, D3D12_RESOURCE_STATE_COPY_DEST);
submit_and_wait();
upload(texture, data, rowBytes, currentState);
}
static std::vector<uint8_t> read_back(ID3D12Resource* texture, D3D12_RESOURCE_STATES state, UINT rowBytes)
{
D3D12_RESOURCE_DESC desc;
texture->GetDesc(&desc);
D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout;
UINT rows;
UINT64 rowSize, total;
g_device->GetCopyableFootprints(&desc, 0, 1, 0, &layout, &rows, &rowSize, &total);
ID3D12Resource* staging = create_buffer(D3D12_HEAP_TYPE_READBACK, total, D3D12_RESOURCE_STATE_COPY_DEST);
barrier(texture, state, D3D12_RESOURCE_STATE_COPY_SOURCE);
D3D12_TEXTURE_COPY_LOCATION destination = {};
destination.pResource = staging;
destination.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
destination.PlacedFootprint = layout;
D3D12_TEXTURE_COPY_LOCATION source = {};
source.pResource = texture;
source.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
g_list->CopyTextureRegion(&destination, 0, 0, 0, &source, nullptr);
barrier(texture, D3D12_RESOURCE_STATE_COPY_SOURCE, state);
submit_and_wait();
std::vector<uint8_t> result(static_cast<size_t>(rowBytes) * rows);
uint8_t* mapped = nullptr;
D3D12_RANGE range = {0, static_cast<SIZE_T>(total)};
staging->Map(0, &range, reinterpret_cast<void**>(&mapped));
for (UINT y = 0; y < rows; ++y)
std::memcpy(result.data() + static_cast<size_t>(y) * rowBytes,
mapped + layout.Offset + static_cast<size_t>(y) * layout.Footprint.RowPitch, rowBytes);
staging->Unmap(0, nullptr);
staging->Release();
return result;
}
static void trace_stage(const char* stage)
{
const char* path = std::getenv("D4R_HARNESS_TRACE");
if (path == nullptr || path[0] == '\0')
return;
if (FILE* file = std::fopen(path, "a"))
{
std::fprintf(file, "%s\n", stage);
std::fclose(file);
}
}
// A CPU-rasterized, subpixel-shifted version of the synthetic fence scene.
// Positive jitter moves the image right/down in screen space, so a render
// pixel samples the scene at (pixel - jitter). Four-by-four coverage samples
// make thin-line and checkerboard changes visible to temporal DLSS.
static void render_jittered_background(std::vector<uint16_t>& color, UINT width, UINT height, UINT stride,
float jitterX, float jitterY)
{
for (UINT y = 0; y < height; ++y)
for (UINT x = 0; x < width; ++x)
{
float sum[3] = {};
for (int sy = 0; sy < 4; ++sy)
for (int sx = 0; sx < 4; ++sx)
{
const float sampleX = float(x) + (float(sx) + 0.5f) * 0.25f - 0.5f - jitterX;
const float sampleY = float(y) + (float(sy) + 0.5f) * 0.25f - 0.5f - jitterY;
const int ix = std::clamp(int(std::floor(sampleX)), 0, int(width) - 1);
const int iy = std::clamp(int(std::floor(sampleY)), 0, int(height) - 1);
const bool fence = (ix % 24 == 0) || (iy % 24 == 0);
const bool checker = (((ix / 4) + (iy / 4)) & 1) != 0;
sum[0] += fence ? 1.0f : (checker ? 0.25f : 0.05f);
sum[1] += ((ix + iy) % 32 < 2) ? 0.8f : (checker ? 0.05f : 0.25f);
sum[2] += (((ix / 8) ^ (iy / 8)) & 1) ? 0.8f : 0.05f;
}
const size_t pixel = static_cast<size_t>(y) * stride + x;
for (size_t channel = 0; channel < 3; ++channel)
color[pixel * 4 + channel] = std::bit_cast<uint16_t>(static_cast<_Float16>(sum[channel] / 16.0f));
color[pixel * 4 + 3] = 0x3c00u;
}
}
// Quality scene (D4R_HARNESS_QUALITY_SCENE=<report file, Windows path>): a static analytic scene with
// detail near and below the render pixel pitch (blinds, thin poles, fine
// diagonals), rendered like a game feeds DLSS: one jittered point sample per
// render pixel. Coordinates are in output pixels.
static float quality_scene(float x, float y, UINT outWidth, UINT outHeight)
{
const float u = x / outWidth, v = y / outHeight;
auto frac = [](float value) { return value - std::floor(value); };
if (u > 0.05f && u < 0.45f && v > 0.1f && v < 0.9f) // blinds: 5 px period, 35% slat
return frac(y / 5.0f) < 0.35f ? 0.9f : 0.08f;
if (u > 0.55f && u < 0.95f && v > 0.1f && v < 0.45f) // thin poles: 7 px period, 1.4 px wide
return frac(x / 7.0f) < 0.2f ? 0.85f : 0.1f;
if (u > 0.55f && u < 0.95f && v > 0.55f && v < 0.9f) // fine diagonals: 6 px period
return frac((x + y) / 6.0f) < 0.25f ? 0.8f : 0.12f;
return 0.3f + 0.1f * std::sin(u * 6.0f) * std::cos(v * 5.0f);
}
// Pole and cable geometry resembling the reported Townfall failure. All
// positions are in output pixels, so both resource-width cases get identical
// active samples and only their three padding columns differ.
static float pole_scene(float x, float y, UINT outWidth, UINT outHeight)
{
const float u = x / outWidth, v = y / outHeight;
auto line = [](float px, float py, float ax, float ay, float bx, float by, float width) {
const float dx = bx - ax, dy = by - ay;
const float t = std::clamp(((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy), 0.0f, 1.0f);
const float ex = px - ax - t * dx, ey = py - ay - t * dy;
return ex * ex + ey * ey < width * width;
};
if (std::abs(u - 0.52f) < 0.004f && v > 0.15f)
return 0.035f;
for (float height : {0.24f, 0.36f, 0.52f, 0.68f})
if (line(u, v, 0.49f, height, 0.55f, height - 0.004f, 0.00045f))
return 0.045f;
if (line(u, v, 0.0f, 0.37f, 1.0f, 0.21f, 0.00036f) ||
line(u, v, 0.1f, 0.02f, 0.93f, 0.85f, 0.00030f) ||
line(u, v, 0.02f, 0.7f, 0.95f, 0.44f, 0.00025f))
return 0.04f;
return 0.46f + 0.03f * v;
}
// D4R_HARNESS_REPLAY_DIR=<dir> replays frames captured in a game by the shim
// (D4R_SHIM_INPUT_DUMP_DIR + D4R_SHIM_CAPTURE_COUNT): frame-NNNNNN-color.rgba16f,
// -depth.r32f, -motion.rg16f and -params.txt, starting at D4R_HARNESS_REPLAY_START.
struct ReplayFrame
{
std::vector<uint8_t> color, depth, motion;
float jitterX = 0, jitterY = 0, mvScaleX = 1, mvScaleY = 1, sharpness = 0;
float preExposure = 1, exposureScale = 1, frameTime = 16.666667f;
int reset = 0, hasExposure = 0;
unsigned renderWidth = 0, renderHeight = 0;
unsigned colorBaseX = 0, colorBaseY = 0, depthBaseX = 0, depthBaseY = 0, mvBaseX = 0, mvBaseY = 0;
};
static bool read_file(const std::string& path, std::vector<uint8_t>& bytes, size_t expected)
{
FILE* file = std::fopen(path.c_str(), "rb");
if (file == nullptr)
return false;
bytes.resize(expected);
const size_t got = std::fread(bytes.data(), 1, expected, file);
const bool extra = std::fgetc(file) != EOF;
std::fclose(file);
return got == expected && !extra;
}
static bool load_replay_frame(const char* directory, uint32_t frame, size_t colorBytes, size_t depthBytes,
size_t motionBytes, ReplayFrame& out)
{
char prefix[MAX_PATH];
std::snprintf(prefix, sizeof(prefix), "%s\\frame-%06u-", directory, frame);
const std::string base = prefix;
if (!read_file(base + "color.rgba16f", out.color, colorBytes) ||
!read_file(base + "depth.r32f", out.depth, depthBytes) ||
!read_file(base + "motion.rg16f", out.motion, motionBytes))
{
std::fprintf(stderr, "replay frame %u: missing or mis-sized plane under %s\n", frame, prefix);
return false;
}
FILE* file = std::fopen((base + "params.txt").c_str(), "r");
if (file == nullptr)
{
std::fprintf(stderr, "replay frame %u: no params\n", frame);
return false;
}
char key[64];
while (std::fscanf(file, "%63s", key) == 1)
{
const std::string name = key;
if (name == "jitter") std::fscanf(file, "%f %f", &out.jitterX, &out.jitterY);
else if (name == "mv_scale") std::fscanf(file, "%f %f", &out.mvScaleX, &out.mvScaleY);
else if (name == "sharpness") std::fscanf(file, "%f", &out.sharpness);
else if (name == "pre_exposure") std::fscanf(file, "%f", &out.preExposure);
else if (name == "exposure_scale") std::fscanf(file, "%f", &out.exposureScale);
else if (name == "frame_time") std::fscanf(file, "%f", &out.frameTime);
else if (name == "reset") std::fscanf(file, "%d", &out.reset);
else if (name == "has_exposure") std::fscanf(file, "%d", &out.hasExposure);
else if (name == "render") std::fscanf(file, "%u %u", &out.renderWidth, &out.renderHeight);
else if (name == "color_base") std::fscanf(file, "%u %u", &out.colorBaseX, &out.colorBaseY);
else if (name == "depth_base") std::fscanf(file, "%u %u", &out.depthBaseX, &out.depthBaseY);
else if (name == "mv_base") std::fscanf(file, "%u %u", &out.mvBaseX, &out.mvBaseY);
else std::fscanf(file, "%*[^\n]");
}
std::fclose(file);
return true;
}
static float halton(uint32_t index, uint32_t base)
{
float result = 0.0f, fraction = 1.0f;
for (; index > 0; index /= base)
{
fraction /= static_cast<float>(base);
result += fraction * static_cast<float>(index % base);
}
return result;
}
int main(int argc, char** argv)
{
trace_stage("entered main");
if (argc < 3)
{
std::fprintf(stderr, "usage: %s SHIM_DLL OUTPUT_RAW [FRAMES]\n", argv[0]);
return 2;
}
const int frames = argc > 3 ? std::atoi(argv[3]) : 6;
const UINT inWidth = argc > 7 ? static_cast<UINT>(std::atoi(argv[4])) : 640;
const UINT inHeight = argc > 7 ? static_cast<UINT>(std::atoi(argv[5])) : 360;
const UINT outWidth = argc > 7 ? static_cast<UINT>(std::atoi(argv[6])) : 1280;
const UINT outHeight = argc > 7 ? static_cast<UINT>(std::atoi(argv[7])) : 720;
const char* resourceWidthSetting = std::getenv("D4R_HARNESS_RESOURCE_WIDTH");
const UINT resourceWidth = resourceWidthSetting != nullptr
? static_cast<UINT>(std::atoi(resourceWidthSetting)) : inWidth;
if (resourceWidth < inWidth || resourceWidth > inWidth + 256)
{
std::fprintf(stderr, "Resource width must be between active width and active width + 256\n");
return 2;
}
const bool motionScene = std::getenv("D4R_HARNESS_MOTION_SCENE") != nullptr;
const bool jitterScene = std::getenv("D4R_HARNESS_JITTER_SCENE") != nullptr;
if (motionScene && jitterScene)
{
std::fprintf(stderr, "Choose one synthetic temporal scene at a time\n");
return 2;
}
const bool qualityScene = std::getenv("D4R_HARNESS_QUALITY_SCENE") != nullptr;
const bool poleScene = std::getenv("D4R_HARNESS_POLE_SCENE") != nullptr;
const bool exposureRGBA32 = std::getenv("D4R_HARNESS_EXPOSURE_RGBA32") != nullptr;
const char* replayDir = std::getenv("D4R_HARNESS_REPLAY_DIR");
const bool rgba8 = std::getenv("D4R_HARNESS_RGBA8") != nullptr;
if (rgba8 && (qualityScene || motionScene || jitterScene || replayDir != nullptr))
{
std::fprintf(stderr, "RGBA8 format probe requires the static synthetic scene\n");
return 2;
}
const uint32_t replayStart = std::getenv("D4R_HARNESS_REPLAY_START") != nullptr
? static_cast<uint32_t>(std::atoi(std::getenv("D4R_HARNESS_REPLAY_START"))) : 1;
const bool temporal = std::getenv("D4R_HARNESS_TEMPORAL") != nullptr || motionScene || jitterScene || qualityScene ||
replayDir != nullptr;
const bool forceReset = std::getenv("D4R_HARNESS_FORCE_RESET") != nullptr;
const bool saveFrames = std::getenv("D4R_HARNESS_SAVE_FRAMES") != nullptr;
const char* waitSetting = std::getenv("D4R_HARNESS_FRAME_WAIT_MS");
const int requestedWait = waitSetting != nullptr ? std::atoi(waitSetting) : 200;
const DWORD frameWaitMs = requestedWait >= 0 && requestedWait <= 10000 ? requestedWait : 200;
trace_stage(temporal ? "temporal history enabled" : "reset each frame");
HMODULE shim = nullptr;
if (std::getenv("D4R_HARNESS_LOAD_NGX_EARLY") != nullptr)
{
trace_stage("loading NGX DLL before D3D12 setup");
shim = LoadLibraryA(argv[1]);
trace_stage(shim != nullptr ? "early NGX DLL load succeeded" : "early NGX DLL load failed");
if (shim == nullptr)
return 1;
}
HMODULE d3d12 = LoadLibraryA("d3d12.dll");
auto createDevice = reinterpret_cast<HRESULT(WINAPI*)(IUnknown*, D3D_FEATURE_LEVEL, REFIID, void**)>(
reinterpret_cast<void*>(GetProcAddress(d3d12, "D3D12CreateDevice")));
if (createDevice == nullptr ||
!check(createDevice(nullptr, D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device), reinterpret_cast<void**>(&g_device)),
"D3D12CreateDevice"))
return 1;
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
check(g_device->CreateCommandQueue(&queueDesc, __uuidof(ID3D12CommandQueue), reinterpret_cast<void**>(&g_queue)),
"CreateCommandQueue");
check(g_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, __uuidof(ID3D12CommandAllocator),
reinterpret_cast<void**>(&g_allocator)),
"CreateCommandAllocator");
check(g_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_allocator, nullptr,
__uuidof(ID3D12GraphicsCommandList), reinterpret_cast<void**>(&g_list)),
"CreateCommandList");
check(g_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence), reinterpret_cast<void**>(&g_fence)),
"CreateFence");
g_event = CreateEventA(nullptr, FALSE, FALSE, nullptr);
std::printf("D3D12 device ready\n");
trace_stage("D3D12 device ready");
// Synthetic inputs, identical to ngx_cuda_probe.cpp.
std::vector<uint16_t> color(static_cast<size_t>(resourceWidth) * inHeight * 4);
constexpr uint16_t dark = 0x2a66u, mid = 0x3400u, bright = 0x3a66u, white = 0x3c00u;
for (UINT y = 0; y < inHeight; ++y)
for (UINT x = 0; x < inWidth; ++x)
{
const size_t pixel = static_cast<size_t>(y) * resourceWidth + x;
const bool fence = (x % 24u == 0u) || (y % 24u == 0u);
const bool checker = (((x / 4u) + (y / 4u)) & 1u) != 0;
color[pixel * 4 + 0] = fence ? white : (checker ? mid : dark);
color[pixel * 4 + 1] = ((x + y) % 32u < 2u) ? bright : (checker ? dark : mid);
color[pixel * 4 + 2] = (((x / 8u) ^ (y / 8u)) & 1u) ? bright : dark;
color[pixel * 4 + 3] = white;
}
for (UINT y = 0; y < inHeight; ++y)
for (UINT x = inWidth; x < resourceWidth; ++x)
{
const size_t pixel = (static_cast<size_t>(y) * resourceWidth + x) * 4;
color[pixel] = white;
color[pixel + 1] = color[pixel + 2] = 0;
color[pixel + 3] = white;
}
const std::vector<uint16_t> backgroundColor = color;
std::vector<float> depth(static_cast<size_t>(resourceWidth) * inHeight, 0.5f);
for (UINT y = 0; y < inHeight; ++y)
for (UINT x = inWidth; x < resourceWidth; ++x)
depth[static_cast<size_t>(y) * resourceWidth + x] = 0.0f;
// D4R_HARNESS_MV_HIRES=1 gives DLSS display-resolution motion vectors,
// what it expects without the MVLowRes create flag (as games pass them).
const bool motionHighRes = std::getenv("D4R_HARNESS_MV_HIRES") != nullptr;
const UINT motionWidth = motionHighRes ? outWidth : resourceWidth, motionHeight = motionHighRes ? outHeight : inHeight;
std::vector<uint16_t> motion(static_cast<size_t>(motionWidth) * motionHeight * 2, 0);
const float exposureValue = 1.0f;
const std::array<float, 4> exposureRGBA = {exposureValue, 0.25f, 0.5f, 0.75f};
std::vector<uint16_t> outputInit(static_cast<size_t>(outWidth) * outHeight * 4, 0);
std::vector<uint8_t> color8;
std::vector<uint8_t> outputInit8;
if (rgba8)
{
color8.resize(color.size());
for (size_t index = 0; index < color.size(); ++index)
color8[index] = static_cast<uint8_t>(std::lround(
std::clamp(static_cast<float>(std::bit_cast<_Float16>(color[index])), 0.0f, 1.0f) * 255.0f));
outputInit8.resize(static_cast<size_t>(outWidth) * outHeight * 4);
}
const auto srv = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
ID3D12Resource* colorTexture = create_texture(resourceWidth, inHeight,
rgba8 ? DXGI_FORMAT_R8G8B8A8_UNORM : DXGI_FORMAT_R16G16B16A16_FLOAT, D3D12_RESOURCE_FLAG_NONE);
ID3D12Resource* depthTexture = create_texture(resourceWidth, inHeight, DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE);
// D4R_HARNESS_MV_RGBA16=1: motion vectors in an R16G16B16A16_TYPELESS texture (as some games pass them), the
// unused components filled with junk that must not reach DLSS.
const bool mvRgba16 = std::getenv("D4R_HARNESS_MV_RGBA16") != nullptr && std::atoi(std::getenv("D4R_HARNESS_MV_RGBA16")) != 0;
ID3D12Resource* motionTexture = create_texture(motionWidth, motionHeight,
mvRgba16 ? DXGI_FORMAT_R16G16B16A16_TYPELESS : DXGI_FORMAT_R16G16_FLOAT, D3D12_RESOURCE_FLAG_NONE);
std::vector<uint16_t> motionWide;
auto motion_rows = [&](const uint16_t* rg) -> const void* {
if (!mvRgba16)
return rg;
const size_t pixels = static_cast<size_t>(motionWidth) * motionHeight;
motionWide.resize(pixels * 4);
for (size_t pixel = 0; pixel < pixels; ++pixel)
{
motionWide[pixel * 4 + 0] = rg[pixel * 2 + 0];
motionWide[pixel * 4 + 1] = rg[pixel * 2 + 1];
motionWide[pixel * 4 + 2] = 0x5640; // 100.0
motionWide[pixel * 4 + 3] = 0xd640; // -100.0
}
return motionWide.data();
};
const UINT motionRowBytes = motionWidth * (mvRgba16 ? 8 : 4);
ID3D12Resource* exposureTexture = create_texture(1, 1,
exposureRGBA32 ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE);
ID3D12Resource* outputTexture = create_texture(outWidth, outHeight,
rgba8 ? DXGI_FORMAT_R8G8B8A8_UNORM : DXGI_FORMAT_R16G16B16A16_FLOAT,
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
upload(colorTexture, rgba8 ? static_cast<const void*>(color8.data()) : static_cast<const void*>(color.data()),
resourceWidth * (rgba8 ? 4 : 8), srv);
upload(depthTexture, depth.data(), resourceWidth * 4, srv);
upload(motionTexture, motion_rows(motion.data()), motionRowBytes, srv);
upload(exposureTexture, exposureRGBA32 ? static_cast<const void*>(exposureRGBA.data())
: static_cast<const void*>(&exposureValue),
exposureRGBA32 ? 16 : 4, srv);
upload(outputTexture, rgba8 ? static_cast<const void*>(outputInit8.data()) : static_cast<const void*>(outputInit.data()),
outWidth * (rgba8 ? 4 : 8), D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
std::printf("synthetic inputs uploaded\n");
trace_stage("synthetic inputs uploaded");
if (shim == nullptr)
{
trace_stage("loading NGX DLL");
shim = LoadLibraryA(argv[1]);
trace_stage(shim != nullptr ? "NGX DLL loaded" : "NGX DLL load failed");
}
if (shim == nullptr)
{
std::printf("LoadLibraryA(%s) failed: %lu\n", argv[1], GetLastError());
return 1;
}
auto init = reinterpret_cast<PFN_Init_Ext>(reinterpret_cast<void*>(GetProcAddress(shim, "NVSDK_NGX_D3D12_Init_Ext")));
auto capability = reinterpret_cast<PFN_Parameters>(reinterpret_cast<void*>(GetProcAddress(shim, "NVSDK_NGX_D3D12_GetCapabilityParameters")));
auto allocate = reinterpret_cast<PFN_Parameters>(reinterpret_cast<void*>(GetProcAddress(shim, "NVSDK_NGX_D3D12_AllocateParameters")));
auto createFeature = reinterpret_cast<PFN_CreateFeature>(reinterpret_cast<void*>(GetProcAddress(shim, "NVSDK_NGX_D3D12_CreateFeature")));
auto evaluate = reinterpret_cast<PFN_Evaluate>(reinterpret_cast<void*>(GetProcAddress(shim, "NVSDK_NGX_D3D12_EvaluateFeature")));
auto release = reinterpret_cast<PFN_Release>(reinterpret_cast<void*>(GetProcAddress(shim, "NVSDK_NGX_D3D12_ReleaseFeature")));
auto shutdown = reinterpret_cast<PFN_Shutdown>(reinterpret_cast<void*>(GetProcAddress(shim, "NVSDK_NGX_D3D12_Shutdown")));
wchar_t dataPath[MAX_PATH];
GetTempPathW(MAX_PATH, dataPath);
const char* appIdSetting = std::getenv("D4R_HARNESS_APP_ID");
const unsigned long long appId = appIdSetting != nullptr ? std::strtoull(appIdSetting, nullptr, 10) : 241534723ULL;
NgxResult result = init(appId, dataPath, g_device, 0x15, nullptr);
trace_stage(result == NGX_SUCCESS ? "NGX init succeeded" : "NGX init failed");
std::printf("NVSDK_NGX_D3D12_Init_Ext -> 0x%08x\n", result);
if (result != NGX_SUCCESS)
return 1;
void* capabilities = nullptr;
result = capability(&capabilities);
int available = -1;
if (result == NGX_SUCCESS)
d4r_ngx_get_int(capabilities, "SuperSampling.Available", &available);
std::printf("GetCapabilityParameters -> 0x%08x, SuperSampling.Available=%d\n", result, available);
void* parameters = nullptr;
result = allocate(&parameters);
std::printf("AllocateParameters -> 0x%08x\n", result);
d4r_ngx_set_uint(parameters, "Width", inWidth);
d4r_ngx_set_uint(parameters, "Height", inHeight);
d4r_ngx_set_uint(parameters, "OutWidth", outWidth);
d4r_ngx_set_uint(parameters, "OutHeight", outHeight);
// D4R_HARNESS_QUALITY selects NVSDK_NGX_PerfQuality_Value (default 2,
// MaxQuality; 0 MaxPerf, 1 Balanced, 3 UltraPerformance, 5 DLAA).
const char* quality = std::getenv("D4R_HARNESS_QUALITY");
d4r_ngx_set_int(parameters, "PerfQualityValue", quality != nullptr && *quality != '\0' ? std::atoi(quality) : 2);
// D4R_HARNESS_CREATE_FLAGS mirrors a game's NVSDK_NGX_DLSS_Feature_Flags
// (e.g. 0x49 = HDR | DepthInverted | AutoExposure).
const char* createFlags = std::getenv("D4R_HARNESS_CREATE_FLAGS");
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)
return 1;
d4r_ngx_set_d3d12_resource(parameters, "Color", colorTexture);
d4r_ngx_set_d3d12_resource(parameters, "Depth", depthTexture);
d4r_ngx_set_d3d12_resource(parameters, "MotionVectors", motionTexture);
d4r_ngx_set_d3d12_resource(parameters, "Output", outputTexture);
d4r_ngx_set_d3d12_resource(parameters, "ExposureTexture", exposureTexture);
d4r_ngx_set_float(parameters, "Jitter.Offset.X", 0.0f);
d4r_ngx_set_float(parameters, "Jitter.Offset.Y", 0.0f);
d4r_ngx_set_float(parameters, "MV.Scale.X", 1.0f);
d4r_ngx_set_float(parameters, "MV.Scale.Y", 1.0f);
d4r_ngx_set_float(parameters, "Sharpness", 0.0f);
d4r_ngx_set_float(parameters, "DLSS.Pre.Exposure", 1.0f);
d4r_ngx_set_float(parameters, "DLSS.Exposure.Scale", 1.0f);
d4r_ngx_set_float(parameters, "FrameTimeDeltaInMsec", 16.666667f);
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Width", inWidth);
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Height", inHeight);
// Default to the proven single-frame reference. Temporal probes reset only
// the first frame and let the same official NGX feature accumulate history.
d4r_ngx_set_int(parameters, "Reset", 1);
// Quality scene ground truth (8x8 supersampled output pixels) and metrics.
// D4R_HARNESS_QUALITY_PAN="vx,vy" scrolls the scene content by whole
// output pixels per frame (motion vectors report it), so every frame's
// truth is an exact shift of one canvas that covers the whole scroll.
int panX = 0, panY = 0;
if (const char* pan = std::getenv("D4R_HARNESS_QUALITY_PAN"))
std::sscanf(pan, "%d,%d", &panX, &panY);
const int canvasX0 = std::min(0, -frames * panX), canvasY0 = std::min(0, -frames * panY);
const int canvasWidth = static_cast<int>(outWidth) + std::abs(frames * panX);
const int canvasHeight = static_cast<int>(outHeight) + std::abs(frames * panY);
std::vector<float> truth;
std::vector<float> previousError;
double flickerSum = 0.0;
int flickerFrames = 0;
if (qualityScene && !poleScene)
{
truth.resize(static_cast<size_t>(canvasWidth) * canvasHeight);
const int truthSamples = poleScene ? 2 : 8;
for (int y = 0; y < canvasHeight; ++y)
for (int x = 0; x < canvasWidth; ++x)
{
float sum = 0.0f;
for (int sy = 0; sy < truthSamples; ++sy)
for (int sx = 0; sx < truthSamples; ++sx)
sum += poleScene
? pole_scene(x + canvasX0 + (sx + 0.5f) / truthSamples,
y + canvasY0 + (sy + 0.5f) / truthSamples, outWidth, outHeight)
: quality_scene(x + canvasX0 + (sx + 0.5f) / truthSamples,
y + canvasY0 + (sy + 0.5f) / truthSamples, outWidth, outHeight);
truth[static_cast<size_t>(y) * canvasWidth + x] = sum / (truthSamples * truthSamples);
}
}
const float scaleX = static_cast<float>(outWidth) / inWidth, scaleY = static_cast<float>(outHeight) / inHeight;
const uint32_t phases = static_cast<uint32_t>(std::lround(8.0f * scaleX * scaleY));
for (int frame = 1; frame <= frames; ++frame)
{
if (qualityScene)
{
// Halton(2,3) jitter in render pixels, [-0.5, 0.5); a render pixel
// samples the scene at its centre plus the jitter.
const uint32_t index = static_cast<uint32_t>(frame - 1) % phases + 1;
const float jx = halton(index, 2) - 0.5f, jy = halton(index, 3) - 0.5f;
// DLSS's Jitter.Offset is minus the sample offset (verified: the other
// signs lose ~9 dB). D4R_HARNESS_JITTER_SIGNS="sx,sy" overrides it.
float signX = -1.0f, signY = -1.0f;
if (const char* signs = std::getenv("D4R_HARNESS_JITTER_SIGNS"))
std::sscanf(signs, "%f,%f", &signX, &signY);
// Scene content at frame f sits f * pan output pixels further along.
const float shiftX = static_cast<float>(frame * panX), shiftY = static_cast<float>(frame * panY);
for (UINT y = 0; y < inHeight; ++y)
for (UINT x = 0; x < inWidth; ++x)
{
const float sampleX = (x + 0.5f + jx) * scaleX - shiftX;
const float sampleY = (y + 0.5f + jy) * scaleY - shiftY;
const float value = poleScene
? pole_scene(sampleX, sampleY, outWidth, outHeight)
: quality_scene(sampleX, sampleY, outWidth, outHeight);
const size_t pixel = static_cast<size_t>(y) * resourceWidth + x;
const uint16_t half = std::bit_cast<uint16_t>(static_cast<_Float16>(value));
color[pixel * 4 + 0] = color[pixel * 4 + 1] = color[pixel * 4 + 2] = half;
color[pixel * 4 + 3] = 0x3c00u;
}
update_texture(colorTexture, color.data(), resourceWidth * 8, srv);
if (frame == 1 && (panX != 0 || panY != 0))
{
// Current position -> previous position. Without MVLowRes DLSS
// reads display-resolution vectors in output pixels (its input
// kernel adds them to the output pixel position); render-size
// vectors are in render pixels. D4R_HARNESS_MV_MULT scales them
// (convention checks).
const char* multiplier = std::getenv("D4R_HARNESS_MV_MULT");
const float mvScale = multiplier != nullptr ? std::strtof(multiplier, nullptr) : 1.0f;
const float unitX = motionHighRes ? 1.0f : 1.0f / scaleX, unitY = motionHighRes ? 1.0f : 1.0f / scaleY;
const uint16_t mvX = std::bit_cast<uint16_t>(static_cast<_Float16>(-mvScale * panX * unitX));
const uint16_t mvY = std::bit_cast<uint16_t>(static_cast<_Float16>(-mvScale * panY * unitY));
for (size_t pixel = 0; pixel < motion.size() / 2; ++pixel)
{
motion[pixel * 2 + 0] = mvX;
motion[pixel * 2 + 1] = mvY;
}
update_texture(motionTexture, motion_rows(motion.data()), motionRowBytes, srv);
}
d4r_ngx_set_float(parameters, "Jitter.Offset.X", signX * jx);
d4r_ngx_set_float(parameters, "Jitter.Offset.Y", signY * jy);
}
if (jitterScene)
{
constexpr std::array<float, 8> jitterX =
{0.0f, -0.25f, 0.25f, -0.375f, 0.125f, 0.375f, -0.125f, 0.0f};
constexpr std::array<float, 8> jitterY =
{0.0f, 0.25f, -0.25f, -0.125f, 0.375f, 0.125f, -0.375f, 0.0f};
const size_t phase = static_cast<size_t>(frame - 1) % jitterX.size();
render_jittered_background(color, inWidth, inHeight, resourceWidth, jitterX[phase], jitterY[phase]);
update_texture(colorTexture, color.data(), resourceWidth * 8, srv);
d4r_ngx_set_float(parameters, "Jitter.Offset.X", jitterX[phase]);
d4r_ngx_set_float(parameters, "Jitter.Offset.Y", jitterY[phase]);
}
if (motionScene)
{
color = backgroundColor;
std::fill(depth.begin(), depth.end(), 0.5f);
std::fill(motion.begin(), motion.end(), 0);
const UINT left = 220u + 4u * static_cast<UINT>(frame - 1);
for (UINT py = 100; py < 180 && py < inHeight; ++py)
for (UINT px = left; px < left + 80 && px < inWidth; ++px)
{
const size_t pixel = static_cast<size_t>(py) * resourceWidth + px;
color[pixel * 4 + 0] = white;
color[pixel * 4 + 1] = dark;
color[pixel * 4 + 2] = dark;
color[pixel * 4 + 3] = white;
depth[pixel] = 0.2f;
const size_t motionPixel = static_cast<size_t>(py) * motionWidth + px;
motion[motionPixel * 2 + 0] = 0xc400u; // -4 render pixels: current position -> previous
motion[motionPixel * 2 + 1] = 0;
}
update_texture(colorTexture, color.data(), resourceWidth * 8, srv);
update_texture(depthTexture, depth.data(), resourceWidth * 4, srv);
update_texture(motionTexture, motion_rows(motion.data()), motionRowBytes, srv);
}
if (replayDir != nullptr)
{
ReplayFrame replay;
if (!load_replay_frame(replayDir, replayStart + static_cast<uint32_t>(frame - 1),
color.size() * sizeof(uint16_t), depth.size() * sizeof(float),
motion.size() * sizeof(uint16_t), replay))
return 1;
update_texture(colorTexture, replay.color.data(), resourceWidth * 8, srv);
update_texture(depthTexture, replay.depth.data(), resourceWidth * 4, srv);
update_texture(motionTexture, motion_rows(reinterpret_cast<const uint16_t*>(replay.motion.data())), motionRowBytes, srv);
d4r_ngx_set_float(parameters, "Jitter.Offset.X", replay.jitterX);
d4r_ngx_set_float(parameters, "Jitter.Offset.Y", replay.jitterY);
d4r_ngx_set_float(parameters, "MV.Scale.X", replay.mvScaleX);
d4r_ngx_set_float(parameters, "MV.Scale.Y", replay.mvScaleY);
d4r_ngx_set_float(parameters, "Sharpness", replay.sharpness);
d4r_ngx_set_float(parameters, "DLSS.Pre.Exposure", replay.preExposure);
d4r_ngx_set_float(parameters, "DLSS.Exposure.Scale", replay.exposureScale);
d4r_ngx_set_float(parameters, "FrameTimeDeltaInMsec", replay.frameTime);
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Width", replay.renderWidth);
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Height", replay.renderHeight);
d4r_ngx_set_uint(parameters, "DLSS.Input.Color.Subrect.Base.X", replay.colorBaseX);
d4r_ngx_set_uint(parameters, "DLSS.Input.Color.Subrect.Base.Y", replay.colorBaseY);
d4r_ngx_set_uint(parameters, "DLSS.Input.Depth.Subrect.Base.X", replay.depthBaseX);
d4r_ngx_set_uint(parameters, "DLSS.Input.Depth.Subrect.Base.Y", replay.depthBaseY);
d4r_ngx_set_uint(parameters, "DLSS.Input.MV.Subrect.Base.X", replay.mvBaseX);
d4r_ngx_set_uint(parameters, "DLSS.Input.MV.Subrect.Base.Y", replay.mvBaseY);
d4r_ngx_set_int(parameters, "Reset", frame == 1 || replay.reset != 0 ? 1 : 0);
// Games with AutoExposure pass no exposure texture; the synthetic scenes pass 1.0.
d4r_ngx_set_d3d12_resource(parameters, "ExposureTexture", replay.hasExposure ? exposureTexture : nullptr);
}
else if (temporal)
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
if (qualityScene && !poleScene && frame > frames - 17)
{
// Luma of the presented result vs the ground truth at this frame's
// scroll position, and the frame-to-frame change of that error
// (ideally 0; for a static scene it is the change of the output).
const std::vector<uint8_t> raw = read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, outWidth * 8);
const uint16_t* halves = reinterpret_cast<const uint16_t*>(raw.data());
const size_t pixels = static_cast<size_t>(outWidth) * outHeight;
// The shim presents the previous evaluation's result (one frame of
// pipeline latency), so compare with the truth of that frame.
static const int presentedAge = std::getenv("D4R_HARNESS_QUALITY_AGE") != nullptr
? std::atoi(std::getenv("D4R_HARNESS_QUALITY_AGE")) : 1;
const int shownFrame = frame - presentedAge;
std::vector<float> error(pixels);
double squared = 0.0, blindsSquared = 0.0;
size_t blindsPixels = 0;
for (size_t pixel = 0; pixel < pixels; ++pixel)
{
float value = 0.0f;
for (int channel = 0; channel < 3; ++channel)
value += static_cast<float>(std::bit_cast<_Float16>(halves[pixel * 4 + channel]));
const float luma = std::clamp(value / 3.0f, 0.0f, 1.0f);
// Content coordinates: the screen pixel minus the scroll.
const int cx = static_cast<int>(pixel % outWidth) - shownFrame * panX;
const int cy = static_cast<int>(pixel / outWidth) - shownFrame * panY;
const float target = truth[static_cast<size_t>(cy - canvasY0) * canvasWidth + (cx - canvasX0)];
error[pixel] = luma - target;
squared += static_cast<double>(error[pixel]) * error[pixel];
const float u = static_cast<float>(cx) / outWidth;
const float v = static_cast<float>(cy) / outHeight;
if (u > 0.05f && u < 0.45f && v > 0.1f && v < 0.9f)
{
blindsSquared += static_cast<double>(error[pixel]) * error[pixel];
++blindsPixels;
}
}
double change = 0.0;
if (!previousError.empty())
{
for (size_t pixel = 0; pixel < pixels; ++pixel)
change += std::fabs(error[pixel] - previousError[pixel]);
change /= static_cast<double>(pixels);
flickerSum += change;
++flickerFrames;
}
previousError = error;
if (const char* dump = std::getenv("D4R_HARNESS_QUALITY_DUMP"))
{
// Every measured frame's raw RGBA16F output, for crops and videos.
char path[MAX_PATH];
std::snprintf(path, sizeof(path), "%s.%03d", dump, frame);
if (FILE* file = std::fopen(path, "wb"))
{
std::fwrite(raw.data(), 1, raw.size(), file);
std::fclose(file);
}
}
FILE* report = std::fopen(std::getenv("D4R_HARNESS_QUALITY_SCENE"), "a");
if (report == nullptr)
report = stdout;
std::fprintf(report, "QUALITY frame=%d psnr=%.2f blinds_psnr=%.2f flicker=%.5f\n", frame,
10.0 * std::log10(1.0 / (squared / pixels)),
10.0 * std::log10(1.0 / (blindsSquared / blindsPixels)), change);
if (frame == frames)
std::fprintf(report, "QUALITY summary psnr=%.2f blinds_psnr=%.2f mean_flicker=%.5f\n",
10.0 * std::log10(1.0 / (squared / pixels)),
10.0 * std::log10(1.0 / (blindsSquared / blindsPixels)),
flickerFrames != 0 ? flickerSum / flickerFrames : 0.0);
if (report != stdout)
std::fclose(report);
}
if (saveFrames)
{
const std::vector<uint8_t> frameOutput =
read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, outWidth * (rgba8 ? 4 : 8));
const std::string framePath = std::string(argv[2]) + ".frame" + std::to_string(frame);
if (FILE* frameFile = std::fopen(framePath.c_str(), "wb"))
{
std::fwrite(frameOutput.data(), 1, frameOutput.size(), frameFile);
std::fclose(frameFile);
}
}
}
const std::vector<uint8_t> output = read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
outWidth * (rgba8 ? 4 : 8));
FILE* file = std::fopen(argv[2], "wb");
if (file != nullptr)
{
std::fwrite(output.data(), 1, output.size(), file);
std::fclose(file);
}
size_t nonzero = 0;
for (uint8_t value : output)
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;
for (int cycle = 1; cycle <= recreateCycles; ++cycle)
{
release(feature);
feature = nullptr;
const UINT width = cycle % 2 != 0 ? inWidth * 10 / 13 : inWidth;
const UINT height = cycle % 2 != 0 ? inHeight * 10 / 13 : inHeight;
d4r_ngx_set_uint(parameters, "Width", width);
d4r_ngx_set_uint(parameters, "Height", height);
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Width", width);
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Height", height);
result = createFeature(g_list, 1, parameters, &feature);
if (result != NGX_SUCCESS)
{
std::printf("RECREATE cycle %d: CreateFeature -> 0x%08x\n", cycle, result);
return 1;
}
for (int frame = 0; frame < 4; ++frame)
{
d4r_ngx_set_int(parameters, "Reset", frame == 0 ? 1 : 0);
evaluate(g_list, feature, parameters, nullptr);
submit_and_wait();
Sleep(frameWaitMs);
}
const std::vector<uint8_t> cycleOutput = read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
outWidth * (rgba8 ? 4 : 8));
if (FILE* cycleFile = std::fopen((std::string(argv[2]) + ".cycle" + std::to_string(cycle)).c_str(), "wb"))
{
std::fwrite(cycleOutput.data(), 1, cycleOutput.size(), cycleFile);
std::fclose(cycleFile);
}
// Also to the quality report file, which survives when Proton drops stdout.
const char* reportPath = std::getenv("D4R_HARNESS_QUALITY_SCENE");
FILE* report = reportPath != nullptr ? std::fopen(reportPath, "a") : nullptr;
for (FILE* out : {stdout, report})
if (out != nullptr)
std::fprintf(out, "RECREATE cycle %d (%ux%u): process VRAM %llu KiB\n", cycle, width, height,
process_vram_kib());
if (report != nullptr)
std::fclose(report);
}
release(feature);
shutdown();
return 0;
}