// Modified in this fork for CUDA Ray Reconstruction support and validation (2026). // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); void d4r_ngx_set_void(void* parameters, const char* name, void* 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 (*)(); struct NgxApplicationIdentifier { unsigned int type; union { unsigned long long applicationId; struct { const char* id; int engineType; const char* engineVersion; } project; } value; }; struct NgxFeatureDiscovery { unsigned int sdkVersion; unsigned int feature; NgxApplicationIdentifier identifier; const wchar_t* dataPath; const void* featureInfo; }; struct NgxFeatureRequirement { unsigned int supported; unsigned int minArchitecture; char minOsVersion[255]; }; using PFN_Requirements = NgxResult (*)(void*, const NgxFeatureDiscovery*, NgxFeatureRequirement*); 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 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& 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 refs_{1}; std::atomic& 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(&interop)), "lifetime interface")) return false; for (bool submitted : {false, true}) { std::atomic 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(&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(&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(&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(&mapped)); for (UINT y = 0; y < rows; ++y) std::memcpy(mapped + layout.Offset + static_cast(y) * layout.Footprint.RowPitch, static_cast(data) + static_cast(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 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 result(static_cast(rowBytes) * rows); uint8_t* mapped = nullptr; D3D12_RANGE range = {0, static_cast(total)}; staging->Map(0, &range, reinterpret_cast(&mapped)); for (UINT y = 0; y < rows; ++y) std::memcpy(result.data() + static_cast(y) * rowBytes, mapped + layout.Offset + static_cast(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& 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(y) * stride + x; for (size_t channel = 0; channel < 3; ++channel) color[pixel * 4 + channel] = std::bit_cast(static_cast<_Float16>(sum[channel] / 16.0f)); color[pixel * 4 + 3] = 0x3c00u; } } // Quality scene (D4R_HARNESS_QUALITY_SCENE=): 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= 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 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& 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(base); result += fraction * static_cast(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(std::atoi(argv[4])) : 640; const UINT inHeight = argc > 7 ? static_cast(std::atoi(argv[5])) : 360; const UINT outWidth = argc > 7 ? static_cast(std::atoi(argv[6])) : 1280; const UINT outHeight = argc > 7 ? static_cast(std::atoi(argv[7])) : 720; const char* resourceWidthSetting = std::getenv("D4R_HARNESS_RESOURCE_WIDTH"); const UINT resourceWidth = resourceWidthSetting != nullptr ? static_cast(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; // D4R_HARNESS_OUTPUT_RGB10A2=1: an R10G10B10A2_UNORM output texture (inputs unchanged), as some games pass const bool rgb10 = std::getenv("D4R_HARNESS_OUTPUT_RGB10A2") != nullptr; if (rgb10 && (rgba8 || std::getenv("D4R_HARNESS_QUALITY_SCENE") != nullptr)) { std::fprintf(stderr, "RGB10A2 output cannot be combined with RGBA8 or the quality scene\n"); return 2; } 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(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( reinterpret_cast(GetProcAddress(d3d12, "D3D12CreateDevice"))); if (createDevice == nullptr || !check(createDevice(nullptr, D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device), reinterpret_cast(&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(&g_queue)), "CreateCommandQueue"); check(g_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, __uuidof(ID3D12CommandAllocator), reinterpret_cast(&g_allocator)), "CreateCommandAllocator"); check(g_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_allocator, nullptr, __uuidof(ID3D12GraphicsCommandList), reinterpret_cast(&g_list)), "CreateCommandList"); check(g_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence), reinterpret_cast(&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 color(static_cast(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(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(y) * resourceWidth + x) * 4; color[pixel] = white; color[pixel + 1] = color[pixel + 2] = 0; color[pixel + 3] = white; } const std::vector backgroundColor = color; std::vector depth(static_cast(resourceWidth) * inHeight, 0.5f); for (UINT y = 0; y < inHeight; ++y) for (UINT x = inWidth; x < resourceWidth; ++x) depth[static_cast(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 motion(static_cast(motionWidth) * motionHeight * 2, 0); const float exposureValue = 1.0f; const std::array exposureRGBA = {exposureValue, 0.25f, 0.5f, 0.75f}; // D4R_HARNESS_EXPOSURE_VALUES=v1,v2,...: exposure texture values, one per frame. std::vector exposureSchedule; if (const char* values = std::getenv("D4R_HARNESS_EXPOSURE_VALUES")) for (const char* cursor = values;;) { char* end = nullptr; exposureSchedule.push_back(std::strtof(cursor, &end)); if (*end != ',') break; cursor = end + 1; } std::vector outputInit(static_cast(outWidth) * outHeight * 4, 0); std::vector color8; std::vector outputInit8; if (rgba8) { color8.resize(color.size()); for (size_t index = 0; index < color.size(); ++index) color8[index] = static_cast(std::lround( std::clamp(static_cast(std::bit_cast<_Float16>(color[index])), 0.0f, 1.0f) * 255.0f)); outputInit8.resize(static_cast(outWidth) * outHeight * 4); } const auto srv = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; const char* featureSetting = std::getenv("D4R_HARNESS_FEATURE_ID"); const unsigned int featureId = featureSetting ? static_cast(std::strtoul(featureSetting, nullptr, 0)) : 1; const bool reconstruction = featureId == 13; std::array guides{}; if (reconstruction) { // Planar synthetic scene: world-space +Z normals, rough diffuse material. // Distinct guides exercise denoising inputs rather than aliasing the noisy colour. std::vector guide(static_cast(resourceWidth) * inHeight * 4); const std::array, 4> values{{ {0.0f, 0.0f, 1.0f, 0.0f}, {0.5f, 0.0f, 0.0f, 0.0f}, {0.7f, 0.4f, 0.2f, 1.0f}, {0.04f, 0.04f, 0.04f, 1.0f}}}; for (size_t input = 0; input < guides.size(); ++input) { // Cyberpunk supplies UNORM8 albedos, while normals remain floating point. const bool unorm = input >= 2; guides[input] = create_texture(resourceWidth, inHeight, unorm ? DXGI_FORMAT_R8G8B8A8_UNORM : DXGI_FORMAT_R16G16B16A16_FLOAT, D3D12_RESOURCE_FLAG_NONE); if (!guides[input]) return 1; for (size_t pixel = 0; pixel < guide.size() / 4; ++pixel) for (size_t component = 0; component < 4; ++component) if (unorm) reinterpret_cast(guide.data())[pixel * 4 + component] = static_cast(std::lround(values[input][component] * 255.0f)); else guide[pixel * 4 + component] = std::bit_cast(static_cast<_Float16>(values[input][component])); upload(guides[input], guide.data(), resourceWidth * (unorm ? 4 : 8), srv); } } const bool alphaScenario = reconstruction && std::getenv("D4R_HARNESS_RR_ALPHA") != nullptr; const UINT alphaBaseX = 4, alphaBaseY = 2; const UINT alphaWidth = outWidth + 8, alphaHeight = outHeight + 4; ID3D12Resource* alphaInput = nullptr; ID3D12Resource* alphaOutput = nullptr; if (alphaScenario) { // A varying coverage guide and a padded destination expose dropped alpha writes and // copies that overwrite pixels outside the caller's nonzero output subrect. std::vector coverage(static_cast(resourceWidth) * inHeight); for (UINT y = 0; y < inHeight; ++y) for (UINT x = 0; x < resourceWidth; ++x) coverage[static_cast(y) * resourceWidth + x] = 0.25f + 0.5f * static_cast(x) / std::max(1u, resourceWidth - 1); alphaInput = create_texture(resourceWidth, inHeight, DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE); alphaOutput = create_texture(alphaWidth, alphaHeight, DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS); if (!alphaInput || !alphaOutput) return 1; upload(alphaInput, coverage.data(), resourceWidth * sizeof(float), srv); std::vector sentinel(static_cast(alphaWidth) * alphaHeight, -2.0f); upload(alphaOutput, sentinel.data(), alphaWidth * sizeof(float), D3D12_RESOURCE_STATE_UNORDERED_ACCESS); } 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 motionWide; auto motion_rows = [&](const uint16_t* rg) -> const void* { if (!mvRgba16) return rg; const size_t pixels = static_cast(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 : rgb10 ? DXGI_FORMAT_R10G10B10A2_UNORM : DXGI_FORMAT_R16G16B16A16_FLOAT, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS); upload(colorTexture, rgba8 ? static_cast(color8.data()) : static_cast(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(exposureRGBA.data()) : static_cast(&exposureValue), exposureRGBA32 ? 16 : 4, srv); const std::vector outputInit10(rgb10 ? static_cast(outWidth) * outHeight * 4 : 0); upload(outputTexture, rgba8 ? static_cast(outputInit8.data()) : rgb10 ? static_cast(outputInit10.data()) : static_cast(outputInit.data()), outWidth * (rgba8 || rgb10 ? 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(reinterpret_cast(GetProcAddress(shim, "NVSDK_NGX_D3D12_Init_Ext"))); auto capability = reinterpret_cast(reinterpret_cast(GetProcAddress(shim, "NVSDK_NGX_D3D12_GetCapabilityParameters"))); auto allocate = reinterpret_cast(reinterpret_cast(GetProcAddress(shim, "NVSDK_NGX_D3D12_AllocateParameters"))); auto createFeature = reinterpret_cast(reinterpret_cast(GetProcAddress(shim, "NVSDK_NGX_D3D12_CreateFeature"))); auto evaluate = reinterpret_cast(reinterpret_cast(GetProcAddress(shim, "NVSDK_NGX_D3D12_EvaluateFeature"))); auto release = reinterpret_cast(reinterpret_cast(GetProcAddress(shim, "NVSDK_NGX_D3D12_ReleaseFeature"))); auto shutdown = reinterpret_cast(reinterpret_cast(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; // Games discover RR before Init; an initialized-only capability answer hides the mode. auto requirements = reinterpret_cast( reinterpret_cast(GetProcAddress(shim, "NVSDK_NGX_D3D12_GetFeatureRequirements"))); NgxFeatureDiscovery discovery{}; discovery.sdkVersion = 0x15; discovery.feature = reconstruction ? 13 : 1; discovery.identifier.value.applicationId = appId; discovery.dataPath = dataPath; NgxFeatureRequirement requirement{}; using CreateFactoryFn = HRESULT (*)(REFIID, void**); const auto createFactory = reinterpret_cast( reinterpret_cast(GetProcAddress(LoadLibraryA("dxgi.dll"), "CreateDXGIFactory1"))); IDXGIFactory4* factory = nullptr; IDXGIAdapter* adapter = nullptr; LUID adapterLuid{}; g_device->GetAdapterLuid(&adapterLuid); if (FAILED(createFactory(__uuidof(IDXGIFactory4), reinterpret_cast(&factory))) || FAILED(factory->EnumAdapterByLuid(adapterLuid, __uuidof(IDXGIAdapter), reinterpret_cast(&adapter)))) return 1; const NgxResult discoveryResult = requirements(adapter, &discovery, &requirement); adapter->Release(); factory->Release(); std::printf("GetFeatureRequirements before Init -> 0x%08x, feature=%u supported=%u\n", discoveryResult, discovery.feature, requirement.supported); if (discoveryResult != NGX_SUCCESS || requirement.supported != 0) return 1; 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; const char* availabilityKey = reconstruction ? "SuperSamplingDenoising.Available" : "SuperSampling.Available"; if (result == NGX_SUCCESS) d4r_ngx_get_int(capabilities, availabilityKey, &available); std::printf("GetCapabilityParameters -> 0x%08x, %s=%d\n", result, availabilityKey, available); void* parameters = nullptr; result = allocate(¶meters); std::printf("AllocateParameters -> 0x%08x\n", result); // D4R_HARNESS_CREATE_SIZE=WxH creates the feature for another render size than the frames use, as // Streamline does for a frame or more after a quality change (PRAGMATA: 1280x720 feature, 854x480 frames). unsigned int createWidth = inWidth, createHeight = inHeight; if (const char* size = std::getenv("D4R_HARNESS_CREATE_SIZE")) std::sscanf(size, "%ux%u", &createWidth, &createHeight); d4r_ngx_set_uint(parameters, "Width", createWidth); d4r_ngx_set_uint(parameters, "Height", createHeight); 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). // Transformer RR requires HDR color and low-resolution motion vectors. const char* createFlags = std::getenv("D4R_HARNESS_CREATE_FLAGS"); d4r_ngx_set_int(parameters, "DLSS.Feature.Create.Flags", (createFlags != nullptr ? static_cast(std::strtol(createFlags, nullptr, 0)) : (reconstruction ? (motionHighRes ? 1 : 3) : 0)) | (alphaScenario ? 0x80 : 0)); if (alphaScenario) d4r_ngx_set_int(parameters, "DLSS.Enable.Output.Subrects", 1); std::array identity{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; if (reconstruction) { d4r_ngx_set_int(parameters, "DLSS.Denoise.Mode", 1); d4r_ngx_set_int(parameters, "DLSS.Roughness.Mode", 0); d4r_ngx_set_int(parameters, "DLSS.Use.HW.Depth", 0); d4r_ngx_set_void(parameters, "WorldToViewMatrix", identity.data()); d4r_ngx_set_void(parameters, "ViewToClipMatrix", identity.data()); const char* preset = std::getenv("D4R_HARNESS_RR_PRESET"); if (preset) for (const char* mode : {"DLAA", "Quality", "Balanced", "Performance", "UltraPerformance", "UltraQuality"}) d4r_ngx_set_uint(parameters, (std::string("RayReconstruction.Hint.Render.Preset.") + mode).c_str(), static_cast(std::strtoul(preset, nullptr, 0))); } NgxHandle* feature = nullptr; if (std::getenv("D4R_HARNESS_LIFETIME_TEST") != nullptr && !test_external_lifetime()) return 1; result = createFeature(g_list, featureId, parameters, &feature); std::printf("CreateFeature -> 0x%08x handle=%p\n", result, static_cast(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); if (reconstruction) { // The SDK's caller-facing names (nvsdk_ngx_defs.h: NVSDK_NGX_Parameter_GBuffer_Normals, // _Roughness, _DiffuseAlbedo, _SpecularAlbedo), which is what a game registers. The // shim republishes the two albedos under the names the denoiser reads. const char* names[] = {"GBuffer.Normals", "GBuffer.Roughness", "GBuffer.DiffuseAlbedo", "GBuffer.SpecularAlbedo"}; for (size_t input = 0; input < guides.size(); ++input) d4r_ngx_set_d3d12_resource(parameters, names[input], guides[input]); } if (alphaScenario) { d4r_ngx_set_d3d12_resource(parameters, "DLSSD.Alpha", alphaInput); d4r_ngx_set_d3d12_resource(parameters, "DLSSD.OutputAlpha", alphaOutput); d4r_ngx_set_uint(parameters, "DLSSD.OutputAlpha.Subrect.Base.X", alphaBaseX); d4r_ngx_set_uint(parameters, "DLSSD.OutputAlpha.Subrect.Base.Y", alphaBaseY); } 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); // D4R_HARNESS_INDICATOR_INVERT=1 sets NGX's DLSS.Indicator.Invert.X/Y.Axis, which games set to place the // DLSS debug indicator; compare against an unset run to see whether they reach the image. if (const char* invert = std::getenv("D4R_HARNESS_INDICATOR_INVERT"); invert != nullptr && std::atoi(invert) != 0) { d4r_ngx_set_int(parameters, "DLSS.Indicator.Invert.X.Axis", 1); d4r_ngx_set_int(parameters, "DLSS.Indicator.Invert.Y.Axis", 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(outWidth) + std::abs(frames * panX); const int canvasHeight = static_cast(outHeight) + std::abs(frames * panY); std::vector truth; std::vector previousError; double flickerSum = 0.0; int flickerFrames = 0; if (qualityScene && !poleScene) { truth.resize(static_cast(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(y) * canvasWidth + x] = sum / (truthSamples * truthSamples); } } const float scaleX = static_cast(outWidth) / inWidth, scaleY = static_cast(outHeight) / inHeight; const uint32_t phases = static_cast(std::lround(8.0f * scaleX * scaleY)); // A discarded recording followed by a submitted frame must not make the // discarded inputs appear ready, or contaminate the new feature's history. if (std::getenv("D4R_HARNESS_SKIP_RECORDED_INPUT") != nullptr) { if (evaluate(g_list, feature, parameters, nullptr) != NGX_SUCCESS || !check(g_list->Close(), "skip input list close") || !check(g_allocator->Reset(), "skip input allocator reset") || !check(g_list->Reset(g_allocator, nullptr), "skip input list reset")) return 1; lifecycle_report("SKIP input: discarded recording before submitting the next frame"); } for (int frame = 1; frame <= frames; ++frame) { // Recreate the game's output between completed frames to exercise imported-memory handle reuse. if (frame > 1 && std::getenv("D4R_HARNESS_RECREATE_OUTPUT") != nullptr) { outputTexture->Release(); outputTexture = create_texture(outWidth, outHeight, rgba8 ? DXGI_FORMAT_R8G8B8A8_UNORM : rgb10 ? DXGI_FORMAT_R10G10B10A2_UNORM : DXGI_FORMAT_R16G16B16A16_FLOAT, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS); upload(outputTexture, rgba8 ? static_cast(outputInit8.data()) : rgb10 ? static_cast(outputInit10.data()) : static_cast(outputInit.data()), outWidth * (rgba8 || rgb10 ? 4 : 8), D3D12_RESOURCE_STATE_UNORDERED_ACCESS); d4r_ngx_set_d3d12_resource(parameters, "Output", outputTexture); } 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(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(frame * panX), shiftY = static_cast(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(y) * resourceWidth + x; const uint16_t half = std::bit_cast(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; // D4R_HARNESS_MV_SCALE="sx,sy": the same motion expressed in other units, as games do // (e.g. "-640,-360": negated, normalised vectors); MV.Scale carries the factor back. float gameScaleX = 1.0f, gameScaleY = 1.0f; if (const char* gameScale = std::getenv("D4R_HARNESS_MV_SCALE")) std::sscanf(gameScale, "%f,%f", &gameScaleX, &gameScaleY); const uint16_t mvX = std::bit_cast(static_cast<_Float16>(-mvScale * panX * unitX / gameScaleX)); const uint16_t mvY = std::bit_cast(static_cast<_Float16>(-mvScale * panY * unitY / gameScaleY)); 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 (const char* gameScale = std::getenv("D4R_HARNESS_MV_SCALE")) { float gameScaleX = 1.0f, gameScaleY = 1.0f; std::sscanf(gameScale, "%f,%f", &gameScaleX, &gameScaleY); d4r_ngx_set_float(parameters, "MV.Scale.X", gameScaleX); d4r_ngx_set_float(parameters, "MV.Scale.Y", gameScaleY); } } if (jitterScene) { constexpr std::array jitterX = {0.0f, -0.25f, 0.25f, -0.375f, 0.125f, 0.375f, -0.125f, 0.0f}; constexpr std::array jitterY = {0.0f, 0.25f, -0.25f, -0.125f, 0.375f, 0.125f, -0.375f, 0.0f}; const size_t phase = static_cast(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(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(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(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(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(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); // D4R_HARNESS_PRE_EXPOSURE / D4R_HARNESS_EXPOSURE_SCALE: non-neutral values on every frame, as games // with their own exposure pipeline pass them. if (const char* value = std::getenv("D4R_HARNESS_PRE_EXPOSURE")) d4r_ngx_set_float(parameters, "DLSS.Pre.Exposure", static_cast(std::atof(value))); if (const char* value = std::getenv("D4R_HARNESS_EXPOSURE_SCALE")) d4r_ngx_set_float(parameters, "DLSS.Exposure.Scale", static_cast(std::atof(value))); // Change exposure after initial frames to exercise temporal/backend transitions. // CUDA supports this scale; the native K adapter currently accepts only a neutral scale. if (const char* changeFrame = std::getenv("D4R_HARNESS_EXPOSURE_CHANGE_FRAME")) if (frame == std::atoi(changeFrame)) d4r_ngx_set_float(parameters, "DLSS.Exposure.Scale", 0.5f); // D4R_HARNESS_EXPOSURE_VALUES: the exposure texture's value on each frame (the last value repeats). if (!exposureSchedule.empty()) { const float value = exposureSchedule[std::min(static_cast(frame - 1), exposureSchedule.size() - 1)]; const std::array rgba = {value, 0.25f, 0.5f, 0.75f}; update_texture(exposureTexture, exposureRGBA32 ? static_cast(rgba.data()) : static_cast(&value), exposureRGBA32 ? 16 : 4, srv); } const DWORD start = GetTickCount(); LARGE_INTEGER counterStart, counterRecorded, counterEnd, counterFrequency; QueryPerformanceFrequency(&counterFrequency); QueryPerformanceCounter(&counterStart); result = evaluate(g_list, feature, parameters, nullptr); QueryPerformanceCounter(&counterRecorded); if (const char* delay = std::getenv("D4R_HARNESS_DELAY_SUBMIT_MS")) Sleep(static_cast(std::clamp(std::atoi(delay), 0, 4000))); submit_and_wait(); QueryPerformanceCounter(&counterEnd); // The list holds only the evaluation, so the second figure is its whole cost when it is recorded into // the list (the engine backend): recording, submission, GPU execution and the fence. std::printf("frame %d: EvaluateFeature -> 0x%08x (%lu ms incl. submit; call %.0f us, call + submit + wait %.0f us)\n", frame, result, GetTickCount() - start, 1e6 * static_cast(counterRecorded.QuadPart - counterStart.QuadPart) / counterFrequency.QuadPart, 1e6 * static_cast(counterEnd.QuadPart - counterStart.QuadPart) / counterFrequency.QuadPart); // D4R_HARNESS_TIMING=: one "frame call_us total_us start_us" line per frame. if (const char* timingPath = std::getenv("D4R_HARNESS_TIMING")) if (FILE* timingFile = std::fopen(timingPath, "a")) { std::fprintf(timingFile, "%d %.1f %.1f %.1f\n", frame, 1e6 * static_cast(counterRecorded.QuadPart - counterStart.QuadPart) / counterFrequency.QuadPart, 1e6 * static_cast(counterEnd.QuadPart - counterStart.QuadPart) / counterFrequency.QuadPart, 1e6 * static_cast(counterStart.QuadPart) / counterFrequency.QuadPart); std::fclose(timingFile); } // NGX rejecting an evaluation outright is reported here; only a failure raised later, // on the shim's worker, is invisible in this return value and shows up in the image. if (result != NGX_SUCCESS) { std::fprintf(stderr, "EvaluateFeature rejected frame %d: 0x%08x\n", frame, result); return 1; } 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 raw = read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, outWidth * 8); const uint16_t* halves = reinterpret_cast(raw.data()); const size_t pixels = static_cast(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 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(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(pixel % outWidth) - shownFrame * panX; const int cy = static_cast(pixel / outWidth) - shownFrame * panY; const float target = truth[static_cast(cy - canvasY0) * canvasWidth + (cx - canvasX0)]; error[pixel] = luma - target; squared += static_cast(error[pixel]) * error[pixel]; const float u = static_cast(cx) / outWidth; const float v = static_cast(cy) / outHeight; if (u > 0.05f && u < 0.45f && v > 0.1f && v < 0.9f) { blindsSquared += static_cast(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(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 frameOutput = read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, outWidth * (rgba8 || rgb10 ? 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 output = read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, outWidth * (rgba8 || rgb10 ? 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]); // The shim queues each evaluation on its own worker, so a failure inside the denoiser is // invisible in this thread's EvaluateFeature return value: the frame is accepted and the // output texture is simply never written. The image is the only observable. On the static // scene the denoiser reconstructs, so the output must carry that scene's structure: // compare block-averaged luma against the same average of the input. Averaging first makes // this about scene structure rather than per-pixel detail a denoiser is free to change. An // untouched output buffer (the initial fill) has no variance and cannot correlate. // Temporal scenes move content between the input and the frame presented, and replay // frames come from a capture, so neither is checked here. if (reconstruction && !rgba8 && replayDir == nullptr && !motionScene && !jitterScene && !qualityScene) { const uint16_t* const halves = reinterpret_cast(output.data()); constexpr UINT block = 8; const UINT blocksX = (outWidth + block - 1) / block, blocksY = (outHeight + block - 1) / block; std::vector outBlocks, inBlocks; outBlocks.reserve(static_cast(blocksX) * blocksY); inBlocks.reserve(static_cast(blocksX) * blocksY); for (UINT by = 0; by < blocksY; ++by) for (UINT bx = 0; bx < blocksX; ++bx) { double outSum = 0.0, inSum = 0.0; size_t count = 0; for (UINT y = by * block; y < std::min(by * block + block, outHeight); ++y) for (UINT x = bx * block; x < std::min(bx * block + block, outWidth); ++x) { const size_t outPixel = (static_cast(y) * outWidth + x) * 4; // The same output block mapped back onto the render grid: the denoiser // upsamples exactly this scene, so no filter assumption is needed. const UINT sourceY = static_cast(static_cast(y) * inHeight / outHeight); const UINT sourceX = static_cast(static_cast(x) * inWidth / outWidth); const size_t inPixel = (static_cast(sourceY) * resourceWidth + sourceX) * 4; for (int channel = 0; channel < 3; ++channel) { outSum += static_cast(std::bit_cast<_Float16>(halves[outPixel + channel])); inSum += static_cast(std::bit_cast<_Float16>(color[inPixel + channel])); } ++count; } outBlocks.push_back(outSum / (3.0 * static_cast(count))); inBlocks.push_back(inSum / (3.0 * static_cast(count))); } const double n = static_cast(outBlocks.size()); double sumOut = 0.0, sumIn = 0.0, sumOutOut = 0.0, sumInIn = 0.0, sumProduct = 0.0; for (size_t index = 0; index < outBlocks.size(); ++index) { sumOut += outBlocks[index]; sumIn += inBlocks[index]; sumOutOut += outBlocks[index] * outBlocks[index]; sumInIn += inBlocks[index] * inBlocks[index]; sumProduct += outBlocks[index] * inBlocks[index]; } const double outVariance = sumOutOut - sumOut * sumOut / n; const double inVariance = sumInIn - sumIn * sumIn / n; const double correlation = (sumProduct - sumOut * sumIn / n) / (std::sqrt(outVariance) * std::sqrt(inVariance)); std::printf("RR output block correlation with the input scene: %.6f over %zu blocks " "(mean luma %.6f vs %.6f)\n", correlation, outBlocks.size(), sumOut / n, sumIn / n); // A scene-structure floor, not a quality comparison with NVIDIA output. Unwritten // output has zero variance and produces NaN here; nonfinite or unrelated output also // fails. Denoising may change individual pixels without losing the scene's structure. if (!(correlation >= 0.5)) { std::fprintf(stderr, "Ray Reconstruction produced no usable image: block correlation with the " "input scene is %.6f (unwritten, unrelated, or nonfinite output)\n", correlation); return 1; } } if (alphaScenario) { const std::vector raw = read_back(alphaOutput, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, alphaWidth * sizeof(float)); const float* values = reinterpret_cast(raw.data()); size_t invalid = 0, overwritten = 0, leftCount = 0, rightCount = 0; double left = 0, right = 0; for (UINT y = 0; y < alphaHeight; ++y) for (UINT x = 0; x < alphaWidth; ++x) { const float value = values[static_cast(y) * alphaWidth + x]; const bool inside = x >= alphaBaseX && x < alphaBaseX + outWidth && y >= alphaBaseY && y < alphaBaseY + outHeight; if (!inside) { overwritten += value != -2.0f; continue; } invalid += !std::isfinite(value) || value < -0.001f || value > 1.001f; const UINT localX = x - alphaBaseX; if (localX < outWidth / 4) { left += value; ++leftCount; } if (localX >= outWidth * 3 / 4) { right += value; ++rightCount; } } const double leftMean = leftCount ? left / leftCount : 0; const double rightMean = rightCount ? right / rightCount : 0; std::printf("RR alpha: invalid=%zu outside-overwritten=%zu left=%.6f right=%.6f\n", invalid, overwritten, leftMean, rightMean); if (FILE* alphaFile = std::fopen((std::string(argv[2]) + ".alpha.raw").c_str(), "wb")) { std::fwrite(raw.data(), 1, raw.size(), alphaFile); std::fclose(alphaFile); } if (invalid || overwritten || !leftCount || !rightCount || !(rightMean - leftMean > 0.1)) { std::fprintf(stderr, "RR alpha coverage or subrect preservation failed\n"); return 1; } } 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, featureId, 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; const bool verifyRecreation = std::getenv("D4R_HARNESS_VERIFY_RECREATION") != nullptr; std::vector recreationReference[2]; for (int cycle = 1; cycle <= recreateCycles; ++cycle) { if (release(feature) != NGX_SUCCESS) return 1; 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, featureId, 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); if (evaluate(g_list, feature, parameters, nullptr) != NGX_SUCCESS) return 1; submit_and_wait(); Sleep(frameWaitMs); } const std::vector cycleOutput = read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, outWidth * (rgba8 || rgb10 ? 4 : 8)); if (verifyRecreation) { auto& reference = recreationReference[cycle % 2]; if (reference.empty()) reference = cycleOutput; else if (reference != cycleOutput) { lifecycle_report("RECREATE coherence FAILED: identical inputs and render size changed output"); return 1; } } 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); } if (verifyRecreation && recreateCycles >= 4) lifecycle_report("RECREATE coherence: repeated render sizes match byte-for-byte"); release(feature); shutdown(); for (ID3D12Resource* guide : guides) if (guide) guide->Release(); if (alphaInput) alphaInput->Release(); if (alphaOutput) alphaOutput->Release(); return 0; }