d4r: official NVIDIA DLSS on AMD GPUs under Linux and Proton
This commit is contained in:
commit
118173ecd7
128 files changed
+31333
No files matched your search
@@ -0,0 +1,814 @@
|
||||
// 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>
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
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 char* replayDir = std::getenv("D4R_HARNESS_REPLAY_DIR");
|
||||
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;
|
||||
std::vector<uint16_t> outputInit(static_cast<size_t>(outWidth) * outHeight * 4, 0);
|
||||
|
||||
const auto srv = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
||||
ID3D12Resource* colorTexture = create_texture(resourceWidth, inHeight, DXGI_FORMAT_R16G16B16A16_FLOAT, D3D12_RESOURCE_FLAG_NONE);
|
||||
ID3D12Resource* depthTexture = create_texture(resourceWidth, inHeight, DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE);
|
||||
ID3D12Resource* motionTexture = create_texture(motionWidth, motionHeight, DXGI_FORMAT_R16G16_FLOAT, D3D12_RESOURCE_FLAG_NONE);
|
||||
ID3D12Resource* exposureTexture = create_texture(1, 1, DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE);
|
||||
ID3D12Resource* outputTexture = create_texture(outWidth, outHeight, DXGI_FORMAT_R16G16B16A16_FLOAT,
|
||||
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
|
||||
upload(colorTexture, color.data(), resourceWidth * 8, srv);
|
||||
upload(depthTexture, depth.data(), resourceWidth * 4, srv);
|
||||
upload(motionTexture, motion.data(), motionWidth * 4, srv);
|
||||
upload(exposureTexture, &exposureValue, 4, srv);
|
||||
upload(outputTexture, outputInit.data(), outWidth * 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);
|
||||
NgxResult result = init(241534723ULL, 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(¶meters);
|
||||
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;
|
||||
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.data(), motionWidth * 4, 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.data(), motionWidth * 4, 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, replay.motion.data(), motionWidth * 4, 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);
|
||||
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 * 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 * 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 (size_t index = 0; index < output.size(); index += 2)
|
||||
nonzero += (output[index] | output[index + 1]) != 0;
|
||||
std::printf("output read back: %zu of %zu halves nonzero, written to %s\n", nonzero, output.size() / 2, argv[2]);
|
||||
|
||||
release(feature);
|
||||
shutdown();
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user