148 lines
9.3 KiB
C++
148 lines
9.3 KiB
C++
#pragma once
|
|
#include "cuda_image_api.h"
|
|
#include <cmath>
|
|
#include <chrono>
|
|
#include <fstream>
|
|
|
|
extern "C" void d4r_ngx_set_uint(void*, const char*, unsigned);
|
|
extern "C" void d4r_ngx_set_int(void*, const char*, int);
|
|
extern "C" void d4r_ngx_set_float(void*, const char*, float);
|
|
extern "C" void d4r_ngx_set_ull(void*, const char*, unsigned long long);
|
|
extern "C" void d4r_ngx_set_void(void*, const char*, void*);
|
|
|
|
namespace d4r::diag {
|
|
inline float half_value(uint16_t bits) {
|
|
const unsigned exponent = (bits >> 10) & 31, mantissa = bits & 1023;
|
|
const float sign = bits & 0x8000 ? -1.0f : 1.0f;
|
|
if (exponent == 31) return mantissa ? NAN : sign * INFINITY;
|
|
if (!exponent) return sign * std::ldexp(float(mantissa), -24);
|
|
return sign * std::ldexp(float(1024 + mantissa), int(exponent) - 25);
|
|
}
|
|
inline void save_output(const std::filesystem::path& directory, const std::vector<uint16_t>& output,
|
|
unsigned width, unsigned height, unsigned frame) {
|
|
const auto stem = "output-" + std::to_string(frame);
|
|
std::ofstream raw(directory / (stem + ".rgba16f"), std::ios::binary);
|
|
raw.write(reinterpret_cast<const char*>(output.data()), std::streamsize(output.size() * 2));
|
|
// BMP is only a diagnostic preview; the raw file preserves unmodified HDR values.
|
|
const unsigned stride = (width * 3 + 3) & ~3u;
|
|
std::vector<unsigned char> bytes(54 + size_t(stride) * height, 0);
|
|
auto u32 = [&](size_t offset, uint32_t value) {
|
|
for (int j = 0; j < 4; ++j) bytes[offset + j] = static_cast<unsigned char>(value >> (8 * j));
|
|
};
|
|
bytes[0] = 'B'; bytes[1] = 'M'; u32(2, static_cast<uint32_t>(bytes.size())); u32(10, 54);
|
|
u32(14, 40); u32(18, width); u32(22, height); bytes[26] = 1; bytes[28] = 24;
|
|
for (unsigned y = 0; y < height; ++y) for (unsigned x = 0; x < width; ++x)
|
|
for (unsigned c = 0; c < 3; ++c) {
|
|
const float value = half_value(output[(size_t(y) * width + x) * 4 + c]);
|
|
bytes[54 + size_t(height - y - 1) * stride + x * 3 + (2 - c)] =
|
|
static_cast<unsigned char>(std::clamp(value, 0.0f, 1.0f) * 255.0f + 0.5f);
|
|
}
|
|
std::ofstream bmp(directory / (stem + ".bmp"), std::ios::binary);
|
|
bmp.write(reinterpret_cast<const char*>(bytes.data()), std::streamsize(bytes.size()));
|
|
if (!raw || !bmp) throw std::runtime_error("Saving DLSS output failed");
|
|
}
|
|
|
|
inline void run_synthetic_dlss(CudaApi& cuda, Library& core, void* parameters,
|
|
unsigned preset, unsigned frames, const std::filesystem::path& directory) {
|
|
// CPU upload/readback is confined to this synthetic diagnostic. The game
|
|
// backend uses external D3D12 memory and GPU copies instead.
|
|
constexpr unsigned width = 256, height = 144, outWidth = 512, outHeight = 288;
|
|
using Create = unsigned(*)(unsigned, void*, void**);
|
|
using Evaluate = unsigned(*)(void*, void*, void*);
|
|
using Release = unsigned(*)(void*);
|
|
const auto create = core.symbol<Create>("NVSDK_NGX_CUDA_CreateFeature");
|
|
const auto evaluate = core.symbol<Evaluate>("NVSDK_NGX_CUDA_EvaluateFeature");
|
|
const auto release = core.symbol<Release>("NVSDK_NGX_CUDA_ReleaseFeature");
|
|
auto ngx_check = [](unsigned result, const char* operation) {
|
|
std::printf("NGX %s -> 0x%08x\n", operation, result);
|
|
if (result != 1) throw std::runtime_error(std::string(operation) + " result=" + std::to_string(result));
|
|
};
|
|
d4r_ngx_set_uint(parameters, "Width", width);
|
|
d4r_ngx_set_uint(parameters, "Height", height);
|
|
d4r_ngx_set_uint(parameters, "OutWidth", outWidth);
|
|
d4r_ngx_set_uint(parameters, "OutHeight", outHeight);
|
|
d4r_ngx_set_int(parameters, "PerfQualityValue", 2);
|
|
d4r_ngx_set_int(parameters, "DLSS.Feature.Create.Flags", 0);
|
|
d4r_ngx_set_int(parameters, "DLSS.Enable.Output.Subrects", 0);
|
|
d4r_ngx_set_uint(parameters, "CreationNodeMask", 1);
|
|
d4r_ngx_set_uint(parameters, "VisibilityNodeMask", 1);
|
|
for (const char* name : {"DLSS.Hint.Render.Preset.DLAA", "DLSS.Hint.Render.Preset.Quality",
|
|
"DLSS.Hint.Render.Preset.Balanced", "DLSS.Hint.Render.Preset.Performance",
|
|
"DLSS.Hint.Render.Preset.UltraPerformance", "DLSS.Hint.Render.Preset.UltraQuality"})
|
|
d4r_ngx_set_uint(parameters, name, preset);
|
|
CUdeviceptr scratch = 0;
|
|
cuda.check(cuda.cuMemAlloc_v2(&scratch, 64ull * 1024 * 1024), "cuMemAlloc_v2(NGX scratch)");
|
|
struct ScratchCleanup { CudaApi& api; CUdeviceptr pointer; ~ScratchCleanup() { (void)api.cuMemFree_v2(pointer); } } sc{cuda, scratch};
|
|
d4r_ngx_set_void(parameters, "Scratch", reinterpret_cast<void*>(uintptr_t(scratch)));
|
|
d4r_ngx_set_ull(parameters, "Scratch.SizeInBytes", 64ull * 1024 * 1024);
|
|
void* handle = nullptr;
|
|
std::printf("STAGE NGX_CREATE preset=%u dimensions=%ux%u->%ux%u\n", preset, width, height, outWidth, outHeight);
|
|
ngx_check(create(1, parameters, &handle), "NVSDK_NGX_CUDA_CreateFeature");
|
|
if (!handle) throw std::runtime_error("NGX CreateFeature returned a null handle");
|
|
struct FeatureCleanup { Release release; void*& handle; ~FeatureCleanup() { if (handle) (void)release(handle); } } fc{release, handle};
|
|
cuda::ImageApi images(cuda);
|
|
cuda::Image color(images, width, height, 16, 4, false, 1);
|
|
cuda::Image depth(images, width, height, 32, 1, false);
|
|
cuda::Image motion(images, width, height, 16, 2, false);
|
|
cuda::Image exposure(images, 1, 1, 32, 1, false);
|
|
cuda::Image output(images, outWidth, outHeight, 16, 4, true);
|
|
std::vector<uint16_t> input(size_t(width) * height * 4), zeroMotion(size_t(width) * height * 2, 0);
|
|
std::vector<float> inputDepth(size_t(width) * height, 0.5f);
|
|
for (unsigned y = 0; y < height; ++y) for (unsigned x = 0; x < width; ++x) {
|
|
const size_t index = (size_t(y) * width + x) * 4;
|
|
input[index] = ((x / 16 + y / 16) & 1) ? 0x3a00 : 0x3400; // .75 / .25
|
|
input[index + 1] = (x & 32) ? 0x3800 : 0x3400;
|
|
input[index + 2] = (y & 32) ? 0x3a00 : 0x3800;
|
|
input[index + 3] = 0x3c00;
|
|
}
|
|
const float one = 1.0f;
|
|
color.upload(input.data()); depth.upload(inputDepth.data()); motion.upload(zeroMotion.data()); exposure.upload(&one);
|
|
d4r_ngx_set_void(parameters, "Color", &color.object);
|
|
d4r_ngx_set_void(parameters, "Depth", &depth.object);
|
|
d4r_ngx_set_void(parameters, "MotionVectors", &motion.object);
|
|
d4r_ngx_set_void(parameters, "ExposureTexture", &exposure.object);
|
|
d4r_ngx_set_void(parameters, "Output", &output.object);
|
|
for (const char* name : {"TransparencyMask", "DLSS.Input.Bias.Current.Color.Mask"})
|
|
d4r_ngx_set_void(parameters, name, nullptr);
|
|
for (const char* name : {"Jitter.Offset.X", "Jitter.Offset.Y", "Sharpness"})
|
|
d4r_ngx_set_float(parameters, name, 0.0f);
|
|
for (const char* name : {"MV.Scale.X", "MV.Scale.Y", "DLSS.Pre.Exposure", "DLSS.Exposure.Scale"})
|
|
d4r_ngx_set_float(parameters, name, 1.0f);
|
|
d4r_ngx_set_float(parameters, "FrameTimeDeltaInMsec", 16.666667f);
|
|
d4r_ngx_set_int(parameters, "Disable.Watermark", 1);
|
|
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Width", width);
|
|
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Height", height);
|
|
for (const char* name : {"DLSS.Input.Color.Subrect.Base.X", "DLSS.Input.Color.Subrect.Base.Y",
|
|
"DLSS.Input.Depth.Subrect.Base.X", "DLSS.Input.Depth.Subrect.Base.Y", "DLSS.Input.MV.Subrect.Base.X",
|
|
"DLSS.Input.MV.Subrect.Base.Y", "DLSS.Output.Subrect.Base.X", "DLSS.Output.Subrect.Base.Y"})
|
|
d4r_ngx_set_uint(parameters, name, 0);
|
|
std::vector<uint16_t> pixels(size_t(outWidth) * outHeight * 4, 0x7e00);
|
|
for (unsigned frame = 0; frame < frames; ++frame) {
|
|
std::fill(pixels.begin(), pixels.end(), 0x7e00); // unwritten output is an explicit failure
|
|
output.upload(pixels.data());
|
|
d4r_ngx_set_int(parameters, "Reset", frame == 0 ? 1 : 0);
|
|
std::printf("STAGE NGX_EVALUATE preset=%u frame=%u\n", preset, frame);
|
|
const auto begin = std::chrono::steady_clock::now();
|
|
ngx_check(evaluate(handle, parameters, nullptr), "NVSDK_NGX_CUDA_EvaluateFeature");
|
|
cuda.check(cuda.cuCtxSynchronize(), "cuCtxSynchronize(NGX Evaluate)");
|
|
const double milliseconds = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - begin).count();
|
|
output.download(pixels.data());
|
|
size_t nonfinite = 0; double sum = 0, square = 0;
|
|
for (size_t i = 0; i < pixels.size(); ++i) {
|
|
const float value = half_value(pixels[i]);
|
|
if (!std::isfinite(value)) ++nonfinite;
|
|
if ((i & 3) < 3) { sum += value; square += double(value) * value; }
|
|
}
|
|
const double count = outWidth * outHeight * 3;
|
|
const double variance = square / count - (sum / count) * (sum / count);
|
|
std::printf("NGX_OUTPUT preset=%u frame=%u nonfinite=%zu mean=%.9g variance=%.9g evaluate_ms=%.3f\n",
|
|
preset, frame, nonfinite, sum / count, variance, milliseconds);
|
|
if (nonfinite || !std::isfinite(variance) || variance < 1e-6)
|
|
throw std::runtime_error("DLSS output has NaN/Inf, unwritten pixels or no synthetic pattern");
|
|
save_output(directory, pixels, outWidth, outHeight, frame);
|
|
}
|
|
ngx_check(release(handle), "NVSDK_NGX_CUDA_ReleaseFeature"); handle = nullptr;
|
|
std::printf("PASS NGX_EVALUATE preset=%u frames=%u finite=1 network_validation=pending\n", preset, frames);
|
|
}
|
|
}
|