Advance native Windows RR integration, kernel tooling and local packaging

Implement native NGX feature-13 RR through a LUID-matched D3D12/Vulkan
shared-memory and timeline-fence bridge. Add owned parameter snapshots,
GPU resource lifetime handling, inherited/enhanced barriers, optional
guides, sampled input formats and depth-plane support with stencil
preservation. Separate RR initialization from the SR CUDA runtime and
expose explicit backend/model selection through player configuration.

Add synthetic bridge, snapshot and NGX lifecycle harnesses covering
camera/history/reset, parameter mutation, delayed submission, early
release, capability gating and unsupported-input rejection.

Add exact gfx11 D/E model generation, verified caches, versioned local
deployment with configuration backups, and isolated portable model
tools with pinned Python, NumPy and glslang dependencies.

Extend Windows K benchmarking and private native-cache selection.
Fix LLVM 22 emitter/helper compatibility and preserve exact-output
validation for local output-kernel optimization.

Add native FSR RR provider diagnostics, local development package
refresh tooling, installer/checker updates, FidelityFX headers and
Windows parity documentation.

Keep NVIDIA binaries, derived models and private optimized kernels out
of distributable packages. Native RR evidence remains synthetic;
game validation, FSR RR dispatch, installer integration and Linux
performance parity remain incomplete.
This commit is contained in:
ayo committed 2026-10-16 12:53:26 -04:00
1 parent 3345fffcd3
commit 247fe1092d
60 files changed
+7168 -66

No files matched your search

+279
View File
@@ -0,0 +1,279 @@
// AMD FSR Ray Regeneration (FidelityFX denoiser 1.2) runtime access, shared by the NGX shim's
// FSR Ray Reconstruction backend and tools/fsr_rr_probe.cpp.
//
// The denoiser is AMD's signed amd_fidelityfx_denoiser_dx12.dll, loaded from an explicit path
// (never from a search path, never patched) and driven through its five FFX API exports. It
// records its compute work into the caller's D3D12 command list; nothing here involves CUDA,
// ZLUDA or NVIDIA's denoiser. Struct layouts come from AMD's MIT-licensed headers in
// third_party/fidelityfx (SDK 2.3.0 / FSR Ray Regeneration 1.2.0).
//
// Each stage reports separately (see FsrRrStage): a missing DLL, a missing export, a provider
// query that returns no denoiser for this device, a failed context creation (where shader
// pipelines are built) and a failed dispatch are different failures and are never folded together.
#pragma once
#include <windows.h>
#include <d3d12.h>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include "../third_party/fidelityfx/api/include/ffx_api.h"
#include "../third_party/fidelityfx/api/include/ffx_api_types.h"
#include "../third_party/fidelityfx/denoisers/include/ffx_denoiser.h"
// ffx_api_dx12.h's backend descriptor, declared here because that header's inline helpers call
// ID3D12Resource::GetDesc() by value, which does not compile with WIDL_EXPLICIT_AGGREGATE_RETURNS.
#ifndef FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12
#define FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12 FFX_API_MAKE_BACKEND_SUB_ID(FFX_API_BACKEND_ID_DX12, 0x02)
struct ffxCreateBackendDX12Desc
{
ffxCreateContextDescHeader header;
ID3D12Device* device;
};
#endif
static_assert(sizeof(ffxCreateBackendDX12Desc) == 24, "ffx_api_dx12.h layout");
// The 1.2 descriptors as the signed runtime reads them (x64, natural alignment, same as MSVC).
static_assert(sizeof(FfxApiResource) == 48 && sizeof(FfxApiDenoiserSignal) == 104, "ffx_api_types.h layout");
static_assert(sizeof(ffxCreateContextDescDenoiser) == 40, "ffx_denoiser.h 1.2 create layout");
static_assert(sizeof(ffxDispatchDescDenoiser) == 448 && offsetof(ffxDispatchDescDenoiser, view) == 296 &&
offsetof(ffxDispatchDescDenoiser, renderSize) == 432,
"ffx_denoiser.h 1.2 dispatch layout");
enum class FsrRrStage
{
NotLoaded, // nothing tried yet
DllMissing, // no file at the configured path
DllLoadFailed, // LoadLibrary failed (dependencies, architecture)
ExportsMissing, // not an FFX API DLL
NoProvider, // the runtime returned no denoiser version for this device
Ready, // a provider was enumerated for this device; contexts may still fail
};
inline const char* fsr_rr_stage_name(FsrRrStage stage)
{
switch (stage)
{
case FsrRrStage::NotLoaded: return "not loaded";
case FsrRrStage::DllMissing: return "denoiser DLL missing";
case FsrRrStage::DllLoadFailed: return "denoiser DLL failed to load";
case FsrRrStage::ExportsMissing: return "denoiser DLL lacks the FFX API exports";
case FsrRrStage::NoProvider: return "no FSR Ray Regeneration provider for this device";
case FsrRrStage::Ready: return "provider available";
}
return "?";
}
inline const char* fsr_rr_return_name(ffxReturnCode_t code)
{
switch (code)
{
case FFX_API_RETURN_OK: return "OK";
case FFX_API_RETURN_ERROR: return "ERROR";
case FFX_API_RETURN_ERROR_UNKNOWN_DESCTYPE: return "ERROR_UNKNOWN_DESCTYPE";
case FFX_API_RETURN_ERROR_RUNTIME_ERROR: return "ERROR_RUNTIME_ERROR";
case FFX_API_RETURN_NO_PROVIDER: return "NO_PROVIDER";
case FFX_API_RETURN_ERROR_MEMORY: return "ERROR_MEMORY";
case FFX_API_RETURN_ERROR_PARAMETER: return "ERROR_PARAMETER";
case FFX_API_RETURN_PROVIDER_NO_SUPPORT_NEW_DESCTYPE: return "PROVIDER_NO_SUPPORT_NEW_DESCTYPE";
}
return "unknown";
}
// The loaded runtime and the provider the device was offered. One per process: the DLL keeps
// global state (its debug callback), so it is loaded once and never unloaded while contexts exist.
struct FsrRrRuntime
{
HMODULE module = nullptr;
std::wstring path; // as configured
std::wstring mappedPath; // as the loader mapped it
PfnFfxCreateContext createContext = nullptr;
PfnFfxDestroyContext destroyContext = nullptr;
PfnFfxConfigure configure = nullptr;
PfnFfxQuery query = nullptr;
PfnFfxDispatch dispatch = nullptr;
FsrRrStage stage = FsrRrStage::NotLoaded;
ffxReturnCode_t queryResult = FFX_API_RETURN_OK;
std::vector<uint64_t> versionIds;
std::vector<std::string> versionNames;
uint64_t providerId = 0; // the provider contexts are created with (the first enumerated)
std::string providerName;
// Loads `dll` with its own directory searched for dependencies. Does not query providers.
bool load(const std::wstring& dll)
{
path = dll;
if (GetFileAttributesW(dll.c_str()) == INVALID_FILE_ATTRIBUTES)
{
stage = FsrRrStage::DllMissing;
return false;
}
module = LoadLibraryExW(dll.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
if (module == nullptr)
{
stage = FsrRrStage::DllLoadFailed;
return false;
}
wchar_t mapped[MAX_PATH] = {};
GetModuleFileNameW(module, mapped, MAX_PATH);
mappedPath = mapped;
createContext = reinterpret_cast<PfnFfxCreateContext>(reinterpret_cast<void*>(GetProcAddress(module, "ffxCreateContext")));
destroyContext = reinterpret_cast<PfnFfxDestroyContext>(reinterpret_cast<void*>(GetProcAddress(module, "ffxDestroyContext")));
configure = reinterpret_cast<PfnFfxConfigure>(reinterpret_cast<void*>(GetProcAddress(module, "ffxConfigure")));
query = reinterpret_cast<PfnFfxQuery>(reinterpret_cast<void*>(GetProcAddress(module, "ffxQuery")));
dispatch = reinterpret_cast<PfnFfxDispatch>(reinterpret_cast<void*>(GetProcAddress(module, "ffxDispatch")));
if (!createContext || !destroyContext || !configure || !query || !dispatch)
{
stage = FsrRrStage::ExportsMissing;
return false;
}
return true;
}
// Routes the runtime's own error/warning messages to `callback` (may be called on any thread).
ffxReturnCode_t set_message_callback(ffxApiMessage callback, uint32_t level)
{
ffxConfigureDescGlobalDebug desc = {};
desc.header.type = FFX_API_CONFIGURE_DESC_TYPE_GLOBALDEBUG;
desc.effectId = FFX_API_EFFECT_ID_DENOISER;
desc.fpMessage = callback;
desc.debugLevel = level;
return configure(nullptr, &desc.header);
}
// The two-call version enumeration AMD's sample uses, against the real device. Zero
// versions is the "no provider for this device" outcome, distinct from a query error.
bool query_providers(ID3D12Device* device)
{
versionIds.clear();
versionNames.clear();
uint64_t count = 0;
ffxQueryDescGetVersions desc = {};
desc.header.type = FFX_API_QUERY_DESC_TYPE_GET_VERSIONS;
desc.createDescType = FFX_API_EFFECT_ID_DENOISER;
desc.device = device;
desc.outputCount = &count;
queryResult = query(nullptr, &desc.header);
if (queryResult != FFX_API_RETURN_OK || count == 0)
{
stage = FsrRrStage::NoProvider;
return false;
}
versionIds.resize(count);
std::vector<const char*> names(count, nullptr);
desc.versionIds = versionIds.data();
desc.versionNames = names.data();
queryResult = query(nullptr, &desc.header);
if (queryResult != FFX_API_RETURN_OK || count == 0)
{
stage = FsrRrStage::NoProvider;
return false;
}
versionIds.resize(count);
for (uint64_t index = 0; index < count; ++index)
versionNames.emplace_back(names[index] != nullptr ? names[index] : "");
providerId = versionIds[0];
providerName = versionNames[0];
stage = FsrRrStage::Ready;
return true;
}
};
// GPU memory the denoiser would allocate for this configuration (total, aliasable).
inline ffxReturnCode_t fsr_rr_query_memory(FsrRrRuntime& runtime, ID3D12Device* device, uint32_t width,
uint32_t height, uint32_t signals, uint32_t flags,
FfxApiEffectMemoryUsage& usage)
{
ffxOverrideVersion version = {};
version.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION;
version.versionId = runtime.providerId;
ffxQueryDescDenoiserGetGPUMemoryUsage desc = {};
desc.header.type = FFX_API_QUERY_DESC_TYPE_DENOISER_GPU_MEMORY_USAGE;
desc.header.pNext = &version.header;
desc.device = device;
desc.maxRenderSize = {width, height};
desc.signalFlags = signals;
desc.flags = flags;
desc.gpuMemoryUsage = &usage;
return runtime.query(nullptr, &desc.header);
}
// Creates one denoiser context on the enumerated provider. The descriptor chain only has to live
// for the call: ffx_api.h requires pointers *inside* descriptors to outlive the context, and
// none of these carry any.
inline ffxReturnCode_t fsr_rr_create_context(FsrRrRuntime& runtime, ID3D12Device* device, uint32_t maxWidth,
uint32_t maxHeight, uint32_t signals, uint32_t flags,
ffxContext& context)
{
ffxCreateContextDescDenoiser desc = {};
desc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_DENOISER;
desc.version = FFX_DENOISER_VERSION;
desc.maxRenderSize = {maxWidth, maxHeight};
desc.signalFlags = signals;
desc.flags = flags;
ffxCreateBackendDX12Desc backend = {};
backend.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12;
backend.device = device;
ffxOverrideVersion version = {};
version.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION;
version.versionId = runtime.providerId;
desc.header.pNext = &backend.header;
backend.header.pNext = &version.header;
context = nullptr;
return runtime.createContext(&context, &desc.header, nullptr);
}
// Asks a live context which provider actually serves it (the runtime's own account, logged as
// the "actual backend").
inline std::string fsr_rr_context_provider(FsrRrRuntime& runtime, ffxContext& context, uint64_t* id = nullptr)
{
ffxQueryGetProviderVersion desc = {};
desc.header.type = FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION;
if (runtime.query(&context, &desc.header) != FFX_API_RETURN_OK || desc.versionName == nullptr)
return std::string();
if (id != nullptr)
*id = desc.versionId;
return desc.versionName;
}
// An FfxApiResource for a 2D texture whose description the caller already knows (the shim never
// calls GetDesc() by value; see above).
inline FfxApiResource fsr_rr_texture(ID3D12Resource* resource, const D3D12_RESOURCE_DESC& desc, uint32_t format,
uint32_t state)
{
FfxApiResource result = {};
result.resource = resource;
result.state = state;
if (resource == nullptr)
return result;
result.description.type = FFX_API_RESOURCE_TYPE_TEXTURE2D;
result.description.format = format;
result.description.width = static_cast<uint32_t>(desc.Width);
result.description.height = desc.Height;
result.description.depth = desc.DepthOrArraySize;
result.description.mipCount = desc.MipLevels;
result.description.usage = FFX_API_RESOURCE_USAGE_READ_ONLY;
if (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS)
result.description.usage |= FFX_API_RESOURCE_USAGE_UAV;
return result;
}
// The FFX surface formats the adapter's own intermediate textures use.
inline uint32_t fsr_rr_surface_format(DXGI_FORMAT format)
{
switch (format)
{
case DXGI_FORMAT_R32_FLOAT: return FFX_API_SURFACE_FORMAT_R32_FLOAT;
case DXGI_FORMAT_R16G16B16A16_FLOAT: return FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT;
case DXGI_FORMAT_R32G32B32A32_FLOAT: return FFX_API_SURFACE_FORMAT_R32G32B32A32_FLOAT;
case DXGI_FORMAT_R10G10B10A2_UNORM: return FFX_API_SURFACE_FORMAT_R10G10B10A2_UNORM;
case DXGI_FORMAT_R8G8B8A8_UNORM: return FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM;
case DXGI_FORMAT_R16_FLOAT: return FFX_API_SURFACE_FORMAT_R16_FLOAT;
case DXGI_FORMAT_R8_UNORM: return FFX_API_SURFACE_FORMAT_R8_UNORM;
default: return FFX_API_SURFACE_FORMAT_UNKNOWN;
}
}
+695
View File
@@ -0,0 +1,695 @@
// Standalone check that AMD's FSR Ray Regeneration runtime runs on this D3D12 device, separate from
// any game and from the NGX shim. It:
// 1. creates a D3D12 device and reports the adapter identity the runtime will see through DXGI,
// 2. loads the given amd_fidelityfx_denoiser_dx12.dll (unmodified, by path) and enumerates its
// denoiser providers against that device (FFX_API_QUERY_DESC_TYPE_GET_VERSIONS),
// 3. creates a context (this is where the runtime builds its shader pipelines) and asks it which
// provider it got,
// 4. dispatches a synthetic ray-traced scene with per-frame white noise: a static camera, then a
// moving one (with exact 2.5D motion vectors), then a camera cut with FFX_DENOISER_DISPATCH_RESET,
// 5. reads every output back and compares input and output against the noise-free signals, so a
// passthrough or a stale history cannot pass for denoising.
// Every stage prints one "stage=... result=..." line, and OUT_DIR/probe.json collects the lot.
//
// usage: fsr_rr_probe.exe DENOISER_DLL OUT_DIR [--size WxH] [--frames N] [--signals HEX] [--query-only]
// [--no-driver-extension]
// --no-driver-extension: do not load Proton's amdxc64.dll for an AMD GPU hidden as NVIDIA
// (see d4r_fsr_rr_identity.h); reproduces the "no provider" outcome
// --signals: FfxApiDenoiserSignalFlags of the radiance signals to run (default 0x36: direct and
// indirect diffuse and specular, AMD's four-signal configuration; 0x30 = indirect only)
#define WIDL_EXPLICIT_AGGREGATE_RETURNS
#include <windows.h>
#include <d3d12.h>
#include <dxgi1_4.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdarg>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include "d4r_fsr_rr.h"
#include "windows/fsr_rr_identity.h"
#include "fsr_rr_scene.h"
namespace
{
using namespace fsr_scene;
FILE* g_json = nullptr;
bool g_jsonFirst = true;
void json_raw(const char* format, ...)
{
if (g_json == nullptr)
return;
va_list args;
va_start(args, format);
std::vfprintf(g_json, format, args);
va_end(args);
}
// One top-level "key": value pair; value is preformatted JSON.
void json_field(const char* key, const std::string& value)
{
json_raw("%s\n \"%s\": %s", g_jsonFirst ? "" : ",", key, value.c_str());
g_jsonFirst = false;
}
std::string quoted(const std::string& text)
{
std::string out = "\"";
for (char c : text)
{
if (c == '"' || c == '\\')
out += '\\';
if (static_cast<unsigned char>(c) < 0x20)
continue;
out += c;
}
return out + "\"";
}
std::string narrow(const wchar_t* text)
{
char buffer[1024] = {};
WideCharToMultiByte(CP_UTF8, 0, text, -1, buffer, sizeof(buffer), nullptr, nullptr);
return buffer;
}
std::string format(const char* pattern, ...)
{
char buffer[2048];
va_list args;
va_start(args, pattern);
std::vsnprintf(buffer, sizeof(buffer), pattern, args);
va_end(args);
return buffer;
}
void stage(const char* name, bool ok, const std::string& detail)
{
std::printf("stage=%s result=%s %s\n", name, ok ? "ok" : "FAIL", detail.c_str());
std::fflush(stdout);
json_field((std::string("stage_") + name).c_str(),
format("{\"ok\": %s, \"detail\": %s}", ok ? "true" : "false", quoted(detail).c_str()));
}
std::vector<std::string> g_messages;
void ffx_message(uint32_t type, const wchar_t* message)
{
const std::string text = narrow(message);
std::printf("ffx %s: %s\n", type == FFX_API_MESSAGE_TYPE_WARNING ? "warning" : "error", text.c_str());
std::fflush(stdout);
if (g_messages.size() < 64)
g_messages.push_back(text);
}
// --- D3D12 plumbing -------------------------------------------------------------------
#define CHECK_HR(expr) \
do \
{ \
const HRESULT hr_ = (expr); \
if (FAILED(hr_)) \
{ \
std::printf("%s failed: 0x%08lx (line %d)\n", #expr, static_cast<unsigned long>(hr_), __LINE__); \
return false; \
} \
} while (0)
struct Texture
{
ID3D12Resource* resource = nullptr;
D3D12_RESOURCE_DESC desc = {};
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {};
UINT rows = 0;
UINT64 rowBytes = 0, total = 0;
D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COMMON;
};
ID3D12Device* g_device = nullptr;
bool make_texture(Texture& texture, uint32_t width, uint32_t height, DXGI_FORMAT format, bool uav,
D3D12_RESOURCE_STATES state)
{
texture.desc = {};
texture.desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
texture.desc.Width = width;
texture.desc.Height = height;
texture.desc.DepthOrArraySize = 1;
texture.desc.MipLevels = 1;
texture.desc.Format = format;
texture.desc.SampleDesc.Count = 1;
texture.desc.Flags = uav ? D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS : D3D12_RESOURCE_FLAG_NONE;
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = D3D12_HEAP_TYPE_DEFAULT;
CHECK_HR(g_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &texture.desc, state, nullptr,
IID_PPV_ARGS(&texture.resource)));
texture.state = state;
g_device->GetCopyableFootprints(&texture.desc, 0, 1, 0, &texture.footprint, &texture.rows, &texture.rowBytes,
&texture.total);
return true;
}
bool make_buffer(ID3D12Resource*& buffer, UINT64 bytes, D3D12_HEAP_TYPE type, uint8_t*& mapped)
{
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Width = bytes;
desc.Height = 1;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = type;
const D3D12_RESOURCE_STATES state =
type == D3D12_HEAP_TYPE_UPLOAD ? D3D12_RESOURCE_STATE_GENERIC_READ : D3D12_RESOURCE_STATE_COPY_DEST;
CHECK_HR(g_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, state, nullptr,
IID_PPV_ARGS(&buffer)));
CHECK_HR(buffer->Map(0, nullptr, reinterpret_cast<void**>(&mapped)));
return true;
}
void barrier(ID3D12GraphicsCommandList* list, Texture& texture, D3D12_RESOURCE_STATES after)
{
if (texture.state == after)
return;
D3D12_RESOURCE_BARRIER b = {};
b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
b.Transition.pResource = texture.resource;
b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
b.Transition.StateBefore = texture.state;
b.Transition.StateAfter = after;
list->ResourceBarrier(1, &b);
texture.state = after;
}
// Copies `texelBytes`-sized texels from `source` (tightly packed rows) into the upload buffer at
// `offset` with the texture's footprint, and records the copy.
void upload(ID3D12GraphicsCommandList* list, Texture& texture, ID3D12Resource* staging, uint8_t* mapped,
UINT64& offset, const void* source, size_t texelBytes)
{
offset = (offset + D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT - 1) & ~UINT64(D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT - 1);
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = texture.footprint;
footprint.Offset = offset;
const size_t rowBytes = texelBytes * texture.desc.Width;
for (UINT row = 0; row < texture.rows; ++row)
std::memcpy(mapped + offset + row * footprint.Footprint.RowPitch,
static_cast<const uint8_t*>(source) + row * rowBytes, rowBytes);
offset += texture.total;
barrier(list, texture, D3D12_RESOURCE_STATE_COPY_DEST);
D3D12_TEXTURE_COPY_LOCATION dst = {};
dst.pResource = texture.resource;
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
D3D12_TEXTURE_COPY_LOCATION src = {};
src.pResource = staging;
src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
src.PlacedFootprint = footprint;
list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
}
void readback(ID3D12GraphicsCommandList* list, Texture& texture, ID3D12Resource* buffer, UINT64 offset)
{
barrier(list, texture, D3D12_RESOURCE_STATE_COPY_SOURCE);
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = texture.footprint;
footprint.Offset = offset;
D3D12_TEXTURE_COPY_LOCATION dst = {};
dst.pResource = buffer;
dst.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
dst.PlacedFootprint = footprint;
D3D12_TEXTURE_COPY_LOCATION src = {};
src.pResource = texture.resource;
src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
}
// --- metrics and captures -------------------------------------------------------------
struct Error
{
double input = 0, output = 0, outputVsPrevious = 0, change = 0;
size_t nonFinite = 0;
};
Error measure(const Frame& frame, const Frame* previous, const uint8_t* rows, UINT pitch, uint32_t width,
uint32_t height, int signal)
{
Error e;
size_t n = 0;
for (uint32_t y = 2; y + 2 < height; ++y)
for (uint32_t x = 2; x + 2 < width; ++x)
{
const size_t i = static_cast<size_t>(y) * width + x;
const uint16_t* out = reinterpret_cast<const uint16_t*>(rows + static_cast<size_t>(y) * pitch) + x * 4;
for (int c = 0; c < 3; ++c)
{
const float o = from_half(out[c]);
if (!std::isfinite(o))
{
++e.nonFinite;
continue;
}
if (!frame.valid[i])
continue;
const float clean = frame.clean[signal][i * 3 + c];
const float in = from_half(frame.noisy[signal][i * 4 + c]);
e.input += (in - clean) * (in - clean);
e.output += (o - clean) * (o - clean);
e.change += std::fabs(o - in);
if (previous != nullptr && previous->valid[i])
{
const float old = previous->clean[signal][i * 3 + c];
e.outputVsPrevious += (o - old) * (o - old);
}
++n;
}
}
if (n > 0)
{
e.input = std::sqrt(e.input / n);
e.output = std::sqrt(e.output / n);
e.outputVsPrevious = std::sqrt(e.outputVsPrevious / n);
e.change /= n;
}
return e;
}
// Tonemapped composite (diffuse signals x diffuse albedo + specular signals x specular albedo).
void write_ppm(const std::string& path, uint32_t width, uint32_t height, const std::vector<V3>& radiance)
{
FILE* file = std::fopen(path.c_str(), "wb");
if (file == nullptr)
return;
std::fprintf(file, "P6\n%u %u\n255\n", width, height);
std::vector<uint8_t> row(width * 3);
for (uint32_t y = 0; y < height; ++y)
{
for (uint32_t x = 0; x < width; ++x)
{
const V3 c = radiance[static_cast<size_t>(y) * width + x];
const float rgb[3] = {c.x, c.y, c.z};
for (int k = 0; k < 3; ++k)
{
const float mapped = std::pow(std::max(0.0f, rgb[k]) / (1.0f + std::max(0.0f, rgb[k])), 1.0f / 2.2f);
row[x * 3 + k] = static_cast<uint8_t>(std::lround(saturate(mapped) * 255.0f));
}
}
std::fwrite(row.data(), 1, row.size(), file);
}
std::fclose(file);
}
} // namespace
int wmain(int argc, wchar_t** argv)
{
if (argc < 3)
{
std::printf("usage: fsr_rr_probe.exe DENOISER_DLL OUT_DIR [--size WxH] [--frames N] [--signals HEX] [--query-only]\n");
return 2;
}
const std::wstring dllPath = argv[1];
const std::string outDir = narrow(argv[2]);
uint32_t width = 960, height = 540, frames = 48, signals = 0x36;
bool queryOnly = false, driverExtension = true;
for (int i = 3; i < argc; ++i)
{
const std::wstring arg = argv[i];
if (arg == L"--size" && i + 1 < argc)
std::swscanf(argv[++i], L"%ux%u", &width, &height);
else if (arg == L"--frames" && i + 1 < argc)
frames = static_cast<uint32_t>(std::wcstoul(argv[++i], nullptr, 0));
else if (arg == L"--signals" && i + 1 < argc)
signals = static_cast<uint32_t>(std::wcstoul(argv[++i], nullptr, 16));
else if (arg == L"--query-only")
queryOnly = true;
else if (arg == L"--no-driver-extension")
driverExtension = false;
}
signals &= 0x36;
if (signals == 0 || frames < 12)
{
std::printf("need at least one radiance signal and 12 frames\n");
return 2;
}
CreateDirectoryA(outDir.c_str(), nullptr);
g_json = std::fopen((outDir + "\\probe.json").c_str(), "w");
json_raw("{");
json_field("denoiser_dll", quoted(narrow(dllPath.c_str())));
json_field("render_size", format("[%u, %u]", width, height));
json_field("signal_flags", format("%u", signals));
json_field("frames", format("%u", frames));
int exitCode = 1;
auto finish = [&](int code) {
json_raw("\n}\n");
if (g_json)
std::fclose(g_json);
g_json = nullptr;
return code;
};
// 1. Device, and the adapter identity DXGI reports (what the runtime can see).
IDXGIFactory4* factory = nullptr;
IDXGIAdapter1* adapter = nullptr;
if (FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&factory))) || FAILED(factory->EnumAdapters1(0, &adapter)))
{
stage("device", false, "no DXGI adapter");
return finish(1);
}
DXGI_ADAPTER_DESC1 adapterDesc = {};
adapter->GetDesc1(&adapterDesc);
if (FAILED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&g_device))))
{
stage("device", false, "D3D12CreateDevice failed");
return finish(1);
}
D3D12_FEATURE_DATA_SHADER_MODEL shaderModel = {D3D_SHADER_MODEL_6_6};
g_device->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel));
stage("device", true,
format("adapter=\"%s\" vendor=0x%04x device=0x%04x luid=%08lx:%08lx shader_model=0x%x",
narrow(adapterDesc.Description).c_str(), adapterDesc.VendorId, adapterDesc.DeviceId,
static_cast<unsigned long>(adapterDesc.AdapterLuid.HighPart),
static_cast<unsigned long>(adapterDesc.AdapterLuid.LowPart),
static_cast<unsigned>(shaderModel.HighestShaderModel)));
json_field("adapter", format("{\"name\": %s, \"vendor\": %u, \"device\": %u}",
quoted(narrow(adapterDesc.Description)).c_str(), adapterDesc.VendorId,
adapterDesc.DeviceId));
// 2. Runtime and provider.
FsrRrRuntime runtime;
if (!runtime.load(dllPath))
{
stage("load", false, format("%s (error %lu)", fsr_rr_stage_name(runtime.stage), GetLastError()));
return finish(1);
}
stage("load", true, "mapped=" + narrow(runtime.mappedPath.c_str()));
if (driverExtension)
{
std::string detail;
const bool loaded = d4r_fsr_rr_driver_extension(g_device, detail);
stage("driver_extension", loaded, detail);
}
const ffxReturnCode_t debug = runtime.set_message_callback(&ffx_message, FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_VERBOSE);
std::printf("ffx global debug callback: %s\n", fsr_rr_return_name(debug));
const bool provider = runtime.query_providers(g_device);
{
std::string list = "[";
for (size_t i = 0; i < runtime.versionIds.size(); ++i)
list += format("%s{\"id\": \"0x%016llx\", \"name\": %s}", i ? ", " : "",
static_cast<unsigned long long>(runtime.versionIds[i]),
quoted(runtime.versionNames[i]).c_str());
json_field("providers", list + "]");
}
stage("provider", provider,
provider ? format("count=%zu selected=0x%016llx name=\"%s\"", runtime.versionIds.size(),
static_cast<unsigned long long>(runtime.providerId), runtime.providerName.c_str())
: format("%s (query %s)", fsr_rr_stage_name(runtime.stage), fsr_rr_return_name(runtime.queryResult)));
if (!provider || queryOnly)
return finish(provider ? 0 : 1);
FfxApiEffectMemoryUsage memory = {};
const ffxReturnCode_t memoryResult = fsr_rr_query_memory(runtime, g_device, width, height, signals, 0, memory);
json_field("gpu_memory", format("{\"result\": %u, \"total_mb\": %.2f, \"aliasable_mb\": %.2f}", memoryResult,
memory.totalUsageInBytes / 1048576.0, memory.aliasableUsageInBytes / 1048576.0));
std::printf("gpu memory query: %s total=%.2f MB aliasable=%.2f MB\n", fsr_rr_return_name(memoryResult),
memory.totalUsageInBytes / 1048576.0, memory.aliasableUsageInBytes / 1048576.0);
// 3. Context (shader pipelines are created here: the slow and the most likely to fail part).
ffxContext context = nullptr;
const auto createStart = std::chrono::steady_clock::now();
const ffxReturnCode_t created = fsr_rr_create_context(runtime, g_device, width, height, signals, 0, context);
const double createMs =
std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - createStart).count();
json_field("context_create_ms", format("%.1f", createMs));
if (created != FFX_API_RETURN_OK)
{
stage("context", false, format("ffxCreateContext -> %s after %.1f ms", fsr_rr_return_name(created), createMs));
return finish(1);
}
uint64_t actualId = 0;
const std::string actual = fsr_rr_context_provider(runtime, context, &actualId);
stage("context", true, format("%.1f ms, provider in use 0x%016llx \"%s\"", createMs,
static_cast<unsigned long long>(actualId), actual.c_str()));
json_field("provider_in_use", quoted(actual));
// 4. Resources and frames.
ID3D12CommandQueue* queue = nullptr;
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
ID3D12CommandAllocator* allocator = nullptr;
ID3D12GraphicsCommandList* list = nullptr;
ID3D12Fence* fence = nullptr;
if (FAILED(g_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&queue))) ||
FAILED(g_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&allocator))) ||
FAILED(g_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator, nullptr, IID_PPV_ARGS(&list))) ||
FAILED(g_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence))))
{
stage("dispatch", false, "command objects");
return finish(1);
}
list->Close();
HANDLE event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
uint64_t fenceValue = 0;
const D3D12_RESOURCE_STATES readState =
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
Texture depth, motion, normals, diffuseAlbedo, specularAlbedo, input[kSignalCount], output[kSignalCount];
bool ok = make_texture(depth, width, height, DXGI_FORMAT_R32_FLOAT, false, readState) &&
make_texture(motion, width, height, DXGI_FORMAT_R16G16B16A16_FLOAT, false, readState) &&
make_texture(normals, width, height, DXGI_FORMAT_R10G10B10A2_UNORM, false, readState) &&
make_texture(diffuseAlbedo, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, false, readState) &&
make_texture(specularAlbedo, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, false, readState);
for (int s = 0; s < kSignalCount && ok; ++s)
ok = make_texture(input[s], width, height, DXGI_FORMAT_R16G16B16A16_FLOAT, false, readState) &&
make_texture(output[s], width, height, DXGI_FORMAT_R16G16B16A16_FLOAT, true,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
ID3D12Resource *staging = nullptr, *readbackBuffer = nullptr, *timestampBuffer = nullptr;
uint8_t *stagingMapped = nullptr, *readbackMapped = nullptr, *timestampMapped = nullptr;
const UINT64 planeBytes = output[0].total;
ok = ok && make_buffer(staging, 16 * (planeBytes + 65536), D3D12_HEAP_TYPE_UPLOAD, stagingMapped) &&
make_buffer(readbackBuffer, kSignalCount * planeBytes, D3D12_HEAP_TYPE_READBACK, readbackMapped) &&
make_buffer(timestampBuffer, 16, D3D12_HEAP_TYPE_READBACK, timestampMapped);
ID3D12QueryHeap* timestamps = nullptr;
D3D12_QUERY_HEAP_DESC queryDesc = {};
queryDesc.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP;
queryDesc.Count = 2;
ok = ok && SUCCEEDED(g_device->CreateQueryHeap(&queryDesc, IID_PPV_ARGS(&timestamps)));
UINT64 ticksPerSecond = 0;
queue->GetTimestampFrequency(&ticksPerSecond);
if (!ok)
{
stage("dispatch", false, "resource creation");
return finish(1);
}
const float aspect = static_cast<float>(width) / height;
const M4 projection = projection_matrix(1.0f, aspect, 0.1f, 1000.0f);
const uint32_t cutFrame = frames * 2 / 3, moveStart = frames / 3;
auto camera_at = [&](uint32_t f) -> Camera {
if (f >= cutFrame)
return {{3.5f, 1.6f, 1.5f}, {-0.5f, -0.6f, 7.5f}};
const float t = f >= moveStart ? static_cast<float>(f - moveStart) : 0.0f;
return {{-0.6f + 0.04f * t, 0.6f, -1.5f}, {0.0f + 0.02f * t, -0.1f, 6.5f}};
};
Frame frameData, previousFrame;
std::vector<double> gpuMs, cpuMs;
std::string frameLog = "[";
bool dispatchOk = true, converged = true, resetOk = true, finite = true, notPassthrough = true;
Camera previousCamera = camera_at(0);
for (uint32_t f = 0; f < frames; ++f)
{
const Camera camera = camera_at(f);
const bool reset = f == 0 || f == cutFrame;
std::swap(previousFrame, frameData);
render(frameData, width, height, camera, reset ? camera : previousCamera, projection, f);
allocator->Reset();
list->Reset(allocator, nullptr);
UINT64 offset = 0;
upload(list, depth, staging, stagingMapped, offset, frameData.depth.data(), 4);
upload(list, motion, staging, stagingMapped, offset, frameData.motion.data(), 8);
upload(list, normals, staging, stagingMapped, offset, frameData.normals.data(), 4);
upload(list, diffuseAlbedo, staging, stagingMapped, offset, frameData.diffuseAlbedo.data(), 4);
upload(list, specularAlbedo, staging, stagingMapped, offset, frameData.specularAlbedo.data(), 4);
for (int s = 0; s < kSignalCount; ++s)
if (signals & kSignalFlags[s])
upload(list, input[s], staging, stagingMapped, offset, frameData.noisy[s].data(), 8);
Texture* all[] = {&depth, &motion, &normals, &diffuseAlbedo, &specularAlbedo};
for (Texture* t : all)
barrier(list, *t, readState);
for (int s = 0; s < kSignalCount; ++s)
{
barrier(list, input[s], readState);
barrier(list, output[s], D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
}
ffxDispatchDescDenoiser desc = {};
desc.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER;
desc.commandList = list;
const uint32_t readFfx = FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ;
desc.linearDepth = fsr_rr_texture(depth.resource, depth.desc, FFX_API_SURFACE_FORMAT_R32_FLOAT, readFfx);
desc.motionVectors = fsr_rr_texture(motion.resource, motion.desc, FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT, readFfx);
desc.normals = fsr_rr_texture(normals.resource, normals.desc, FFX_API_SURFACE_FORMAT_R10G10B10A2_UNORM, readFfx);
desc.specularAlbedo = fsr_rr_texture(specularAlbedo.resource, specularAlbedo.desc, FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM, readFfx);
desc.diffuseAlbedo = fsr_rr_texture(diffuseAlbedo.resource, diffuseAlbedo.desc, FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM, readFfx);
desc.motionVectorScale = {1.0f, 1.0f, 1.0f};
desc.jitterOffsets = {0.0f, 0.0f};
const V3 delta = previousCamera.eye - camera.eye;
desc.cameraPositionDelta = reset ? FfxApiFloatCoords3D{0, 0, 0} : FfxApiFloatCoords3D{delta.x, delta.y, delta.z};
const M4 view = view_matrix(camera.eye, camera.target);
std::memcpy(&desc.view, &view, sizeof(desc.view));
std::memcpy(&desc.projection, &projection, sizeof(desc.projection));
desc.linearDepthBounds = {0.0f, 1000.0f};
desc.renderSize = {width, height};
desc.frameIndex = f;
desc.flags = reset ? FFX_DENOISER_DISPATCH_RESET : 0;
ffxDispatchDescDenoiserDirectDiffuse dd = {};
ffxDispatchDescDenoiserDirectSpecular ds = {};
ffxDispatchDescDenoiserIndirectDiffuse id = {};
ffxDispatchDescDenoiserIndirectSpecular is = {};
dd.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_DIFFUSE;
ds.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_SPECULAR;
id.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_DIFFUSE;
is.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_SPECULAR;
FfxApiDenoiserSignal* signalDescs[kSignalCount] = {&dd.signal, &ds.signal, &id.signal, &is.signal};
ffxApiHeader* headers[kSignalCount] = {&dd.header, &ds.header, &id.header, &is.header};
ffxApiHeader* tail = &desc.header;
for (int s = 0; s < kSignalCount; ++s)
{
if (!(signals & kSignalFlags[s]))
continue;
signalDescs[s]->input = fsr_rr_texture(input[s].resource, input[s].desc, FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT, readFfx);
signalDescs[s]->output = fsr_rr_texture(output[s].resource, output[s].desc, FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT,
FFX_API_RESOURCE_STATE_UNORDERED_ACCESS);
tail->pNext = headers[s];
tail = headers[s];
}
list->EndQuery(timestamps, D3D12_QUERY_TYPE_TIMESTAMP, 0);
const auto cpuStart = std::chrono::steady_clock::now();
const ffxReturnCode_t dispatched = runtime.dispatch(&context, &desc.header);
cpuMs.push_back(std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - cpuStart).count());
list->EndQuery(timestamps, D3D12_QUERY_TYPE_TIMESTAMP, 1);
list->ResolveQueryData(timestamps, D3D12_QUERY_TYPE_TIMESTAMP, 0, 2, timestampBuffer, 0);
if (dispatched != FFX_API_RETURN_OK)
{
stage("dispatch", false, format("frame %u: ffxDispatch -> %s", f, fsr_rr_return_name(dispatched)));
dispatchOk = false;
list->Close();
break;
}
for (int s = 0; s < kSignalCount; ++s)
if (signals & kSignalFlags[s])
readback(list, output[s], readbackBuffer, s * planeBytes);
if (FAILED(list->Close()))
{
stage("dispatch", false, format("frame %u: Close failed", f));
dispatchOk = false;
break;
}
ID3D12CommandList* lists[] = {list};
queue->ExecuteCommandLists(1, lists);
queue->Signal(fence, ++fenceValue);
fence->SetEventOnCompletion(fenceValue, event);
if (WaitForSingleObject(event, 20000) != WAIT_OBJECT_0)
{
stage("dispatch", false, format("frame %u: GPU did not finish within 20 s", f));
dispatchOk = false;
break;
}
const HRESULT removed = g_device->GetDeviceRemovedReason();
if (FAILED(removed))
{
stage("dispatch", false, format("frame %u: device removed 0x%08lx", f, static_cast<unsigned long>(removed)));
dispatchOk = false;
break;
}
uint64_t stamps[2];
std::memcpy(stamps, timestampMapped, sizeof(stamps));
const double gpu = ticksPerSecond ? (stamps[1] - stamps[0]) * 1000.0 / ticksPerSecond : -1.0;
gpuMs.push_back(gpu);
// 5. Compare against the clean signals.
frameLog += format("%s\n {\"frame\": %u, \"reset\": %s, \"gpu_ms\": %.3f, \"cpu_ms\": %.3f, \"signals\": {",
f ? "," : "", f, reset ? "true" : "false", gpu, cpuMs.back());
bool firstSignal = true;
std::vector<V3> composeOut(static_cast<size_t>(width) * height), composeIn(composeOut.size()),
composeClean(composeOut.size());
for (int s = 0; s < kSignalCount; ++s)
{
if (!(signals & kSignalFlags[s]))
continue;
const uint8_t* rows = readbackMapped + s * planeBytes;
const Error e = measure(frameData, f == cutFrame ? &previousFrame : nullptr, rows,
output[s].footprint.Footprint.RowPitch, width, height, s);
frameLog += format("%s\"%s\": {\"rmse_in\": %.5f, \"rmse_out\": %.5f, \"mean_abs_change\": %.5f, "
"\"rmse_out_vs_previous_scene\": %.5f, \"nonfinite\": %zu}",
firstSignal ? "" : ", ", kSignalNames[s], e.input, e.output, e.change,
e.outputVsPrevious, e.nonFinite);
firstSignal = false;
finite = finite && e.nonFinite == 0;
notPassthrough = notPassthrough && (f < 2 || e.change > 1e-3 * (e.input + 1e-6));
// Converged before the cut: at least 8 static or slowly moving frames into history.
if (f + 1 == cutFrame && !(e.output < 0.5 * e.input))
converged = false;
if (f == cutFrame && !(e.output < e.outputVsPrevious))
resetOk = false;
if (f + 1 == frames && !(e.output < 0.6 * e.input))
resetOk = false;
const bool diffuse = s == kDirectDiffuse || s == kIndirectDiffuse;
for (uint32_t y = 0; y < height; ++y)
for (uint32_t x = 0; x < width; ++x)
{
const size_t i = static_cast<size_t>(y) * width + x;
const V3 albedo = diffuse ? frameData.diffuseLinear[i] : frameData.specularLinear[i];
const uint16_t* o = reinterpret_cast<const uint16_t*>(rows + static_cast<size_t>(y) * output[s].footprint.Footprint.RowPitch) + x * 4;
composeOut[i] = composeOut[i] + mul(V3{from_half(o[0]), from_half(o[1]), from_half(o[2])}, albedo);
composeIn[i] = composeIn[i] + mul(V3{from_half(frameData.noisy[s][i * 4]), from_half(frameData.noisy[s][i * 4 + 1]),
from_half(frameData.noisy[s][i * 4 + 2])}, albedo);
composeClean[i] = composeClean[i] + mul(V3{frameData.clean[s][i * 3], frameData.clean[s][i * 3 + 1],
frameData.clean[s][i * 3 + 2]}, albedo);
}
}
frameLog += "}}";
if (f == 0 || f + 1 == moveStart || f + 1 == cutFrame || f == cutFrame || f == cutFrame + 1 || f + 1 == frames)
{
write_ppm(format("%s\\frame%03u_noisy.ppm", outDir.c_str(), f), width, height, composeIn);
write_ppm(format("%s\\frame%03u_denoised.ppm", outDir.c_str(), f), width, height, composeOut);
write_ppm(format("%s\\frame%03u_clean.ppm", outDir.c_str(), f), width, height, composeClean);
}
previousCamera = camera;
}
json_field("frame_log", frameLog + "\n ]");
if (dispatchOk)
{
std::vector<double> warm(gpuMs.begin() + std::min<size_t>(gpuMs.size(), 3), gpuMs.end());
std::sort(warm.begin(), warm.end());
const double median = warm.empty() ? -1 : warm[warm.size() / 2];
std::vector<double> cpuWarm(cpuMs.begin() + std::min<size_t>(cpuMs.size(), 3), cpuMs.end());
std::sort(cpuWarm.begin(), cpuWarm.end());
stage("dispatch", true, format("%u frames, GPU median %.3f ms (first frame %.3f ms), CPU record median %.3f ms",
frames, median, gpuMs.empty() ? -1.0 : gpuMs[0],
cpuWarm.empty() ? -1.0 : cpuWarm[cpuWarm.size() / 2]));
json_field("gpu_ms_median", format("%.4f", median));
stage("output_finite", finite, finite ? "no NaN/Inf in any output" : "non-finite output values");
stage("not_passthrough", notPassthrough, "outputs differ from the noisy inputs");
stage("denoised", converged, format("before the cut (frame %u) every signal's RMSE vs clean is < 0.5x the input's",
cutFrame - 1));
stage("reset", resetOk, format("frame %u (reset) is closer to the new scene than the old one; final frame < 0.6x input",
cutFrame));
exitCode = finite && notPassthrough && converged && resetOk ? 0 : 1;
}
std::string messages = "[";
for (size_t i = 0; i < g_messages.size(); ++i)
messages += (i ? ", " : "") + quoted(g_messages[i]);
json_field("ffx_messages", messages + "]");
runtime.destroyContext(&context, nullptr);
return finish(exitCode);
}
+515
View File
@@ -0,0 +1,515 @@
// The synthetic ray-traced scene shared by tools/fsr_rr_probe.cpp (AMD's denoiser alone) and
// tools/rr_backend_check.cpp (a game's NGX Ray Reconstruction request through the shim): a camera over
// a checkered floor, three spheres and a wall, with smooth noise-free lighting signals and one-sample
// white-noise versions of them, every guide the denoisers read, and exact 2.5D motion. Because the
// noise-free signals are known, outputs are compared against ground truth rather than another
// denoiser.
#pragma once
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <vector>
namespace fsr_scene
{
// --- small math -----------------------------------------------------------------
struct V3
{
float x, y, z;
};
inline V3 operator+(V3 a, V3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
inline V3 operator-(V3 a, V3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
inline V3 operator*(V3 a, float s) { return {a.x * s, a.y * s, a.z * s}; }
inline V3 mul(V3 a, V3 b) { return {a.x * b.x, a.y * b.y, a.z * b.z}; }
inline float dot(V3 a, V3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
inline V3 cross(V3 a, V3 b) { return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; }
inline V3 normalize(V3 a) { return a * (1.0f / std::sqrt(dot(a, a))); }
inline float saturate(float v) { return std::min(1.0f, std::max(0.0f, v)); }
inline uint16_t to_half(float value)
{
uint32_t bits;
std::memcpy(&bits, &value, 4);
const uint32_t sign = (bits >> 16) & 0x8000u;
int exponent = static_cast<int>((bits >> 23) & 0xff) - 127 + 15;
uint32_t mantissa = bits & 0x7fffffu;
if (((bits >> 23) & 0xff) == 0xff)
return static_cast<uint16_t>(sign | 0x7c00u | (mantissa ? 0x200u : 0u));
if (exponent >= 31)
return static_cast<uint16_t>(sign | 0x7bffu); // clamp to the largest finite half
if (exponent <= 0)
{
if (exponent < -10)
return static_cast<uint16_t>(sign);
mantissa |= 0x800000u;
const uint32_t shift = static_cast<uint32_t>(14 - exponent);
uint32_t half = mantissa >> shift;
if ((mantissa >> (shift - 1)) & 1u)
++half;
return static_cast<uint16_t>(sign | half);
}
uint32_t half = sign | (static_cast<uint32_t>(exponent) << 10) | (mantissa >> 13);
if (mantissa & 0x1000u)
++half;
return static_cast<uint16_t>(half);
}
inline float from_half(uint16_t half)
{
const uint32_t sign = (half & 0x8000u) << 16;
uint32_t exponent = (half >> 10) & 0x1fu;
uint32_t mantissa = half & 0x3ffu;
uint32_t bits;
if (exponent == 0)
{
if (mantissa == 0)
bits = sign;
else
{
exponent = 127 - 15 + 1;
while ((mantissa & 0x400u) == 0)
{
mantissa <<= 1;
--exponent;
}
bits = sign | (exponent << 23) | ((mantissa & 0x3ffu) << 13);
}
}
else if (exponent == 31)
bits = sign | 0x7f800000u | (mantissa << 13);
else
bits = sign | ((exponent - 15 + 127) << 23) | (mantissa << 13);
float value;
std::memcpy(&value, &bits, 4);
return value;
}
// Row-major, row-vector (v' = vM) 4x4, the convention ffx_denoiser.h asks for.
struct M4
{
float m[4][4];
};
inline M4 view_matrix(V3 eye, V3 target)
{
const V3 forward = normalize(target - eye);
const V3 right = normalize(cross({0, 1, 0}, forward));
const V3 up = cross(forward, right);
M4 v = {};
v.m[0][0] = right.x, v.m[0][1] = up.x, v.m[0][2] = forward.x;
v.m[1][0] = right.y, v.m[1][1] = up.y, v.m[1][2] = forward.y;
v.m[2][0] = right.z, v.m[2][1] = up.z, v.m[2][2] = forward.z;
v.m[3][0] = -dot(eye, right), v.m[3][1] = -dot(eye, up), v.m[3][2] = -dot(eye, forward), v.m[3][3] = 1;
return v;
}
inline M4 projection_matrix(float fovY, float aspect, float zNear, float zFar)
{
const float ys = 1.0f / std::tan(fovY * 0.5f);
M4 p = {};
p.m[0][0] = ys / aspect;
p.m[1][1] = ys;
p.m[2][2] = zFar / (zFar - zNear);
p.m[2][3] = 1;
p.m[3][2] = -zNear * zFar / (zFar - zNear);
return p;
}
inline void transform(const M4& m, V3 v, float out[4])
{
for (int c = 0; c < 4; ++c)
out[c] = v.x * m.m[0][c] + v.y * m.m[1][c] + v.z * m.m[2][c] + m.m[3][c];
}
// --- the synthetic scene ----------------------------------------------------------
struct Camera
{
V3 eye, target;
};
struct Sphere
{
V3 center;
float radius;
V3 base;
float roughness, metal;
};
inline const Sphere kSpheres[] = {
{{0.0f, 0.2f, 6.0f}, 1.2f, {0.8f, 0.3f, 0.2f}, 0.35f, 0.0f},
{{2.4f, -0.3f, 7.5f}, 0.7f, {0.95f, 0.9f, 0.8f}, 0.15f, 1.0f},
{{-2.2f, -0.4f, 5.0f}, 0.6f, {0.2f, 0.5f, 0.85f}, 0.6f, 0.0f},
};
inline constexpr float kGroundY = -1.0f;
inline constexpr float kWallZ = 14.0f;
// Optional near-mirror pond in the floor (rr_backend_check --water): the reflection is the traced scene, with
// guides as Alan Wake 2 hands them for its water (flat diffuse albedo 0.5, specular albedo ~0.96, roughness ~0.047).
inline bool g_water = false;
inline constexpr float kWaterRoughness = 0.047f;
inline constexpr float kWaterMinX = -4.0f, kWaterMaxX = 4.5f, kWaterMinZ = 0.5f, kWaterMaxZ = 4.8f;
inline const V3 kLight = normalize({0.45f, 0.9f, -0.35f});
struct Hit
{
float t = 1e30f;
V3 position{}, normal{}, base{};
float roughness = 1, metal = 0;
bool valid = false, water = false;
};
inline bool hit_sphere(V3 origin, V3 direction, const Sphere& s, float& t)
{
const V3 oc = origin - s.center;
const float b = dot(oc, direction);
const float c = dot(oc, oc) - s.radius * s.radius;
const float h = b * b - c;
if (h < 0)
return false;
const float root = -b - std::sqrt(h);
if (root <= 1e-3f)
return false;
t = root;
return true;
}
inline Hit trace(V3 origin, V3 direction)
{
Hit hit;
float t;
for (const Sphere& s : kSpheres)
if (hit_sphere(origin, direction, s, t) && t < hit.t)
{
hit.t = t;
hit.position = origin + direction * t;
hit.normal = normalize(hit.position - s.center);
hit.base = s.base;
hit.roughness = s.roughness;
hit.metal = s.metal;
hit.valid = true;
}
if (direction.y < -1e-4f)
{
t = (kGroundY - origin.y) / direction.y;
if (t > 1e-3f && t < hit.t)
{
hit.t = t;
hit.position = origin + direction * t;
hit.normal = {0, 1, 0};
const int checker = (static_cast<int>(std::floor(hit.position.x)) + static_cast<int>(std::floor(hit.position.z))) & 1;
hit.base = checker ? V3{0.75f, 0.75f, 0.72f} : V3{0.22f, 0.24f, 0.25f};
hit.roughness = 0.55f;
hit.metal = 0;
hit.valid = true;
const V3 q = hit.position;
if (g_water && q.x > kWaterMinX && q.x < kWaterMaxX && q.z > kWaterMinZ && q.z < kWaterMaxZ)
{
// small world-space ripples, so the reflection has fine detail a denoiser can lose
hit.normal = normalize({0.035f * std::sin(5.1f * q.x + 1.3f * q.z) + 0.02f * std::sin(11.7f * q.z),
1.0f,
0.035f * std::cos(4.3f * q.z - 0.7f * q.x) + 0.02f * std::cos(13.1f * q.x)});
hit.base = {0.5f, 0.5f, 0.5f};
hit.roughness = kWaterRoughness;
hit.water = true;
}
}
}
if (direction.z > 1e-4f)
{
t = (kWallZ - origin.z) / direction.z;
if (t > 1e-3f && t < hit.t)
{
hit.t = t;
hit.position = origin + direction * t;
hit.normal = {0, 0, -1};
hit.base = {0.55f, 0.5f, 0.45f};
hit.roughness = 0.85f;
hit.metal = 0;
hit.valid = true;
}
}
return hit;
}
inline bool shadowed(V3 position, V3 normal)
{
float t;
const V3 origin = position + normal * 2e-3f;
for (const Sphere& s : kSpheres)
if (hit_sphere(origin, kLight, s, t))
return true;
return false;
}
// AMD's ApproximateBRDF (denoising.md 5.5.2, after Ray Tracing Gems ch. 32).
inline V3 approximate_brdf(V3 f0, float alpha, float nov)
{
nov = std::fabs(nov);
const float x[4] = {1, nov, nov * nov, nov * nov * nov};
const float y[4] = {1, alpha, alpha * alpha, alpha * alpha * alpha};
const float m1[2][2] = {{0.99044f, -1.28514f}, {1.29678f, -0.755907f}};
const float m2[3][3] = {{1.0f, 2.92338f, 59.4188f}, {20.3225f, -27.0302f, 222.592f}, {121.563f, 626.13f, 316.627f}};
const float m3[2][2] = {{0.0365463f, 3.32707f}, {9.0632f, -9.04756f}};
const float m4[3][3] = {{1.0f, 3.59685f, -1.36772f}, {9.04401f, -16.3174f, 9.22949f}, {5.56589f, 19.7886f, -20.2123f}};
// HLSL mul(M, v) with M row-major: (M v)_i = sum_j M[i][j] v_j
const float a0 = m1[0][0] * x[0] + m1[0][1] * x[1], a1 = m1[1][0] * x[0] + m1[1][1] * x[1];
const float xw[3] = {x[0], x[1], x[3]}, yw[3] = {y[0], y[1], y[3]};
float b[3];
for (int i = 0; i < 3; ++i)
b[i] = m2[i][0] * xw[0] + m2[i][1] * xw[1] + m2[i][2] * xw[2];
const float c0 = m3[0][0] * x[0] + m3[0][1] * x[1], c1 = m3[1][0] * x[0] + m3[1][1] * x[1];
const float xz[3] = {x[0], x[2], x[3]};
float d[3];
for (int i = 0; i < 3; ++i)
d[i] = m4[i][0] * xz[0] + m4[i][1] * xz[1] + m4[i][2] * xz[2];
float bias = (a0 * y[0] + a1 * y[1]) / (b[0] * yw[0] + b[1] * yw[1] + b[2] * yw[2]);
const float scale = (c0 * y[0] + c1 * y[1]) / (d[0] * yw[0] + d[1] * yw[1] + d[2] * yw[2]);
bias *= saturate(f0.y * 50);
const float s = std::max(0.0f, scale), bb = std::max(0.0f, bias);
return {f0.x * s + bb, f0.y * s + bb, f0.z * s + bb};
}
inline V3 reflect_dir(V3 direction, V3 normal) { return direction - normal * (2 * dot(direction, normal)); }
inline V3 environment(V3 direction)
{
const float up = saturate(direction.y * 0.5f + 0.5f);
return V3{0.35f, 0.45f, 0.6f} * (0.4f + 1.2f * up);
}
inline uint32_t hash32(uint32_t x)
{
// lowbias32, as AMD's guide recommends for white noise
x ^= x >> 16;
x *= 0x7feb352du;
x ^= x >> 15;
x *= 0x846ca68bu;
x ^= x >> 16;
return x;
}
inline float uniform(uint32_t pixel, uint32_t frame, uint32_t stream)
{
return (hash32(pixel * 0x9e3779b9u ^ hash32(frame * 4u + stream + 0x1234567u)) >> 8) * (1.0f / 16777216.0f);
}
enum Signal
{
kDirectDiffuse,
kDirectSpecular,
kIndirectDiffuse,
kIndirectSpecular,
kSignalCount
};
// FfxApiDenoiserSignalFlags of each signal (DIRECT_DIFFUSE 2, DIRECT_SPECULAR 4, INDIRECT_DIFFUSE 16,
// INDIRECT_SPECULAR 32)
inline const uint32_t kSignalFlags[kSignalCount] = {1u << 1, 1u << 2, 1u << 4, 1u << 5};
inline const char* const kSignalNames[kSignalCount] = {"direct_diffuse", "direct_specular", "indirect_diffuse",
"indirect_specular"};
struct Frame
{
// FSR Ray Regeneration's own inputs (fsr_rr_probe)
std::vector<float> depth; // signed linear (view z)
std::vector<uint16_t> motion; // RGBA16F: prevUV - curUV, prev z - cur z
std::vector<uint32_t> normals; // R10G10B10A2: oct normal, roughness, material 0
std::vector<uint32_t> diffuseAlbedo, specularAlbedo; // RGBA8, sqrt encoded
std::vector<float> clean[kSignalCount]; // RGB
std::vector<uint16_t> noisy[kSignalCount]; // RGBA16F
std::vector<uint8_t> valid;
std::vector<V3> diffuseLinear, specularLinear;
// What a DLSS Ray Reconstruction game hands NGX (rr_backend_check)
std::vector<uint16_t> color; // RGBA16F: the noisy signals remodulated and summed, sky added
std::vector<V3> colorClean; // the same from the noise-free signals: the ground truth
std::vector<float> hardwareDepth; // D32: NDC z of the projection (1 for the sky)
std::vector<uint16_t> motionPixels; // RG16F: (previous - current) position in render pixels
std::vector<uint16_t> normalsRoughness; // RGBA16F: signed world normal, w = linear roughness
std::vector<uint32_t> diffuseAlbedoLinear, specularAlbedoLinear; // RGBA8 UNORM, linear
};
inline uint32_t pack_unorm8(V3 v)
{
auto q = [](float f) { return static_cast<uint32_t>(std::lround(saturate(f) * 255.0f)); };
return q(v.x) | (q(v.y) << 8) | (q(v.z) << 16) | (255u << 24);
}
inline uint32_t pack_normal(V3 n, float roughness)
{
// AMD's NormalToOctahedronUv (denoising.md 5.3.1)
const float l1 = std::fabs(n.x) + std::fabs(n.y) + std::fabs(n.z);
float x = n.x / l1, y = n.y / l1;
if (n.z < 0)
{
const float ox = (1.0f - std::fabs(y)) * (x >= 0 ? 1.0f : -1.0f);
const float oy = (1.0f - std::fabs(x)) * (y >= 0 ? 1.0f : -1.0f);
x = ox, y = oy;
}
auto q10 = [](float f) { return static_cast<uint32_t>(std::lround(saturate(f) * 1023.0f)); };
return q10(x * 0.5f + 0.5f) | (q10(y * 0.5f + 0.5f) << 10) | (q10(roughness) << 20) | (0u << 30);
}
// Renders one frame. (jitterX, jitterY): the sample offset from each pixel's centre in pixels (DLSS's
// Jitter.Offset is its negation). Motion is that of the sampled surface point.
inline void render(Frame& frame, uint32_t width, uint32_t height, const Camera& camera, const Camera& previous,
const M4& projection, uint32_t frameIndex, float jitterX = 0.0f, float jitterY = 0.0f)
{
const size_t count = static_cast<size_t>(width) * height;
frame.depth.assign(count, 0);
frame.motion.assign(count * 4, 0);
frame.normals.assign(count, 0);
frame.diffuseAlbedo.assign(count, 0);
frame.specularAlbedo.assign(count, 0);
frame.valid.assign(count, 0);
frame.diffuseLinear.assign(count, V3{});
frame.specularLinear.assign(count, V3{});
frame.color.assign(count * 4, 0);
frame.colorClean.assign(count, V3{});
frame.hardwareDepth.assign(count, 1.0f);
frame.motionPixels.assign(count * 2, 0);
frame.normalsRoughness.assign(count * 4, 0);
frame.diffuseAlbedoLinear.assign(count, 0);
frame.specularAlbedoLinear.assign(count, 0);
for (int s = 0; s < kSignalCount; ++s)
{
frame.clean[s].assign(count * 3, 0);
frame.noisy[s].assign(count * 4, 0);
}
const M4 view = view_matrix(camera.eye, camera.target);
const M4 previousView = view_matrix(previous.eye, previous.target);
const V3 forward = normalize(camera.target - camera.eye);
const V3 right = normalize(cross({0, 1, 0}, forward));
const V3 up = cross(forward, right);
const float tanX = 1.0f / projection.m[0][0], tanY = 1.0f / projection.m[1][1];
for (uint32_t py = 0; py < height; ++py)
for (uint32_t px = 0; px < width; ++px)
{
const size_t i = static_cast<size_t>(py) * width + px;
const float u = (px + 0.5f + jitterX) / width, v = (py + 0.5f + jitterY) / height;
const float ndcX = u * 2 - 1, ndcY = 1 - v * 2;
const V3 direction = normalize(forward + right * (ndcX * tanX) + up * (ndcY * tanY));
const Hit hit = trace(camera.eye, direction);
const uint32_t pixelId = static_cast<uint32_t>(i);
frame.color[i * 4 + 3] = to_half(1.0f);
frame.normalsRoughness[i * 4 + 2] = to_half(-1.0f);
frame.normalsRoughness[i * 4 + 3] = to_half(1.0f);
if (!hit.valid)
{
// Sky: outside linearDepthBounds, so the runtime passes it through.
frame.depth[i] = 5000.0f;
frame.normals[i] = pack_normal({0, 0, -1}, 1);
const V3 sky = environment(direction);
frame.colorClean[i] = sky;
frame.color[i * 4 + 0] = to_half(sky.x);
frame.color[i * 4 + 1] = to_half(sky.y);
frame.color[i * 4 + 2] = to_half(sky.z);
continue;
}
frame.valid[i] = 1;
float clip[4];
transform(view, hit.position, clip);
frame.depth[i] = clip[2];
float projected[4];
transform(projection, {clip[0], clip[1], clip[2]}, projected);
frame.hardwareDepth[i] = projected[2] / projected[3];
float prevView[4], prevClip[4];
transform(previousView, hit.position, prevView);
const M4& p = projection;
const V3 pv = {prevView[0], prevView[1], prevView[2]};
transform(p, pv, prevClip);
const float prevU = (prevClip[0] / prevClip[3]) * 0.5f + 0.5f;
const float prevV = 0.5f - (prevClip[1] / prevClip[3]) * 0.5f;
frame.motion[i * 4 + 0] = to_half(prevU - u);
frame.motion[i * 4 + 1] = to_half(prevV - v);
frame.motion[i * 4 + 2] = to_half(prevView[2] - clip[2]);
frame.motion[i * 4 + 3] = to_half(0);
frame.motionPixels[i * 2 + 0] = to_half((prevU - u) * width);
frame.motionPixels[i * 2 + 1] = to_half((prevV - v) * height);
const V3 diffuse = hit.base * (1.0f - hit.metal);
const V3 f0 = V3{0.04f, 0.04f, 0.04f} * (1.0f - hit.metal) + hit.base * hit.metal;
const V3 toEye = normalize(camera.eye - hit.position);
const float nov = std::max(dot(hit.normal, toEye), 1e-3f);
const V3 specular = hit.water ? V3{0.96f, 0.96f, 0.96f} : approximate_brdf(f0, hit.roughness * hit.roughness, nov);
frame.diffuseLinear[i] = diffuse;
frame.specularLinear[i] = specular;
frame.diffuseAlbedo[i] = pack_unorm8({std::sqrt(diffuse.x), std::sqrt(diffuse.y), std::sqrt(diffuse.z)});
frame.specularAlbedo[i] = pack_unorm8({std::sqrt(specular.x), std::sqrt(specular.y), std::sqrt(specular.z)});
frame.diffuseAlbedoLinear[i] = pack_unorm8(diffuse);
frame.specularAlbedoLinear[i] = pack_unorm8(specular);
frame.normals[i] = pack_normal(hit.normal, hit.roughness);
frame.normalsRoughness[i * 4 + 0] = to_half(hit.normal.x);
frame.normalsRoughness[i * 4 + 1] = to_half(hit.normal.y);
frame.normalsRoughness[i * 4 + 2] = to_half(hit.normal.z);
frame.normalsRoughness[i * 4 + 3] = to_half(hit.roughness);
// Demodulated (albedo-free) clean signals, smooth in world space.
const float visibility = shadowed(hit.position, hit.normal) ? 0.0f : 1.0f;
const float ndl = std::max(dot(hit.normal, kLight), 0.0f);
const V3 reflected = direction - hit.normal * (2 * dot(direction, hit.normal));
const float rdl = std::max(dot(reflected, kLight), 0.0f);
const float glossExponent = 2.0f / (hit.roughness * hit.roughness * hit.roughness * hit.roughness + 0.02f);
V3 clean[kSignalCount];
clean[kDirectDiffuse] = V3{1.0f, 0.95f, 0.85f} * (2.5f * ndl * visibility);
clean[kDirectSpecular] = V3{1.0f, 0.95f, 0.85f} * std::min(8.0f, 3.0f * std::pow(rdl, glossExponent) * visibility * (ndl > 0));
const float wobble = 0.12f * std::sin(1.7f * hit.position.x) * std::sin(1.3f * hit.position.z);
clean[kIndirectDiffuse] = environment(hit.normal) * (0.6f + wobble);
clean[kIndirectSpecular] = environment(reflected) * (0.8f + 0.2f * (1 - hit.roughness));
if (hit.water)
{
// The pond's light is its reflection: the traced scene, lit noise-free. Its diffuse lobe is
// nearly dark despite the 0.5 guide albedo.
clean[kDirectDiffuse] = clean[kDirectDiffuse] * 0.01f;
clean[kIndirectDiffuse] = clean[kIndirectDiffuse] * 0.01f;
const Hit bounce = trace(hit.position + hit.normal * 2e-3f, reflected);
if (bounce.valid && !bounce.water)
{
const float bounceVisibility = shadowed(bounce.position, bounce.normal) ? 0.0f : 1.0f;
const float bounceNdl = std::max(dot(bounce.normal, kLight), 0.0f);
const V3 light = V3{1.0f, 0.95f, 0.85f} * (2.5f * bounceNdl * bounceVisibility) +
environment(bounce.normal) * 0.6f;
clean[kIndirectSpecular] = mul(bounce.base * (1.0f - bounce.metal), light) +
environment(reflect_dir(reflected, bounce.normal)) * (0.04f + 0.9f * bounce.metal);
}
else
clean[kIndirectSpecular] = environment(reflected);
}
V3 combined{}, combinedClean{};
for (int s = 0; s < kSignalCount; ++s)
{
// One-sample-per-pixel white noise: a mean-one exponential weight shared by RGB,
// like a single path sample's throughput.
float sample = -std::log(std::max(1e-6f, 1.0f - uniform(pixelId, frameIndex, s)));
if (hit.water && s == kIndirectSpecular)
sample = 1.0f + 0.35f * (sample - 1.0f); // a mirror's reflection ray is far less noisy
frame.clean[s][i * 3 + 0] = clean[s].x;
frame.clean[s][i * 3 + 1] = clean[s].y;
frame.clean[s][i * 3 + 2] = clean[s].z;
frame.noisy[s][i * 4 + 0] = to_half(clean[s].x * sample);
frame.noisy[s][i * 4 + 1] = to_half(clean[s].y * sample);
frame.noisy[s][i * 4 + 2] = to_half(clean[s].z * sample);
// A: ray hit distance for the indirect signals, any non-negative value for direct.
float alpha = 0.0f;
if (s == kIndirectDiffuse)
alpha = 0.5f + 3.0f * uniform(pixelId, frameIndex, 10 + s);
else if (s == kIndirectSpecular)
{
const Hit bounce = trace(hit.position + hit.normal * 2e-3f, reflected);
alpha = bounce.valid ? std::min(bounce.t, 100.0f) : 100.0f;
}
frame.noisy[s][i * 4 + 3] = to_half(alpha);
const V3 albedo = s == kDirectDiffuse || s == kIndirectDiffuse ? diffuse : specular;
combined = combined + mul(clean[s] * sample, albedo);
combinedClean = combinedClean + mul(clean[s], albedo);
}
frame.colorClean[i] = combinedClean;
frame.color[i * 4 + 0] = to_half(combined.x);
frame.color[i * 4 + 1] = to_half(combined.y);
frame.color[i * 4 + 2] = to_half(combined.z);
}
}
} // namespace fsr_scene
+140
View File
@@ -0,0 +1,140 @@
"""Compare K input batching using identical synthetic frames and alternating runs.
Uses an existing private runtime; never installs into a game. CPU interop timing
includes synchronization and NGX execution, and is not a GPU network-only time.
"""
import argparse
import hashlib
import json
import os
from pathlib import Path
import re
import statistics
import subprocess
def main():
parser = argparse.ArgumentParser(description=__doc__)
for key in ('probe', 'shim', 'runtime', 'core', 'dlss', 'output'):
parser.add_argument('--' + key, type=lambda s: Path(s).resolve(), required=True)
parser.add_argument('--resolution', default='1920x1080')
parser.add_argument('--input-resolution', help='defaults to nominal Quality (two-thirds per axis)')
parser.add_argument('--frames', type=int, default=16)
parser.add_argument('--profile-stages', action='store_true',
help='diagnostic CPU stage logging; adds overhead to timings')
parser.add_argument('--candidate-native', type=lambda s: Path(s).resolve(),
help='compare an experimental native directory instead of input batching')
parser.add_argument('--profile-kernels', action='store_true',
help='diagnostic deferred GPU events, allowing legacy-stream dependencies; timings are instrumented')
args = parser.parse_args()
if args.frames < 8 or args.frames > 120:
parser.error('frames must be 8..120')
if not re.fullmatch(r'[1-9][0-9]*x[1-9][0-9]*', args.resolution):
parser.error('resolution must be WIDTHxHEIGHT')
ow, oh = map(int, args.resolution.split('x'))
if not args.input_resolution:
args.input_resolution = f'{ow * 2 // 3}x{oh * 2 // 3}'
if not re.fullmatch(r'[1-9][0-9]*x[1-9][0-9]*', args.input_resolution):
parser.error('input-resolution must be WIDTHxHEIGHT')
build_root = Path(__file__).resolve().parents[2] / 'build'
if not args.output.is_relative_to(build_root.resolve()):
parser.error('output must stay under the repository ignored build directory')
args.output.mkdir(parents=True, exist_ok=False)
report = {'passed': False, 'resolution': args.resolution, 'warmupFrames': 4,
'inputResolution': args.input_resolution,
'stageProfiling': args.profile_stages,
'kernelProfiling': args.profile_kernels,
'comparison': 'native kernels' if args.candidate_native else 'input batching',
'timing': 'CPU interop including NGX and synchronization', 'runs': []}
runtime = args.runtime
native = runtime / 'native'
if (native / 'gfx1101').is_dir():
native = native / 'gfx1101'
nvapi = runtime / 'nvapi/nvapi64.dll'
if not nvapi.is_file():
nvapi = runtime / 'nvapi/gfx1101/nvapi64.dll'
report['artifacts'] = {str(p): hashlib.sha256(p.read_bytes()).hexdigest()
for p in (args.probe, args.shim, args.core, args.dlss)}
report['nativeArtifacts'] = {p.name: hashlib.sha256(p.read_bytes()).hexdigest()
for p in sorted(native.glob('*')) if p.is_file()}
if args.candidate_native:
report['candidateNativeArtifacts'] = {p.name: hashlib.sha256(p.read_bytes()).hexdigest()
for p in sorted(args.candidate_native.glob('*')) if p.is_file()}
env = os.environ.copy()
# Keep profiling and arithmetic settings independent of the caller's shell.
for key in list(env):
if key.startswith('D4R_') or key in ('ZLUDA_LOG_DIR', 'ZLUDA_CACHE_DIR', 'ZLUDA_CUDA_LIB'):
del env[key]
env.update(HIP_PATH=str(runtime / 'hip'),
ZLUDA_CUDA_LIB=str(runtime / 'zluda/nvcuda.dll'),
D4R_NVAPI_BACKEND=str(runtime / 'nvapi/backend/nvapi64.dll'),
D4R_FORMAT_MODULE=str(runtime / 'pixel_convert_gfx1101.hsaco'),
D4R_ZLUDA_NATIVE_DIR=str(native),
D4R_ZLUDA_WMMA='1', D4R_ZLUDA_WMMA_FP8='1',
D4R_ZLUDA_WMMA_F16_REFERENCE='1', D4R_QUIET_API='1',
ZLUDA_CACHE_DIR=str(Path(os.environ['LOCALAPPDATA']) / 'd4r/cache'),
D4R_NGX_DATA_DIR=str(args.output / 'ngx-data'))
env['PATH'] = str(runtime / 'hip/bin') + os.pathsep + str(runtime / 'zluda') + os.pathsep + env['PATH']
if args.profile_stages:
env['D4R_PROFILE_STAGES'] = '1'
if args.profile_kernels:
env.update(D4R_ZLUDA_PROFILE_DEFERRED='1', D4R_ZLUDA_PROFILE_ALLOW_LEGACY='1',
D4R_ZLUDA_PROFILE_EVERY='1')
try:
reference = None
for index, batched in enumerate((False, True, True, False)):
label = ('native-candidate' if args.candidate_native else 'batch') if batched else 'control'
output = args.output / f'{index}-{label}'
output.mkdir()
env['D4R_DIAG_DIR'] = str(output)
if batched and not args.candidate_native:
env['D4R_BATCH_INPUT_COPIES'] = '1'
else:
env.pop('D4R_BATCH_INPUT_COPIES', None)
env['D4R_ZLUDA_NATIVE_DIR'] = str(args.candidate_native if batched and args.candidate_native else native)
command = [str(args.probe), '--module', str(args.shim), '--hip-root', str(runtime / 'hip'),
'--cuda-dll', str(runtime / 'zluda/nvcuda.dll'), '--ngx-core', str(args.core),
'--dlss-dll', str(args.dlss), '--nvapi-dll', str(nvapi),
'--ngx-abi', 'project', '--interop-mode', 'command-list', '--preset', '11',
'--ngx-output-resolution', args.resolution,
'--ngx-input-resolution', args.input_resolution, '--iterations', str(args.frames)]
with (output / 'stdout.log').open('wb') as out, (output / 'stderr.log').open('wb') as err:
result = subprocess.run(command, env=env, stdout=out, stderr=err, timeout=180)
text = (output / 'stdout.log').read_text(errors='replace')
if result.returncode or 'PASS D3D12_DLSS' not in text:
raise RuntimeError(f'run {index} failed ({result.returncode}); inspect {output}')
iw, ih = map(int, args.input_resolution.split('x'))
expected_size = f'D3D12_NGX_DIMENSIONS input={iw}x{ih} output={ow}x{oh}'
if expected_size not in text or f'D4R_INTEROP async=0 batch_inputs={int(batched and not args.candidate_native)}' not in text:
raise RuntimeError(f'run {index}: requested dimensions/interop mode not applied')
if len(re.findall(r'D3D12_OUTPUT preset=11 frame=\d+ finite=1 ', text)) != args.frames:
raise RuntimeError(f'run {index}: missing finite-output checks')
times = [float(x) for x in re.findall(r'D4R_FRAME .*?interop_ngx_ms=([0-9.]+)', text)]
if len(times) != args.frames:
raise RuntimeError(f'run {index}: incomplete frame timing')
hashes = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in output.glob('output-*.rgba16f')}
if len(hashes) != args.frames:
raise RuntimeError(f'run {index}: incomplete output readback')
if reference is None:
reference = hashes
elif hashes != reference:
raise RuntimeError(f'run {index}: output differs from control')
report['runs'].append({'batch': batched and not bool(args.candidate_native),
'candidate': batched, 'allFrameMs': times,
'medianMs': statistics.median(times[4:]), 'outputHashes': hashes})
print(f'run={index} variant={label} warmed_cpu_interop_ms={statistics.median(times[4:]):.3f}', flush=True)
for mode in (False, True):
values = [t for row in report['runs'] if row['candidate'] == mode for t in row['allFrameMs'][4:]]
key = ('candidateMedianMs' if args.candidate_native else 'batchMedianMs') if mode else 'controlMedianMs'
report[key] = statistics.median(values)
report['passed'] = True
except Exception as error:
report['error'] = str(error)
print(str(error), flush=True)
finally:
(args.output / 'summary.json').write_text(json.dumps(report, indent=2) + '\n')
return 0 if report['passed'] else 1
if __name__ == '__main__':
raise SystemExit(main())
+20 -3
View File
@@ -14,6 +14,7 @@ import shutil
import subprocess
import sys
from gpu_targets import TARGETS
from llvm22_ir import compatible_ir
def require_code_object_target(path, target):
@@ -39,6 +40,8 @@ def main():
parser.error('this private experimental build recipe supports K v2 input/output variants only')
repo = pathlib.Path(__file__).resolve().parents[2]
output = args.output_directory.resolve()
if not output.is_relative_to((repo / 'build').resolve()):
parser.error('private texture outputs must stay under the repository ignored build directory')
output.mkdir(parents=True, exist_ok=True)
for existing in output.glob('*.hsaco'):
require_code_object_target(existing, args.gpu_arch)
@@ -46,8 +49,16 @@ def main():
work.mkdir(exist_ok=True)
hip = args.hip_root.resolve()
zluda = args.zluda_root.resolve()
tools = {name: hip / 'bin' / (name + '.exe') for name in ('clang++', 'llvm-dis', 'llvm-as')}
tools = {}
for name in ('clang++', 'llvm-dis', 'llvm-as'):
candidates = [hip / folder / (name + '.exe') for folder in ('lib/llvm/bin', 'bin')]
tools[name] = next((path for path in candidates if path.is_file()), candidates[0])
emitter = zluda / 'd4r_emit.exe'
# The emitter's LLVM reader must consume the helper bitcode. A newer HIP
# compiler can emit textual IR, but its bitcode attributes may be unreadable.
emitter_assembler = zluda / 'llvm-as.exe'
if emitter_assembler.is_file():
tools['llvm-as'] = emitter_assembler
for path in [args.dlss_dll, emitter, *tools.values()]:
if not path.is_file():
parser.error(f'missing dependency: {path}')
@@ -63,7 +74,8 @@ def main():
def run(label, command):
with (work / (label + '.log')).open('wb') as log:
result = subprocess.run([str(arg) for arg in command], env=env, stdout=log, stderr=subprocess.STDOUT)
result = subprocess.run([str(arg) for arg in command], env=env, stdout=log,
stderr=subprocess.STDOUT, timeout=600)
if result.returncode:
print((work / (label + '.log')).read_text(errors='replace')[-8000:], file=sys.stderr)
raise RuntimeError(f'{label} failed ({result.returncode}); see {work}')
@@ -89,11 +101,16 @@ def main():
run('disassemble', [tools['llvm-dis'], raw, '-o', ir])
lines = []
for line in ir.read_text().splitlines():
# Helpers are linked directly into the translated module. Their ThinLTO
# index becomes stale when the unused HIP/LLVM globals below are removed.
if re.match(r'^\^\d+\s*=', line):
continue
if re.search(r'@llvm.used|wchar_size|llvm.module.flags|__hip_cuid', line):
continue
line = line.replace('optnone', '').replace(f'"target-cpu"="{args.gpu_arch}"', '')
lines.append(re.sub(r'"target-features"="[^"]+"', '', line))
ir.write_text('\n'.join(lines) + '\n')
ir.write_text(compatible_ir('\n'.join(lines) + '\n') if emitter_assembler.is_file()
else '\n'.join(lines) + '\n')
run('assemble', [tools['llvm-as'], ir, '-o', extra])
env['D4R_ZLUDA_EXTRA_BC'] = str(extra)
run('emit', [emitter, edited, work / 'emitted', args.gpu_arch])
+155
View File
@@ -0,0 +1,155 @@
"""Build exact gfx11 RR D/E models natively on Windows, without Git Bash.
Derived weights/shaders remain under ignored build/. Nothing is installed into
a game or bundled. Uses the same packers and compiler recipe as the Linux shell.
"""
import argparse
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import subprocess
import sys
def sha(path):
with path.open('rb') as stream:
return hashlib.file_digest(stream, 'sha256').hexdigest()
def main():
repo = Path(__file__).resolve().parents[2]
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--dll', required=True, type=lambda p: Path(p).resolve())
parser.add_argument('--preset', choices=('D', 'E'), default='D')
parser.add_argument('--output', required=True, type=lambda p: Path(p).resolve())
parser.add_argument('--glslang', type=lambda p: Path(p).resolve(), default=repo / '.tools/glslang-16.6.0/bin/glslangValidator.exe')
parser.add_argument('--reuse', action='store_true')
args = parser.parse_args()
if not args.output.is_relative_to((repo / 'build').resolve()):
parser.error('derived models must stay under ignored build/')
if sha(args.dll) != '4bc7ea5fcb2f32cf86bc2cb072e8d2860914a0be511cbdcb2fc206c1d9d83b80':
parser.error('requires pinned NVIDIA RR 310.9.1 DLL')
if sha(args.glslang) != '9643709c02c83e26773f96b6a87f197ab2e84f78be0879ad8fb3619418b5d9ad':
parser.error('requires pinned glslang 16.6.0 executable')
env = os.environ.copy()
for key in tuple(env):
if key.startswith('D4R_RR_'):
del env[key]
env.update(D4R_RR_GFX11='1', D4R_RR_RELAXED='0', PYTHONPATH=str(repo / '.tools/python/vendor'))
env['PATH'] = str(args.glslang.parent) + os.pathsep + env['PATH']
numpy_version = subprocess.check_output([sys.executable, '-c', 'import numpy; print(numpy.__version__)'], env=env, text=True, timeout=30).strip()
sources = {str(Path(__file__).relative_to(repo)).replace('\\', '/'): sha(Path(__file__))}
for directory in ('engine', 'vulkan', 'kernels/tools'):
for path in sorted((repo / directory).rglob('*')):
if path.is_file() and path.suffix in ('.py', '.comp', '.glsl', '.h', '.inc', '.sh', '.txt'):
sources[path.relative_to(repo).as_posix()] = sha(path)
identity = {'schema': 1, 'preset': args.preset, 'architecture': 'gfx1101', 'arithmetic': 'exact-gfx11',
'dll': sha(args.dll), 'glslang': sha(args.glslang), 'python': sha(Path(sys.executable)),
'numpy': numpy_version, 'sources': sources}
stamp = args.output / 'windows-native-python-cache.json'
def products():
return {p.name: sha(p) for p in sorted(args.output.iterdir()) if p.is_file() and p.name != stamp.name}
if args.output.exists():
if not args.reuse:
parser.error('use a fresh output directory or --reuse')
cached = json.loads(stamp.read_text())
if cached['identity'] != identity or cached['products'] != products():
raise RuntimeError('RR cache changed; use a fresh output directory')
print('RR_NATIVE_CACHE_HIT preset=' + args.preset)
return
args.output.mkdir(parents=True)
out = args.output
work = out / 'native-work'
work.mkdir()
rr = repo / 'engine/rr'
serial = 0
def run(command, label):
nonlocal serial
serial += 1
print(f'RR_BUILD step={serial} {label}', flush=True)
log = work / f'{serial:02}-{label}.log'
with log.open('wb') as stream:
process = subprocess.Popen([str(p) for p in command], cwd=repo, env=env, stdout=stream, stderr=subprocess.STDOUT)
try:
code = process.wait(timeout=600)
except subprocess.TimeoutExpired:
if os.name == 'nt':
subprocess.run(['taskkill', '/PID', str(process.pid), '/T', '/F'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=30)
else:
process.kill()
process.wait(timeout=30)
raise RuntimeError('RR build timed out; see ' + str(log))
if code:
raise RuntimeError(f'RR build failed ({code}); see {log}')
return log.read_text(errors='replace').strip()
def python(script, *arguments, label):
return run([sys.executable, script, *arguments], label)
python(repo / 'kernels/tools/extract_dlss_ptx.py', args.dll, work / 'ptx', label='extract')
python(repo / 'engine/compile_rr.py', '--dll', args.dll, '--preset', args.preset, out,
'--ptx-dir', work / 'ptx', '--network-bindings-only', label='bindings')
python(rr / 'dll_weights.py', args.dll, work / 'dll', '--model', '0' if args.preset == 'D' else '1', label='weights')
manifest = (out / 'rr.txt').read_text().replace(f'preset {4 if args.preset == "D" else 5}\n',
f'preset {4 if args.preset == "D" else 5}\narithmetic gfx11\n')
defines = ['-DRR_EXACT_CONVERT', '-DRR_GFX11'] + (['-DRR_PRESET_D'] if args.preset == 'D' else [])
def native(stage, shader, include):
nonlocal manifest
weights = out / f'weights-{stage}m.bin'
wide = int(python(rr / 'widen_weights.py', weights, label='widen-' + stage).splitlines()[-1])
run([args.glslang, '--target-env', 'vulkan1.3', '-V', *defines, f'-DW_WIDE={wide}u',
'-I' + str(include), '-I' + str(rr), '-o', out / f'{stage}_m.spv', shader], 'compile-' + stage)
python(rr / 'spv_exact_convert.py', out / f'{stage}_m.spv', label='exact-' + stage)
pattern = rf'^stage {stage} {stage}\.spv 32 weights-{stage}\.bin (.*)$'
def replacement(match):
tail = match[1].removesuffix(' noopt')
return f'stage {stage} {stage}_m.spv 32 weights-{stage}m.bin {tail}'
manifest, count = re.subn(pattern, replacement, manifest, flags=re.MULTILINE)
if count != 1:
raise RuntimeError('Missing/duplicate native stage: ' + stage)
for stage in ('enc1', 'enc2', 'enc3', 'enc4', 'enc5', 'dec4', 'dec3', 'dec2', 'dec1'):
directory = work / stage
directory.mkdir()
shutil.copyfile(work / 'dll' / f'{stage}.npz', directory / 'weights.npz')
python(rr / 'swin_pack.py', stage, directory / 'weights.npz', out / f'weights-{stage}m.bin', directory / 'swin_stage.h', label='pack-' + stage)
native(stage, rr / 'swin_m.comp', directory)
for stage in ('dec0', 'enc0'):
directory = work / stage
directory.mkdir()
shutil.copyfile(work / 'dll' / f'{stage}.npz', directory / 'weights.npz')
if stage == 'dec0':
python(rr / 'dec0_pack.py', directory / 'weights.npz', out / 'weights-dec0m.bin', directory / 'dec0_m_w.h', label='pack-dec0')
native(stage, rr / 'dec0_m.comp', directory)
else:
base = 0 if args.preset == 'D' else 4096
python(rr / 'enc0_pack.py', directory / 'weights.npz', directory / 'enc0_m.bin', directory / 'enc0_m_w.h', str(base), label='pack-enc0')
noise = b'' if not base else (work / 'dll/noise.bin').read_bytes()
if len(noise) != base:
raise RuntimeError('Invalid E noise table')
(out / 'weights-enc0m.bin').write_bytes(noise + (directory / 'enc0_m.bin').read_bytes())
python(rr / 'gen_enc0_m.py', out / 'enc0.generated.comp', directory / 'enc0_m.comp', label='gather-enc0')
native(stage, directory / 'enc0_m.comp', directory)
metadata = json.loads((out / 'model.json').read_text())
if metadata['stages']['hkpn'].get('shader') == 'generated from the DLL':
manifest = manifest.replace('stage hkpn hkpn.spv 32 ', 'stage hkpn hkpn.spv 64 ')
for stage in ('enc0', 'enc1', 'enc2', 'enc3', 'enc4', 'enc5', 'dec4', 'dec3', 'dec2', 'dec1', 'dec0'):
path = out / f'weights-{stage}.bin'
if path.exists():
path.unlink()
(out / 'rr.txt').write_text(manifest, newline='\n')
stages = [line.split() for line in manifest.splitlines() if line.startswith('stage ')]
if len(stages) != 12 or 'arithmetic gfx11' not in manifest:
raise RuntimeError('Incomplete native manifest')
for fields in stages:
if not (out / fields[2]).is_file() or not (out / fields[4]).is_file():
raise RuntimeError('Missing stage product')
textures = [] if fields[5] == '-' else list(map(int, fields[5].split(',')))
surfaces = [] if fields[6] == '-' else [[int(s.split(':')[0]), s.split(':')[1]] for s in fields[6].split(',')]
(out / fields[2]).with_suffix('.json').write_text(json.dumps({'textures': textures, 'surfaces': surfaces}) + '\n')
stamp.write_text(json.dumps({'identity': identity, 'products': products()}, indent=2) + '\n')
print('RR_NATIVE_MODEL_BUILT preset=' + args.preset + ' output=' + str(out))
if __name__ == '__main__':
main()
+1 -1
View File
@@ -29,7 +29,7 @@ int main(int argc, char** argv) {
for (const auto& pair : {std::make_pair("D4R_HIP_ROOT", args.hip_root),
std::make_pair("D4R_NVCUDA_DLL", args.cuda_dll), std::make_pair("D4R_NVAPI_DLL", args.nvapi_dll),
std::make_pair("D4R_NGX_CORE", args.ngx_core), std::make_pair("D4R_DLSS_DLL", args.dlss_dll)})
if (_putenv_s(pair.first, pair.second.c_str())) throw std::runtime_error("Cannot set runtime environment");
if (_putenv_s(pair.first, args.player_mode ? "" : pair.second.c_str())) throw std::runtime_error("Cannot set runtime environment");
HipApi hip(args.hip_root); hipDeviceProp_t props{}; hip.select_architecture(args.device, props);
ComPtr<IDXGIFactory4> factory; dx(CreateDXGIFactory1(IID_PPV_ARGS(factory.GetAddressOf())), "CreateDXGIFactory1");
ComPtr<IDXGIAdapter1> adapter;
+180 -6
View File
@@ -1,6 +1,10 @@
// Native Windows backend. The Linux/Wine implementation remains separate.
#include "ngx_windows_runtime.h"
#include "ngx_runtime_initialization.h"
#include "player_config.h"
#if defined(D4R_NATIVE_RR_BACKEND)
#include "rr_native_backend.h"
#endif
#include <dxgi.h>
#include <unordered_map>
#include <functional>
@@ -8,9 +12,102 @@
namespace {
std::mutex apiMutex;
std::shared_ptr<d4r::win::Runtime> runtime;
std::shared_ptr<d4r::win::RuntimeInitialization> initialization;
bool deferredSr = false;
void ensure_sr_runtime() {
if (!runtime) {
if (!initialization) throw std::runtime_error("NGX is not initialized");
runtime = initialization->create();
}
}
void initialize(ID3D12Device* device, unsigned long long app, const wchar_t* data, unsigned sdk,
const d4r::ngx::FeatureCommonInfo* common, const d4r::ngx::ProjectIdentity* project = nullptr) {
if (initialization) {
if (initialization->device.Get() != device) throw std::runtime_error("NGX already initialized on another device");
return;
}
auto candidate = std::make_shared<d4r::win::RuntimeInitialization>(device, app, data, sdk, common, project);
bool native = false;
#if defined(D4R_NATIVE_RR_BACKEND)
const char* backend = std::getenv("D4R_RR_BACKEND"); native = backend && std::string(backend) == "native";
#endif
// Default SR startup remains eager. Native RR owns its Vulkan execution
// independently; SR initializes only when its capabilities/feature are used.
if (!native) runtime = candidate->create();
initialization = std::move(candidate); deferredSr = native;
}
struct Handle { unsigned Id; };
unsigned nextId = 1;
std::unordered_map<Handle*, std::shared_ptr<d4r::win::Feature>> features;
#if defined(D4R_NATIVE_RR_BACKEND)
struct RrFeature {
d4r::win::rr::Creation creation;
d4r::win::rr::Backend backend;
RrFeature(ID3D12Device* device, void* parameters, const std::string& directory)
: creation(parameters), backend(device, creation, directory) {}
};
std::unordered_map<Handle*, std::shared_ptr<RrFeature>> rrFeatures;
std::string rr_directory() {
const char* backend = std::getenv("D4R_RR_BACKEND");
if (!backend || std::string(backend) != "native")
throw std::runtime_error("Native RR requires D4R_RR_BACKEND=native");
return d4r::win::env_path("D4R_RR_MODEL_DIR");
}
bool rr_ready(ID3D12Device* device) {
try {
const auto directory = rr_directory();
void* p = d4r_ngx_parameters_create();
if (!p) return false;
struct Cleanup { void* p; ~Cleanup() { d4r_ngx_parameters_destroy(p); } } cleanup{p};
for (const char* name : {"Width", "Height", "OutWidth", "OutHeight"}) d4r_ngx_set_uint(p, name, 64);
d4r_ngx_set_uint(p, "DLSS.Feature.Create.Flags", 3);
d4r::win::rr::Creation creation(p);
// Readiness checks the actual configured model's pipeline/weight load,
// adapter features and packet/fence import. No synthetic frame runs.
d4r::win::rr::Backend candidate(device, creation, directory);
return true;
} catch (const std::exception& error) {
if (std::getenv("D4R_RR_BACKEND")) std::fprintf(stderr, "D4R_RR_UNAVAILABLE %s\n", error.what());
return false;
}
}
struct RrSnapshot {
d4r::win::rr::Snapshot frame;
std::array<d4r::win::ResourceAccess, d4r::win::rr::Snapshot::Count> access;
std::array<bool, d4r::win::rr::Snapshot::Count> explicitState{};
RrSnapshot(const d4r::win::rr::Creation& creation, void* p, ID3D12GraphicsCommandList* list)
: frame(creation, p) {
const char* names[] = {"D4R.Color.State", "D4R.Motion.State", "D4R.Depth.State", "D4R.Normals.State",
"D4R.Roughness.State", "D4R.Diffuse.State", "D4R.Specular.State", "D4R.Output.State", "D4R.Exposure.State",
"D4R.HitDistance.State", "D4R.Disocclusion.State", "D4R.Subsurface.State", "D4R.BeforeParticles.State"};
for (size_t i = 0; i < access.size(); ++i) {
if (!frame.resources[i]) {
if (i < 8) throw std::runtime_error("Missing required NGX RR guide/output");
continue;
}
const auto fallback = d4r::win::ngx_resource_access(p, names[i], i == 7 ?
D3D12_RESOURCE_STATE_UNORDERED_ACCESS : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
unsigned declared = 0;
explicitState[i] = d4r_ngx_get_uint(p, names[i], &declared) == 1;
access[i] = list ? d4r::win::commands::resource_access(list, frame.resources[i].Get(), fallback) :
d4r::win::commands::submitted_resource_access(frame.resources[i].Get(), fallback);
if (access[i].pending_split) throw std::runtime_error("NGX RR texture has an unfinished split barrier");
}
if (frame.settings.roughnessPacked) {
access[4] = access[3]; explicitState[4] = explicitState[3];
}
}
void resolve() {
for (size_t i = 0; i < access.size(); ++i) if (access[i].inherited) {
access[i] = d4r::win::commands::submitted_resource_access(frame.resources[i].Get(), access[i]);
// Explicit caller state remains valid when no prior submitted list
// supplied a tracked transition for this resource.
if (access[i].inherited && explicitState[i]) access[i].inherited = false;
if (access[i].pending_split || access[i].inherited) throw std::runtime_error("NGX RR inherited texture state unresolved");
}
}
};
#endif
std::unordered_map<void*, bool> parameters;
constexpr unsigned failure = 0xBAD00002, invalid = 0xBAD00005;
unsigned call(const std::function<unsigned()>& fn) noexcept {
@@ -27,7 +124,7 @@ unsigned call(const std::function<unsigned()>& fn) noexcept {
}
}
unsigned allocate(void** out, bool caps) {
if (!runtime || !out) return invalid;
if (!initialization || !out) return invalid;
void* value = d4r_ngx_parameters_create();
if (!value) return failure;
*out = nullptr;
@@ -36,6 +133,15 @@ unsigned allocate(void** out, bool caps) {
~LocalParameters() { if (p) d4r_ngx_parameters_destroy(p); }
} owned{value};
if (caps) {
try { ensure_sr_runtime(); }
catch (const std::exception& error) {
if (!deferredSr) throw;
std::fprintf(stderr, "D4R_SR_UNAVAILABLE %s\n", error.what());
d4r_ngx_set_int(value, "SuperSampling.Available", 0);
d4r_ngx_set_int(value, "SuperSampling.NeedsUpdatedDriver", 0);
d4r_ngx_set_int(value, "SuperSampling.FeatureInitResult", int(failure));
}
if (runtime) {
std::lock_guard<std::mutex> lock(runtime->mutex);
runtime->current(); void* official = nullptr;
d4r::win::ngx_check(runtime->capabilities(&official), "CUDA capabilities");
@@ -48,6 +154,14 @@ unsigned allocate(void** out, bool caps) {
void* callback = nullptr;
if (d4r_ngx_get_void(official, name, &callback) == 1) d4r_ngx_set_void(value, name, callback);
}
}
bool rrAvailable = false;
#if defined(D4R_NATIVE_RR_BACKEND)
rrAvailable = rr_ready(initialization->device.Get());
#endif
d4r_ngx_set_int(value, "SuperSamplingDenoising.Available", rrAvailable ? 1 : 0);
d4r_ngx_set_int(value, "SuperSamplingDenoising.NeedsUpdatedDriver", 0);
d4r_ngx_set_int(value, "SuperSamplingDenoising.FeatureInitResult", rrAvailable ? 1 : int(0xBAD00001u));
}
parameters.emplace(value, true); *out = value; owned.p = nullptr;
return 1;
@@ -61,7 +175,7 @@ API unsigned NVSDK_NGX_D3D12_Init_Ext(unsigned long long app, const wchar_t* dat
std::lock_guard<std::mutex> lock(apiMutex);
return call([&] {
d4r::win::player::ensure_environment();
if (!runtime) runtime = std::make_shared<d4r::win::Runtime>(device, app, data, sdk, static_cast<const d4r::ngx::FeatureCommonInfo*>(info));
initialize(device, app, data, sdk, static_cast<const d4r::ngx::FeatureCommonInfo*>(info));
if (const char* backend = std::getenv("D4R_D3D12_COMMAND_BACKEND"); backend && std::string(backend) == "1")
d4r::win::commands::install(device);
return 1u;
@@ -76,7 +190,7 @@ API unsigned NVSDK_NGX_D3D12_Init_ProjectID(const char* project, int engine, con
if (!project || !*project) return invalid;
d4r::win::player::ensure_environment();
d4r::ngx::ProjectIdentity identity{project, engine, version ? version : ""};
if (!runtime) runtime = std::make_shared<d4r::win::Runtime>(device, 0, data, sdk, static_cast<const d4r::ngx::FeatureCommonInfo*>(info), &identity);
initialize(device, 0, data, sdk, static_cast<const d4r::ngx::FeatureCommonInfo*>(info), &identity);
if (const char* backend = std::getenv("D4R_D3D12_COMMAND_BACKEND"); backend && std::string(backend) == "1") d4r::win::commands::install(device);
return 1u;
});
@@ -86,7 +200,19 @@ API unsigned NVSDK_NGX_D3D12_GetFeatureRequirements(IDXGIAdapter* adapter, const
if (!adapter || !info || !requirements) return invalid;
return call([&] {
*requirements = {}; requirements->FeatureSupported = 16; // Unsupported feature.
if (info->FeatureID != 1) return 1u;
if (info->FeatureID != 1) {
#if defined(D4R_NATIVE_RR_BACKEND)
if (info->FeatureID == 13) {
d4r::win::player::ensure_environment();
d4r::win::ComPtr<ID3D12Device> device;
if (SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device))) && rr_ready(device.Get())) {
requirements->FeatureSupported = 0;
std::strcpy(requirements->MinOSVersion, "10.0.22000.0");
}
}
#endif
return 1u;
}
d4r::win::player::ensure_environment();
DXGI_ADAPTER_DESC desc{}; d4r::win::dx(adapter->GetDesc(&desc), "NGX requirements adapter");
// OptiScaler can override the DXGI vendor ID. The physical HIP
@@ -119,7 +245,18 @@ API unsigned NVSDK_NGX_D3D12_GetScratchBufferSize(unsigned, const void*, size_t*
API unsigned NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsCommandList*, unsigned feature, void* p, Handle** out) {
std::lock_guard<std::mutex> lock(apiMutex);
return call([&] {
if (!runtime || feature != 1 || !p || !out) return invalid;
if (!initialization || !p || !out) return invalid;
*out = nullptr;
#if defined(D4R_NATIVE_RR_BACKEND)
if (feature == 13) {
auto value = std::make_shared<RrFeature>(initialization->device.Get(), p, rr_directory());
d4r::win::commands::install(initialization->device.Get());
auto handle = std::make_unique<Handle>(); handle->Id = nextId++;
rrFeatures.emplace(handle.get(), std::move(value)); *out = handle.release(); return 1u;
}
#endif
if (feature != 1) return invalid;
ensure_sr_runtime();
d4r::win::player::apply_preset(p);
auto value = std::make_shared<d4r::win::Feature>(runtime, p);
auto handle = std::make_unique<Handle>(); handle->Id = nextId++;
@@ -127,12 +264,37 @@ API unsigned NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsCommandList*, unsigned
});
}
API unsigned d4r_D3D12_EvaluateAtBoundary(ID3D12CommandQueue* queue, Handle* handle, void* p) {
#if defined(D4R_NATIVE_RR_BACKEND)
std::shared_ptr<RrFeature> rr;
{ std::lock_guard<std::mutex> lock(apiMutex);
auto it = rrFeatures.find(handle); if (it != rrFeatures.end()) rr = it->second; }
if (rr) return call([&] {
RrSnapshot snapshot(rr->creation, p, nullptr); snapshot.resolve();
rr->backend.evaluate(queue, snapshot.frame, snapshot.access); return 1u;
});
#endif
std::shared_ptr<d4r::win::Feature> feature;
{ std::lock_guard<std::mutex> lock(apiMutex);
auto it = features.find(handle); if (it == features.end() || !p) return invalid; feature = it->second; }
return call([&] { feature->evaluate_boundary(queue, p); return 1u; });
}
API unsigned NVSDK_NGX_D3D12_EvaluateFeature(ID3D12GraphicsCommandList* list, const Handle* handle, void* p, void*) {
#if defined(D4R_NATIVE_RR_BACKEND)
std::shared_ptr<RrFeature> rr;
{ std::lock_guard<std::mutex> lock(apiMutex);
auto it = rrFeatures.find(const_cast<Handle*>(handle)); if (it != rrFeatures.end()) rr = it->second; }
if (rr) return call([&] {
if (!p || !list) return invalid;
auto snapshot = std::make_shared<RrSnapshot>(rr->creation, p, list);
// Reject unsupported contracts before splitting the logical command
// list. Submission still rechecks resources and resolved access.
rr->backend.validate_frame(snapshot->frame);
d4r::win::commands::record_boundary(list, [rr, snapshot](ID3D12CommandQueue* queue) {
snapshot->resolve(); rr->backend.evaluate(queue, snapshot->frame, snapshot->access);
});
return 1u;
});
#endif
std::shared_ptr<d4r::win::Feature> feature;
{ std::lock_guard<std::mutex> lock(apiMutex);
auto it = features.find(const_cast<Handle*>(handle));
@@ -194,6 +356,13 @@ API unsigned NVSDK_NGX_D3D12_EvaluateFeature_C(ID3D12GraphicsCommandList* list,
return NVSDK_NGX_D3D12_EvaluateFeature(list, handle, p, callback);
}
API unsigned NVSDK_NGX_D3D12_ReleaseFeature(Handle* handle) {
#if defined(D4R_NATIVE_RR_BACKEND)
std::shared_ptr<RrFeature> rr;
{ std::lock_guard<std::mutex> lock(apiMutex);
auto it = rrFeatures.find(handle);
if (it != rrFeatures.end()) { rr = std::move(it->second); rrFeatures.erase(it); delete handle; } }
if (rr) return call([&] { rr->backend.drain(); rr.reset(); return 1u; });
#endif
std::shared_ptr<d4r::win::Feature> feature;
{ std::lock_guard<std::mutex> lock(apiMutex);
auto it = features.find(handle); if (it == features.end()) return invalid;
@@ -206,10 +375,15 @@ API unsigned NVSDK_NGX_D3D12_Shutdown() {
std::lock_guard<std::mutex> lock(apiMutex);
return call([&] {
if (runtime && std::getenv("D4R_ASYNC_INTEROP")) runtime->drain_async_queue();
#if defined(D4R_NATIVE_RR_BACKEND)
for (auto& pair : rrFeatures) pair.second->backend.drain();
for (auto& pair : rrFeatures) delete pair.first;
rrFeatures.clear();
#endif
for (auto& pair : features) delete pair.first;
features.clear();
for (auto& pair : parameters) d4r_ngx_parameters_destroy(pair.first);
parameters.clear(); runtime.reset(); return 1u;
parameters.clear(); runtime.reset(); initialization.reset(); deferredSr = false; return 1u;
});
}
API unsigned NVSDK_NGX_D3D12_Shutdown1(ID3D12Device*) { return NVSDK_NGX_D3D12_Shutdown(); }
+135
View File
@@ -0,0 +1,135 @@
"""Verify and deploy a locally built RR model; optionally select it in d4r.ini.
Existing models are preserved. Configuration changes receive an external backup.
No NVIDIA DLL, compiler, driver, package archive or system setting is changed.
"""
import argparse
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import uuid
def sha(data):
return hashlib.sha256(data).hexdigest()
def select_native(data, model):
bom = data.startswith(b'\xef\xbb\xbf')
text = data.decode('utf-8-sig')
newline = '\r\n' if '\r\n' in text else '\n'
result, in_rr, found, pending = [], False, False, set()
values = {'backend': 'native', 'modeldir': model}
def flush():
if pending and result and not result[-1].endswith(('\n', '\r')):
result.append(newline)
for key in ('backend', 'modeldir'):
if key in pending:
result.append(('Backend' if key == 'backend' else 'ModelDir') + ' = ' + values[key] + newline)
pending.clear()
for line in text.splitlines(keepends=True):
stripped = line.strip()
if stripped.startswith('['):
if in_rr:
flush()
in_rr = stripped.split(']', 1)[0][1:].strip().casefold() == 'rayreconstruction'
if in_rr:
found = True
pending = set(values)
if in_rr and '=' in line and not stripped.startswith((';', '#')):
key = line.split('=', 1)[0].strip().casefold()
if key in values:
prefix, body = line.split('=', 1)
ending = '\r\n' if body.endswith('\r\n') else '\n' if body.endswith('\n') else ''
body = body.removesuffix(ending) if ending else body
comment = re.search(r'\s[;#]', body)
suffix = body[comment.start():] if comment else ''
result.append(prefix + '= ' + values[key] + suffix + ending)
pending.discard(key)
continue
result.append(line)
if in_rr:
flush()
if not found:
if result and not result[-1].endswith('\n'):
result.append(newline)
result.extend((newline, '[RayReconstruction]' + newline, 'Backend = native' + newline,
'ModelDir = ' + model + newline))
return (b'\xef\xbb\xbf' if bom else b'') + ''.join(result).encode('utf-8')
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--model', required=True, type=lambda p: Path(p).resolve())
parser.add_argument('--runtime', required=True, type=lambda p: Path(p).resolve())
parser.add_argument('--enable', action='store_true')
parser.add_argument('--backup-root', type=lambda p: Path(p).resolve(),
default=Path(os.environ.get('LOCALAPPDATA', str(Path.home()))) / 'd4r/backups')
args = parser.parse_args()
config = args.runtime / 'd4r.ini'
if not config.is_file():
parser.error('--runtime must contain d4r.ini')
if args.enable and args.backup_root.resolve().is_relative_to(args.runtime.parent):
parser.error('configuration backups must stay outside the game/runtime parent directory')
cache_data = (args.model / 'windows-native-python-cache.json').read_bytes()
cache = json.loads(cache_data)
identity = cache['identity']
if identity.get('preset') not in ('D', 'E') or identity.get('architecture') != 'gfx1101' or identity.get('arithmetic') != 'exact-gfx11':
raise RuntimeError('Unsupported local RR cache identity')
products = cache['products']
if not {'rr.txt', 'enc0.args', 'hkpn.args'}.issubset(products):
raise RuntimeError('RR cache omits required manifest/templates')
for name, expected in products.items():
if Path(name).name != name or name in ('.', '..') or '\\' in name or ':' in name:
raise RuntimeError('Invalid model product path: ' + name)
if sha((args.model / name).read_bytes()) != expected:
raise RuntimeError('Model product changed: ' + name)
manifest = (args.model / 'rr.txt').read_text()
stages = [line.split() for line in manifest.splitlines() if line.startswith('stage ')]
expected_stages = {'enc0', 'enc1', 'enc2', 'enc3', 'enc4', 'enc5', 'dec4', 'dec3', 'dec2', 'dec1', 'dec0', 'hkpn'}
if (not manifest.startswith('D4RR0001\n') or
f'preset {4 if identity["preset"] == "D" else 5}' not in manifest.splitlines() or
len(stages) != 12 or {fields[1] for fields in stages} != expected_stages or 'arithmetic gfx11' not in manifest):
raise RuntimeError('Incomplete RR model manifest')
for fields in stages:
if fields[2] not in products or fields[4] not in products:
raise RuntimeError('Stage product not covered by cache')
name = 'rr-' + identity['preset'].lower() + '-' + sha(cache_data)[:12]
destination = args.runtime / 'engine' / name
if not destination.resolve().is_relative_to(args.runtime):
raise RuntimeError('RR destination must stay within the selected runtime')
reused = destination.exists()
if not reused:
destination.mkdir(parents=True)
for product in products:
shutil.copy2(args.model / product, destination / product)
(destination / 'd4r-private-rr.json').write_text(json.dumps({'privateLocalModel': True, 'cacheSha256': sha(cache_data), 'products': products}, indent=2) + '\n')
for product, expected in products.items():
if sha((destination / product).read_bytes()) != expected:
raise RuntimeError('Existing/deployed RR model differs; preserving it: ' + str(destination))
backup = None
if args.enable:
original = config.read_bytes()
updated = select_native(original, destination.relative_to(args.runtime).as_posix())
if updated != original:
args.backup_root.mkdir(parents=True, exist_ok=True)
backup = args.backup_root / ('rr-config-' + uuid.uuid4().hex + '.ini')
with backup.open('xb') as stream:
stream.write(original)
temporary = config.with_name('d4r.ini.' + uuid.uuid4().hex + '.tmp')
with temporary.open('xb') as stream:
stream.write(updated)
if config.read_bytes() != original:
temporary.unlink()
raise RuntimeError('d4r.ini changed during deployment; preserved current configuration')
os.replace(temporary, config)
print(json.dumps({'deployed': str(destination), 'preset': identity['preset'], 'productsVerified': len(products),
'reused': reused, 'enabled': args.enable, 'configBackup': str(backup) if backup else None,
'privateLocalModel': True, 'published': False}, indent=2))
if __name__ == '__main__':
main()
+2
View File
@@ -119,6 +119,7 @@ struct Args {
std::string pixel_profile = "baseline";
std::string barrier_mode = "legacy";
bool early_indirect = false;
bool player_mode = false;
std::string ngx_core, dlss_dll;
std::string nvapi_dll;
std::string ngx_abi = "driver";
@@ -160,6 +161,7 @@ struct Args {
barrier_mode = value;
}
else if (key == "--early-indirect") early_indirect = value == "1";
else if (key == "--player-mode") player_mode = value == "1";
else if (key == "--ngx-core") ngx_core = value;
else if (key == "--dlss-dll") dlss_dll = value;
else if (key == "--nvapi-dll") nvapi_dll = value;
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include <windows.h>
#include <d3d12.h>
#include <dxgi1_4.h>
#include <cstdio>
#include <string>
// Native Windows uses the installed driver's normal D3D12 path. Do not import
// Proton's amdxc64 shim or Linux PCI/sysfs discovery into this process.
inline bool d4r_fsr_rr_driver_extension(ID3D12Device* device, std::string& detail)
{
LUID luid{};
device->GetAdapterLuid(&luid);
IDXGIFactory4* factory = nullptr;
if (FAILED(CreateDXGIFactory2(0, IID_PPV_ARGS(&factory)))) {
detail = "native Windows: DXGI factory query failed";
return false;
}
IDXGIAdapter1* adapter = nullptr;
const HRESULT result = factory->EnumAdapterByLuid(luid, IID_PPV_ARGS(&adapter));
factory->Release();
if (FAILED(result)) {
detail = "native Windows: device adapter query failed";
return false;
}
DXGI_ADAPTER_DESC1 desc{};
const HRESULT described = adapter->GetDesc1(&desc);
adapter->Release();
if (FAILED(described)) {
detail = "native Windows: adapter description failed";
return false;
}
char text[160];
std::snprintf(text, sizeof(text), "native Windows DXGI %04x:%04x; installed D3D12 driver path",
desc.VendorId, desc.DeviceId);
detail = text;
return true;
}
+64
View File
@@ -0,0 +1,64 @@
"""Adapt textual HIP helper IR to the pinned LLVM 22 reader without relaxing FP."""
import argparse
from pathlib import Path
import re
import struct
def float_hex(match):
bits = int(match[1], 16)
sign, exponent, fraction = bits >> 31, (bits >> 23) & 255, bits & 0x7fffff
if exponent:
wide_exponent = 2047 if exponent == 255 else exponent + 896
wide_fraction = fraction << 29
elif fraction:
leading = fraction.bit_length() - 1
wide_exponent = leading - 149 + 1023
wide_fraction = (fraction - (1 << leading)) << (52 - leading)
else:
wide_exponent = wide_fraction = 0
# LLVM's legacy float spelling uses the exact widening's binary64 bits.
# Integer operations preserve signed zero and signaling/quiet NaN payloads.
return f'0x{((sign << 63) | (wide_exponent << 52) | wide_fraction):016X}'
def compatible_ir(text):
text = re.sub(r'\bf0x([0-9A-Fa-f]{16})\b', r'0x\1', text)
text = re.sub(r'\bf0x([0-9A-Fa-f]{4})\b', r'0xH\1', text)
text = re.sub(r'\bf0x([0-9A-Fa-f]{8})\b', float_hex, text)
text = text.replace('denormal_fpenv(dynamic)', '"denormal-fp-math"="dynamic,dynamic"')
# This newer optimization promise is absent in LLVM 22. Dropping a promise
# weakens what the optimizer may assume; it does not change instructions.
text = re.sub(r'\bnocreateundeforpoison\b', '', text)
text = re.sub(r'\bmemory\([^)]*\btarget_mem\b[^)]*\)', '', text)
text = re.sub(r'\b(uitofp|sitofp|fptoui|fptosi)\s+(?:(?:nnan|ninf|nsz|arcp|contract|afn|reassoc|fast)\s+)+',
r'\1 ', text)
# LLVM 24 permits short decimal floats that the older reader rejects.
# Restrict conversion to unquoted literals whose nearest IR type is float/half;
# double literals and string attributes retain their original spelling.
result = []
decimal = re.compile(r'(?<![\w.])[-+]?\d+\.\d+(?:[eE][-+]?\d+)?(?![\w.])')
for line in text.splitlines():
pieces = re.split(r'("(?:[^"\\]|\\.)*")', line)
for index in range(0, len(pieces), 2):
piece = pieces[index]
def convert(match):
types = re.findall(r'\b(float|double|half|bfloat|fp128|i\d+)\b', piece[:match.start()])
if not types or types[-1] not in ('float', 'half'):
return match[0]
if types[-1] == 'half':
bits = struct.unpack('>H', struct.pack('>e', float(match[0])))[0]
return f'0xH{bits:04X}'
bits = struct.unpack('>I', struct.pack('>f', float(match[0])))[0]
return float_hex(re.match(r'(.*)', f'{bits:08X}'))
pieces[index] = decimal.sub(convert, piece)
result.append(''.join(pieces))
return '\n'.join(result) + '\n'
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('input', type=Path)
parser.add_argument('output', type=Path)
args = parser.parse_args()
args.output.write_text(compatible_ir(args.input.read_text()))
+1
View File
@@ -1,5 +1,6 @@
#pragma once
#include <cstdint>
#include <initializer_list>
struct ID3D12Resource;
extern "C" {
void d4r_ngx_set_ull(void*, const char*, unsigned long long);
@@ -0,0 +1,42 @@
#pragma once
#include "ngx_windows_runtime.h"
namespace d4r::win {
// Own the complete Init contract when native RR defers the independent SR
// runtime. The caller may mutate or free its strings/path list after Init.
struct RuntimeInitialization {
ComPtr<ID3D12Device> device;
unsigned long long application;
unsigned sdk;
std::wstring data;
ngx::FeatureCommonInfo common{};
std::vector<std::wstring> paths;
std::vector<const wchar_t*> pathPointers;
ngx::ProjectIdentity project;
bool hasCommon = false, hasProject = false;
RuntimeInitialization(ID3D12Device* d3d, unsigned long long app, const wchar_t* directory, unsigned version,
const ngx::FeatureCommonInfo* info, const ngx::ProjectIdentity* identity)
: device(d3d), application(app), sdk(version), data(directory && *directory ? directory : std::filesystem::current_path().wstring()) {
if (!d3d) throw std::runtime_error("Null NGX initialization device");
if (identity) { project = *identity; hasProject = true; }
if (info) {
if (info->PathListInfo.Length > 1024 || (info->PathListInfo.Length && !info->PathListInfo.Path))
throw std::runtime_error("Invalid NGX initialization path list");
hasCommon = true;
// Runtime consumes only path and logging information. InternalData
// is opaque and must not become a deferred borrowed pointer.
if (sdk >= 0x14) common.LoggingInfo = info->LoggingInfo;
for (unsigned i = 0; i < info->PathListInfo.Length; ++i)
if (info->PathListInfo.Path[i]) paths.emplace_back(info->PathListInfo.Path[i]);
for (const auto& path : paths) pathPointers.push_back(path.c_str());
common.PathListInfo = {pathPointers.data(), unsigned(pathPointers.size())};
}
}
RuntimeInitialization(const RuntimeInitialization&) = delete;
RuntimeInitialization& operator=(const RuntimeInitialization&) = delete;
std::shared_ptr<Runtime> create() const {
return std::make_shared<Runtime>(device.Get(), application, data.c_str(), sdk,
hasCommon ? &common : nullptr, hasProject ? &project : nullptr);
}
};
}
+45 -8
View File
@@ -26,6 +26,9 @@ struct Settings {
bool nativeFp8 = false;
bool asyncInterop = false, validateOutput = false, verbose = false, showWatermark = false;
std::wstring cacheDir;
std::wstring nativeDir;
std::string rrBackend = "off";
std::wstring rrModelDir;
};
struct State {
bool active = false;
@@ -122,6 +125,17 @@ inline Settings read_settings(const std::vector<IniEntry>& ini, std::vector<std:
notes.push_back("d4r.ini: [Debug] Log must be normal or verbose, not '" + entry.value + "'; using normal");
} else if (name == "paths.cachedir") {
if (!entry.value.empty() && lower(entry.value) != "auto") settings.cacheDir = diag::wide(entry.value);
} else if (name == "paths.nativedir") {
if (!entry.value.empty() && lower(entry.value) != "auto") settings.nativeDir = diag::wide(entry.value);
} else if (name == "rayreconstruction.backend") {
const auto value = lower(entry.value);
if (value.empty() || value == "auto" || value == "off") settings.rrBackend = "off";
else if (value == "native" || value == "fsr") {
settings.rrBackend = value;
if (value == "fsr") notes.push_back("d4r.ini: native Windows FSR RR is unavailable; RR requests will be rejected");
} else { settings.rrBackend = "off"; notes.push_back("d4r.ini: [RayReconstruction] Backend must be off, native or fsr; using off"); }
} else if (name == "rayreconstruction.modeldir") {
settings.rrModelDir = !entry.value.empty() && lower(entry.value) != "auto" ? diag::wide(entry.value) : L"";
} else notes.push_back("d4r.ini: [" + entry.section + "] " + entry.key + " is not a Windows setting; ignored");
}
return settings;
@@ -192,14 +206,31 @@ inline void verify_dll(const std::filesystem::path& dir, const std::vector<Pin>&
// The installer used to check these before touching the game; a drag-in
// install checks them at startup instead.
inline std::filesystem::path native_code_object_dir(const std::filesystem::path& dir) {
inline std::filesystem::path native_code_object_dir(const std::filesystem::path& dir, const Settings* settings = nullptr) {
if (settings && !settings->nativeDir.empty()) {
auto root = std::filesystem::path(settings->nativeDir);
if (root.is_relative()) root = dir / root;
const auto arch = root / diag::wide(diag::HipApi::target_arch());
return std::filesystem::is_directory(arch) ? arch : root;
}
const auto arch = dir / L"native" / diag::wide(diag::HipApi::target_arch());
return std::filesystem::is_directory(arch) ? arch : dir / L"native";
}
inline void verify_code_objects(const std::filesystem::path& dir) {
inline void verify_code_objects(const std::filesystem::path& dir, const Settings* settings = nullptr) {
const std::string arch = diag::HipApi::target_arch();
std::vector<std::filesystem::path> objects{dir / (L"pixel_convert_" + diag::wide(arch) + L".hsaco")};
const auto native = native_code_object_dir(dir);
const auto native = native_code_object_dir(dir, settings);
if (settings && !settings->nativeDir.empty() &&
(!std::filesystem::is_directory(native) || !std::filesystem::is_regular_file(native / L"d4r-kernels.txt")))
throw std::runtime_error("[Paths] NativeDir must contain the complete native kernel set and d4r-kernels.txt");
if (settings && !settings->nativeDir.empty()) {
for (const char* name : {"dltss_pwin_enc0_layer", "dltss_pwin_enc1_layer", "dltss_pwin_enc2_layer",
"dltss_pwin_enc3_layer", "dltss_pwin_enc4_layer", "dltss_pwin_dec5_layer", "dltss_pwin_dec4_layer",
"dltss_pwin_dec3_layer", "dltss_pwin_dec2_layer", "dltss_pwin_dec1_layer", "dltss_pwin_dec0_layer",
"rrlite_enc1_4x4", "rrlite_enc2_4x4", "rrlite_enc3_tube_4x4", "rrlite_dec2_4x4", "rrlite_dec1_4x4"})
if (!std::filesystem::is_regular_file(native / (std::string(name) + ".hsaco")))
throw std::runtime_error(std::string("[Paths] NativeDir is incomplete; missing ") + name);
}
if (!std::filesystem::is_directory(native)) throw std::runtime_error("d4r\\native (or d4r\\native\\" + arch + ") is missing; extract the d4r ZIP again");
for (const auto& entry : std::filesystem::directory_iterator(native))
if (entry.path().extension() == L".hsaco") objects.push_back(entry.path());
@@ -212,9 +243,9 @@ inline void verify_code_objects(const std::filesystem::path& dir) {
}
}
inline void verify_native_fp8_bundle(const std::filesystem::path& dir) {
inline void verify_native_fp8_bundle(const std::filesystem::path& dir, const Settings* settings = nullptr) {
const auto arch = diag::HipApi::target_arch();
const auto marker = native_code_object_dir(dir) / L"d4r-native-fp8.txt";
const auto marker = native_code_object_dir(dir, settings) / L"d4r-native-fp8.txt";
if (!std::filesystem::is_regular_file(marker))
throw std::runtime_error("[Kernels] NativeFp8=true needs a matching gfx12 token-lane M bundle; rebuild with -TokenLanes and restart.");
const auto contents = read_text(marker);
@@ -260,6 +291,10 @@ inline void redirect(FILE* crt, DWORD stream, const std::filesystem::path& path)
SetStdHandle(stream, reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(crt))));
}
inline std::filesystem::path rr_model_directory(const std::filesystem::path& dir, const Settings& settings) {
const std::filesystem::path model = settings.rrModelDir.empty() ? std::filesystem::path(L"engine/rr-d") : std::filesystem::path(settings.rrModelDir);
return std::filesystem::absolute(model.is_relative() ? dir / model : model).lexically_normal();
}
inline void apply(const std::filesystem::path& dir, const Settings& settings) {
std::filesystem::path cache = settings.cacheDir;
if (cache.empty()) {
@@ -276,6 +311,8 @@ inline void apply(const std::filesystem::path& dir, const Settings& settings) {
const auto compatNvapi = compat_nvapi_path(dir);
// Same codegen and paths as windows-game.ps1; only diagnostics differ.
const std::pair<const wchar_t*, std::optional<std::wstring>> values[] = {
{L"D4R_RR_BACKEND", settings.rrBackend == "off" ? unset : std::optional<std::wstring>(diag::wide(settings.rrBackend))},
{L"D4R_RR_MODEL_DIR", settings.rrBackend == "native" ? std::optional<std::wstring>(rr_model_directory(dir, settings).wstring()) : unset},
{L"D4R_HIP_ROOT", p(L"hip")}, {L"HIP_PATH", p(L"hip")},
{L"D4R_NVCUDA_DLL", p(L"zluda\\nvcuda.dll")}, {L"ZLUDA_CUDA_LIB", p(L"zluda\\nvcuda.dll")},
{L"D4R_NVAPI_DLL", std::optional<std::wstring>(compatNvapi.wstring())},
@@ -283,7 +320,7 @@ inline void apply(const std::filesystem::path& dir, const Settings& settings) {
{L"D4R_NGX_CORE", p(L"ngx\\_nvngx.dll")}, {L"D4R_DLSS_DLL", p(L"dlss\\nvngx_dlss.dll")},
{L"D4R_NGX_DATA_DIR", std::optional<std::wstring>(ngxData.wstring())},
{L"D4R_FORMAT_MODULE", p(L"pixel_convert_" + diag::wide(diag::HipApi::target_arch()) + L".hsaco")},
{L"D4R_ZLUDA_NATIVE_DIR", std::optional<std::wstring>(native_code_object_dir(dir).wstring())},{L"D4R_D3D12_COMMAND_BACKEND", on},
{L"D4R_ZLUDA_NATIVE_DIR", std::optional<std::wstring>(native_code_object_dir(dir, &settings).wstring())},{L"D4R_D3D12_COMMAND_BACKEND", on},
{L"D4R_ZLUDA_WMMA", on}, {L"D4R_ZLUDA_WMMA_FP8", on},
{L"D4R_ZLUDA_WMMA_FP8_NATIVE", settings.nativeFp8 ? on : off},
{L"D4R_ZLUDA_WMMA_F16_REFERENCE", on},
@@ -328,8 +365,8 @@ inline void ensure_environment() {
verify_dll(dir, pins, "dlss", L"dlss\\nvngx_dlss.dll", "nvngx_dlss.dll", current.notes);
verify_dll(dir, pins, "ngx", L"ngx\\_nvngx.dll", "_nvngx.dll", current.notes);
reject_translation_layer();
if (settings.nativeFp8) verify_native_fp8_bundle(dir);
verify_code_objects(dir);
if (settings.nativeFp8) verify_native_fp8_bundle(dir, &settings);
verify_code_objects(dir, &settings);
apply(dir, settings);
current.active = true; current.preset = settings.preset;
const char* model = settings.preset == 5 ? "E" : settings.preset == 12 ? "L" : settings.preset == 13 ? "M" : "K";
+22
View File
@@ -60,6 +60,28 @@ int wmain(int argc, wchar_t** argv) {
expect(!s.showWatermark && has_note(notes, "ShowWatermark must be true or false"), "invalid ShowWatermark falls back to hidden with a note");
s = settings("[Paths]\nCacheDir = D:\\d4r-cache\n", notes);
expect(s.cacheDir == L"D:\\d4r-cache" && notes.empty(), "CacheDir path");
s = settings("[Paths]\nNativeDir = private-native\n", notes);
expect(s.nativeDir == L"private-native" && notes.empty(), "NativeDir path");
expect(player::native_code_object_dir(dir, &s) == dir / L"private-native", "relative NativeDir resolves beside d4r.ini");
expect(!error_of([&] { player::verify_code_objects(dir, &s); }).empty(), "incomplete private native directory rejected");
s.cacheDir = (dir / L"cache").wstring();
player::apply(dir, s);
expect(std::wstring(_wgetenv(L"D4R_ZLUDA_NATIVE_DIR")) == (dir / L"private-native").wstring(),
"player environment selects configured private native directory");
s = settings("[Paths]\nNativeDir = auto\n", notes);
expect(s.nativeDir.empty() && notes.empty(), "NativeDir auto preserves packaged kernels");
s = settings("[RayReconstruction]\nBackend = NATIVE\nModelDir = local-rr\n", notes);
expect(s.rrBackend == "native" && s.rrModelDir == L"local-rr" && notes.empty(), "native RR settings parse independently of SR");
expect(player::rr_model_directory(dir, s) == std::filesystem::absolute(dir / L"local-rr").lexically_normal(), "relative RR model resolves beside d4r.ini");
s.cacheDir = (dir / L"cache").wstring(); player::apply(dir, s);
expect(std::string(std::getenv("D4R_RR_BACKEND")) == "native" &&
std::filesystem::path(_wgetenv(L"D4R_RR_MODEL_DIR")) == player::rr_model_directory(dir, s), "native RR environment selected without a launcher");
s = settings("[RayReconstruction]\nBackend = fsr\n", notes);
expect(s.rrBackend == "fsr" && has_note(notes, "FSR RR is unavailable"), "unavailable FSR selection remains explicit");
s = settings("[RayReconstruction]\nBackend = typo\n", notes);
expect(s.rrBackend == "off" && has_note(notes, "Backend must be"), "invalid RR backend stays off");
s = settings("", notes); s.cacheDir = (dir / L"cache").wstring(); player::apply(dir, s);
expect(!std::getenv("D4R_RR_BACKEND") && !std::getenv("D4R_RR_MODEL_DIR"), "default RR off clears stale selection");
std::filesystem::create_directories(dir / L"nvapi");
{ std::ofstream(dir / L"nvapi" / L"nvapi64.dll", std::ios::binary) << "generic nvapi fixture"; }
+125
View File
@@ -0,0 +1,125 @@
"""Prepare a private portable RR model-tool bundle from pinned dependencies.
Downloads/extracts tools locally. Does not install Python, use pip, modify PATH,
change policy, build a model, or install into a game.
"""
import argparse
import hashlib
import json
from pathlib import Path, PurePosixPath
import shutil
import subprocess
import urllib.request
import uuid
import zipfile
DEPENDENCIES = {
'python-3.12.10-embed-amd64.zip': (
'https://www.python.org/ftp/python/3.12.10/python-3.12.10-embed-amd64.zip',
'4acbed6dd1c744b0376e3b1cf57ce906f9dc9e95e68824584c8099a63025a3c3'),
'numpy-2.4.6-cp312-cp312-win_amd64.whl': (
'https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl',
'd8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751'),
'glslang-16.6.0-windows-x86_64-release.zip': (
'https://github.com/KhronosGroup/glslang/releases/download/16.6.0/glslang-16.6.0-windows-x86_64-release.zip',
'82bf434e69b9bb4829de7e2b4bc2c5e7a7861e53d66cf75e5cc70f5f694a8d9b'),
'glslang-16.6.0-LICENSE.txt': (
'https://raw.githubusercontent.com/KhronosGroup/glslang/16.6.0/LICENSE.txt',
'17e70c676e1521ff3e4686f04a2053d93a7e28a33be8de7ec37ab0ff72feb677'),
}
def sha(path):
with path.open('rb') as stream:
return hashlib.file_digest(stream, 'sha256').hexdigest()
def extract(archive, destination):
with zipfile.ZipFile(archive) as source:
for entry in source.infolist():
name = PurePosixPath(entry.filename)
if name.is_absolute() or '..' in name.parts or '\\' in entry.filename or ':' in entry.filename:
raise RuntimeError('Unsafe dependency archive path')
if entry.is_dir():
continue
target = destination.joinpath(*name.parts)
target.parent.mkdir(parents=True, exist_ok=True)
with source.open(entry) as data, target.open('xb') as out:
shutil.copyfileobj(data, out)
def main():
repo = Path(__file__).resolve().parents[2]
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output', required=True, type=lambda p: Path(p).resolve())
parser.add_argument('--archive', type=lambda p: Path(p).resolve(), help='optional clean portable-tools ZIP, before any model build')
parser.add_argument('--download-cache', type=lambda p: Path(p).resolve(), default=repo / '.tools/downloads')
args = parser.parse_args()
if args.output.exists() or not args.output.is_relative_to((repo / 'build').resolve()):
parser.error('use a fresh output directory under ignored build/')
if args.archive and (args.archive.exists() or not args.archive.is_relative_to((repo / 'build').resolve()) or args.archive.is_relative_to(args.output)):
parser.error('use a fresh archive under build/, outside the bundle directory')
args.download_cache.mkdir(parents=True, exist_ok=True)
for name, (url, expected) in DEPENDENCIES.items():
path = args.download_cache / name
if not path.exists():
print('RR_TOOLS_DOWNLOAD ' + name, flush=True)
temporary = path.with_name(name + '.part-' + uuid.uuid4().hex)
with urllib.request.urlopen(url, timeout=60) as response, temporary.open('xb') as out:
shutil.copyfileobj(response, out)
if sha(temporary) != expected:
raise RuntimeError('Dependency download hash mismatch: ' + name)
temporary.rename(path)
if sha(path) != expected:
raise RuntimeError('Cached dependency changed: ' + name)
python = args.output / 'python'
recipe = args.output / 'recipe'
extract(args.download_cache / 'python-3.12.10-embed-amd64.zip', python)
extract(args.download_cache / 'numpy-2.4.6-cp312-cp312-win_amd64.whl', recipe / '.tools/python/vendor')
glslang = recipe / '.tools/glslang-16.6.0'
extract(args.download_cache / 'glslang-16.6.0-windows-x86_64-release.zip', glslang)
shutil.copy2(glslang / 'bin/glslang.exe', glslang / 'bin/glslangValidator.exe')
if sha(glslang / 'bin/glslangValidator.exe') != '9643709c02c83e26773f96b6a87f197ab2e84f78be0879ad8fb3619418b5d9ad':
raise RuntimeError('Extracted glslang identity mismatch')
files = subprocess.check_output(['git', 'ls-files', '-z', 'engine', 'vulkan', 'kernels/tools'], cwd=repo).decode().split('\0')
suffixes = {'.py', '.comp', '.glsl', '.h', '.inc', '.sh'}
sources = [repo / name for name in files if name and Path(name).suffix in suffixes]
sources += [repo / 'engine/rr/native_kernels.txt', repo / 'tools/windows/build_rr_model.py', repo / 'tools/windows/deploy_rr_model.py', repo / 'LICENSE']
for source in sources:
target = recipe / source.relative_to(repo)
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, target)
# Keep isolated imports: no user site-packages or PYTHONPATH dependence.
paths = ['python312.zip', '.', '../recipe', '../recipe/engine', '../recipe/engine/rr',
'../recipe/kernels/tools', '../recipe/vulkan', '../recipe/tools/windows', '../recipe/.tools/python/vendor']
(python / 'python312._pth').write_text('\n'.join(paths) + '\n')
licenses = args.output / 'licenses'
licenses.mkdir()
shutil.copy2(args.download_cache / 'glslang-16.6.0-LICENSE.txt', licenses / 'glslang-LICENSE.txt')
shutil.copy2(python / 'LICENSE.txt', licenses / 'Python-LICENSE.txt')
for source in (recipe / '.tools/python/vendor/numpy-2.4.6.dist-info').glob('licenses/*'):
if source.is_file():
shutil.copy2(source, licenses / ('numpy-' + source.name))
manifest = {'kind': 'local-rr-model-tools', 'dependencies': {name: {'url': url, 'sha256': digest}
for name, (url, digest) in DEPENDENCIES.items()}, 'sourceWorkingTree': 'local modified; file hashes authoritative',
'files': {path.relative_to(args.output).as_posix(): sha(path) for path in sorted(args.output.rglob('*')) if path.is_file()},
'modelsBundled': False, 'nvidiaBinariesBundled': False}
(args.output / 'rr-tools.json').write_text(json.dumps(manifest, indent=2) + '\n')
if args.archive:
args.archive.parent.mkdir(parents=True, exist_ok=True)
# Package only the recorded pristine tool inputs. Never sweep a used
# bundle's model-build directory into a distributable archive.
with zipfile.ZipFile(args.archive, 'x', zipfile.ZIP_DEFLATED, compresslevel=6) as archive:
for name in (*manifest['files'], 'rr-tools.json'):
archive.write(args.output / name, name)
with zipfile.ZipFile(args.archive) as archive:
for name, expected in manifest['files'].items():
if hashlib.sha256(archive.read(name)).hexdigest() != expected:
raise RuntimeError('Portable-tools ZIP hash mismatch: ' + name)
print('RR_TOOLS_ARCHIVE sha256=' + sha(args.archive) + ' bytes=' + str(args.archive.stat().st_size))
print('RR_TOOLS_READY ' + str(args.output))
if __name__ == '__main__':
main()
+93
View File
@@ -0,0 +1,93 @@
"""Refresh the verified gfx1101 development ZIP. Never creates a public release."""
import argparse
import hashlib
import json
from pathlib import Path, PurePosixPath
import shutil
import subprocess
import zipfile
def sha(path):
with path.open('rb') as stream:
return hashlib.file_digest(stream, 'sha256').hexdigest()
def main():
parser = argparse.ArgumentParser(description=__doc__)
for name in ('base-zip', 'shim', 'output', 'archive'):
parser.add_argument('--' + name, required=True, type=lambda value: Path(value).resolve())
args = parser.parse_args()
repo = Path(__file__).resolve().parents[2]
build = (repo / 'build').resolve()
for path in (args.output, args.archive):
if not path.is_relative_to(build) or path.exists():
parser.error('use fresh output/archive paths under ignored build/')
if args.archive.is_relative_to(args.output):
parser.error('archive must be outside the package directory')
base_hash = sha(args.base_zip)
if base_hash != 'e93be288f4013616cc2229be0132b40395d5031d8733bfa63886ba75ce1389ca':
parser.error('base must be the verified local v3 ZIP')
with zipfile.ZipFile(args.base_zip) as base:
manifest = json.loads(base.read('d4r/package.json').decode('utf-8-sig'))
if not manifest.get('localDevelopment') or set(manifest['targets']) != {'gfx1101'}:
raise RuntimeError('Unexpected base package identity')
records = {entry['path']: entry for entry in manifest['files']}
names = [entry.filename for entry in base.infolist() if not entry.is_dir()]
if len(names) != len(set(names)) or set(names) != set(records) | {'d4r/package.json'}:
raise RuntimeError('Base ZIP entries differ from manifest')
for name in names:
parts = PurePosixPath(name)
if parts.is_absolute() or '..' in parts.parts or '\\' in name or ':' in name:
raise RuntimeError('Unsafe ZIP path: ' + name)
data = base.read(name)
if name in records and hashlib.sha256(data).hexdigest().upper() != records[name]['sha256'].upper():
raise RuntimeError('Base ZIP hash mismatch: ' + name)
destination = args.output.joinpath(*parts.parts)
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_bytes(data)
overlays = {
'd4r/_nvngx_gfx1101.dll': args.shim,
'd4r/d4r.ini': repo / 'packaging/windows/d4r.ini',
'WINDOWS_README.txt': repo / 'packaging/windows/WINDOWS_README.txt',
'd4r/d4r-check.ps1': repo / 'scripts/windows/d4r-check.ps1',
'install.bat': repo / 'packaging/windows/install.bat',
'install-dlss.ps1': repo / 'scripts/windows/install-dlss.ps1',
'd4r/dll-pins.txt': repo / 'tools/windows/dll-pins.txt',
}
for name, source in overlays.items():
if name not in records:
raise RuntimeError('Overlay is not part of the validated package layout: ' + name)
shutil.copy2(source, args.output / name)
records[name] = {'path': name, 'sha256': sha(source).upper(), 'version': None}
prohibited = {'nvngx_dlss.dll', 'nvngx_dlssd.dll'}
for path in args.output.rglob('*'):
if path.is_file() and (path.name.lower() in prohibited or path.suffix.lower() in ('.spv', '.ptx', '.cubin', '.weights')):
raise RuntimeError('Proprietary/model artifact cannot be bundled: ' + str(path))
manifest.update(localDevelopment=True, packageWorkingTreeDirty=True, binarySourceCommit=None,
packageCommit=subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=repo, text=True).strip(),
files=list(records.values()),
localRefresh={'baseZipSha256': base_hash, 'binaryProvenance': 'local modified build; hashes authoritative',
'overlays': {name: sha(path) for name, path in overlays.items()},
'rr': 'experimental synthetic validation; disabled by default; models generated separately'})
manifest['targets']['gfx1101']['binarySourceCommit'] = None
manifest['targets']['gfx1101']['hardwareValidation'] = 'SR prior limited evidence; native RR synthetic NGX lifecycle only; no game RR or performance parity'
(args.output / 'd4r/package.json').write_text(json.dumps(manifest, indent=2) + '\n')
args.archive.parent.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(args.archive, 'x', zipfile.ZIP_DEFLATED, compresslevel=6) as archive:
for path in sorted(args.output.rglob('*')):
if path.is_file():
archive.write(path, path.relative_to(args.output).as_posix())
with zipfile.ZipFile(args.archive) as archive:
for name, entry in records.items():
if hashlib.sha256(archive.read(name)).hexdigest().upper() != entry['sha256'].upper():
raise RuntimeError('Refreshed ZIP hash mismatch: ' + name)
report = {'zip': str(args.archive), 'sha256': sha(args.archive), 'bytes': args.archive.stat().st_size,
'allHashesVerified': True, 'privateOverrideBundled': False, 'rrModelBundled': False,
'sourceCommitCreated': False, 'published': False, 'baseZipSha256': base_hash}
args.archive.with_suffix('.validation.json').write_text(json.dumps(report, indent=2) + '\n')
print(json.dumps(report, indent=2))
if __name__ == '__main__':
main()
+326
View File
@@ -0,0 +1,326 @@
#pragma once
#include <d3d12.h>
#include <wrl/client.h>
#include "../../engine/runtime.h"
#include "binary16.h"
#include <array>
#include <filesystem>
#include <fstream>
#include <memory>
#include <limits>
#include <algorithm>
#include <set>
#include <cstdio>
#include <cstring>
#include <string>
// Canonical-input replay through the native D3D12/Vulkan shared packet. The
// packet is GPU memory at the API boundary; CPU access is fixture upload/QA only.
namespace rr_bridge {
inline void check(VkResult r, const char* operation) {
if (r != VK_SUCCESS) throw std::runtime_error(std::string(operation) + " VkResult=" + std::to_string(r));
}
inline std::vector<char> read(const std::filesystem::path& path) {
std::ifstream f(path, std::ios::binary | std::ios::ate);
if (!f) throw std::runtime_error("Cannot read RR fixture: " + path.string());
std::vector<char> result(size_t(f.tellg())); f.seekg(0); f.read(result.data(), std::streamsize(result.size()));
if (!f) throw std::runtime_error("Incomplete RR fixture read");
return result;
}
struct Frame { uint32_t id; d4r::RFrameSettings settings; std::array<bool, 4> optional{}; };
struct Plane {
const char* name; VkFormat format; uint32_t bytes;
VkDeviceSize offset = 0, pitch = 0;
bool enabled = true;
};
struct Layout {
uint32_t rw = 0, rh = 0, ow = 0, oh = 0;
std::array<Plane, 11> planes{{
{"color", VK_FORMAT_R16G16B16A16_SFLOAT, 8}, {"motion", VK_FORMAT_R16G16_SFLOAT, 4},
{"depth", VK_FORMAT_R32_SFLOAT, 4}, {"normals", VK_FORMAT_R16G16B16A16_SFLOAT, 8},
{"roughness", VK_FORMAT_R16G16B16A16_SFLOAT, 8}, {"diffuse", VK_FORMAT_R8G8B8A8_UNORM, 4},
{"specular", VK_FORMAT_R8G8B8A8_UNORM, 4},
{"hitdistance", VK_FORMAT_R32_SFLOAT, 4, 0, 0, false},
{"disocclusion", VK_FORMAT_R16_SFLOAT, 2, 0, 0, false},
{"sss", VK_FORMAT_R16_SFLOAT, 2, 0, 0, false},
{"particles", VK_FORMAT_R16G16B16A16_SFLOAT, 8, 0, 0, false}}};
VkDeviceSize bytes = 0, outputOffset = 0, outputPitch = 0;
std::filesystem::path inputs, output, reference;
std::vector<Frame> frames;
// Runtime layout construction performs no fixture I/O. Both callers use
// the same aligned packet contract for native texture transfers.
Layout(uint32_t renderWidth, uint32_t renderHeight, uint32_t outputWidth, uint32_t outputHeight)
: rw(renderWidth), rh(renderHeight), ow(outputWidth), oh(outputHeight) {
if (!rw || !rh || !ow || !oh || rw > 16384 || rh > 16384 || ow > 16384 || oh > 16384)
throw std::runtime_error("Invalid RR packet geometry");
initializePacket();
}
void initializePacket() {
bytes = 0;
for (auto& plane : planes) {
bytes = (bytes + 511) & ~VkDeviceSize(511); plane.offset = bytes;
plane.pitch = (VkDeviceSize(rw) * plane.bytes + 255) & ~VkDeviceSize(255);
bytes += plane.pitch * rh;
}
outputOffset = (bytes + 511) & ~VkDeviceSize(511);
outputPitch = (VkDeviceSize(ow) * 8 + 255) & ~VkDeviceSize(255);
bytes = outputOffset + outputPitch * oh;
}
Layout(const std::string& inputDir, const std::string& outputDir, const std::string& referenceDir)
: inputs(inputDir), output(outputDir), reference(referenceDir) {
std::ifstream f(inputs / "frames.txt"); std::string token;
if (!(f >> token >> rw >> rh >> ow >> oh) || token != "size" || rw < 32 || rh < 32 ||
rw > 1024 || rh > 1024 || !ow || !oh || ow > 2560 || oh > 1440)
throw std::runtime_error("Invalid bounded RR replay geometry");
while (f >> token) {
Frame frame{}; int reset, mode;
auto& s = frame.settings;
if (token != "frame" || !(f >> frame.id >> s.jitter[0] >> s.jitter[1] >> s.motionScale[0] >>
s.motionScale[1] >> s.preExposure >> reset >> mode)) throw std::runtime_error("Invalid RR frame header");
for (auto& value : s.worldToView) if (!(f >> value)) throw std::runtime_error("Invalid RR view matrix");
for (auto& value : s.viewToClip) if (!(f >> value)) throw std::runtime_error("Invalid RR projection matrix");
s.reset = reset != 0; s.depthInverted = (mode & 1) != 0; s.hardwareDepth = (mode & 2) != 0;
s.roughnessPacked = (mode & 4) != 0;
if (!std::isfinite(s.preExposure) || s.preExposure <= 0 ||
!std::isfinite(s.jitter[0]) || !std::isfinite(s.jitter[1]) ||
!std::isfinite(s.motionScale[0]) || !std::isfinite(s.motionScale[1]))
throw std::runtime_error("Nonfinite or invalid RR frame settings");
for (float value : s.worldToView) if (!std::isfinite(value)) throw std::runtime_error("Nonfinite RR view matrix");
for (float value : s.viewToClip) if (!std::isfinite(value)) throw std::runtime_error("Nonfinite RR projection matrix");
frames.push_back(frame);
if (frames.size() > 32) throw std::runtime_error("RR replay is limited to 32 frames");
}
if (frames.empty()) throw std::runtime_error("RR fixture has no frames");
std::set<uint32_t> ids;
for (auto& frame : frames) {
if (!ids.insert(frame.id).second) throw std::runtime_error("Duplicate RR frame ID");
for (size_t i = 7; i < planes.size(); ++i) {
frame.optional[i - 7] = std::filesystem::exists(inputs / (std::to_string(frame.id) + "-" + planes[i].name + ".bin"));
planes[i].enabled = planes[i].enabled || frame.optional[i - 7];
}
}
initializePacket();
std::filesystem::create_directories(output);
for (const auto& frame : frames)
if (std::filesystem::exists(output / (std::to_string(frame.id) + "-out.bin")))
throw std::runtime_error("Use a fresh RR output directory");
}
void upload(void* destination, unsigned frame) const {
std::memset(destination, 0, size_t(bytes));
for (const auto& p : planes) {
if (!p.enabled) continue;
const auto index = size_t(&p - planes.data());
if (index >= 7 && !frames.at(frame).optional[index - 7]) continue;
auto data = read(inputs / (std::to_string(frames.at(frame).id) + "-" + p.name + ".bin"));
if (data.size() != size_t(rw) * rh * p.bytes) throw std::runtime_error("RR input size mismatch");
for (uint32_t y = 0; y < rh; ++y)
std::memcpy(static_cast<char*>(destination) + p.offset + y * p.pitch,
data.data() + size_t(y) * rw * p.bytes, size_t(rw) * p.bytes);
}
}
void validate(const void* source, unsigned frame) const {
std::vector<char> packed(size_t(ow) * oh * 8);
for (uint32_t y = 0; y < oh; ++y)
std::memcpy(packed.data() + size_t(y) * ow * 8,
static_cast<const char*>(source) + outputOffset + y * outputPitch, size_t(ow) * 8);
double low = std::numeric_limits<double>::infinity(), high = -low;
for (size_t i = 0; i < packed.size() / 2; ++i) {
uint16_t bits; std::memcpy(&bits, packed.data() + i * 2, 2);
const double value = d4r::diag::binary16::half_value(bits);
if (!std::isfinite(value)) throw std::runtime_error("RR output contains NaN/Inf");
if (i % 4 != 3) { low = std::min(low, value); high = std::max(high, value); }
}
if (!(high - low > 1e-5)) throw std::runtime_error("RR output is uniform");
const auto name = std::to_string(frames.at(frame).id) + "-out.bin";
if (!reference.empty() && read(reference / name) != packed)
throw std::runtime_error("Bridged RR output differs from standalone replay");
std::ofstream file(output / name, std::ios::binary); file.write(packed.data(), std::streamsize(packed.size()));
if (!file) throw std::runtime_error("RR output write failed");
std::printf("RR_BRIDGED_FRAME id=%u finite=1 nonuniform=1 standalone_exact=%u output=%ux%u\n",
frames[frame].id, !reference.empty(), ow, oh);
}
};
struct D3D12Frames {
const Layout& layout;
std::array<Microsoft::WRL::ComPtr<ID3D12Resource>, 11> inputs;
Microsoft::WRL::ComPtr<ID3D12Resource> output;
static void checkD3D(HRESULT r) { if (FAILED(r)) throw std::runtime_error("RR D3D12 texture operation failed: " + std::to_string(r)); }
static DXGI_FORMAT format(VkFormat f) {
switch (f) {
case VK_FORMAT_R16G16B16A16_SFLOAT: return DXGI_FORMAT_R16G16B16A16_FLOAT;
case VK_FORMAT_R16G16_SFLOAT: return DXGI_FORMAT_R16G16_FLOAT;
case VK_FORMAT_R32_SFLOAT: return DXGI_FORMAT_R32_FLOAT;
case VK_FORMAT_R8G8B8A8_UNORM: return DXGI_FORMAT_R8G8B8A8_UNORM;
case VK_FORMAT_R32G32B32A32_SFLOAT: return DXGI_FORMAT_R32G32B32A32_FLOAT;
case VK_FORMAT_R32G32_SFLOAT: return DXGI_FORMAT_R32G32_FLOAT;
case VK_FORMAT_R16_SFLOAT: return DXGI_FORMAT_R16_FLOAT;
case VK_FORMAT_R8_UNORM: return DXGI_FORMAT_R8_UNORM;
case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return DXGI_FORMAT_R10G10B10A2_UNORM;
default: throw std::runtime_error("Unsupported canonical RR D3D12 format");
}
}
static void barrier(ID3D12GraphicsCommandList* cb, ID3D12Resource* image, D3D12_RESOURCE_STATES from, D3D12_RESOURCE_STATES to) {
D3D12_RESOURCE_BARRIER b{}; b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
b.Transition = {image, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, from, to}; cb->ResourceBarrier(1, &b);
}
static D3D12_TEXTURE_COPY_LOCATION placed(ID3D12Resource* resource, VkDeviceSize offset, VkDeviceSize pitch,
DXGI_FORMAT f, uint32_t width, uint32_t height) {
D3D12_TEXTURE_COPY_LOCATION result{}; result.pResource = resource;
result.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; result.PlacedFootprint.Offset = offset;
result.PlacedFootprint.Footprint = {f, width, height, 1, UINT(pitch)}; return result;
}
static D3D12_TEXTURE_COPY_LOCATION texture(ID3D12Resource* resource) {
D3D12_TEXTURE_COPY_LOCATION result{}; result.pResource = resource;
result.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; return result;
}
D3D12Frames(const Layout& l, ID3D12Device* device, DXGI_FORMAT depthFormat = DXGI_FORMAT_UNKNOWN) : layout(l) {
auto create = [&](DXGI_FORMAT f, uint32_t width, uint32_t height, bool isOutput, ID3D12Resource** resource) {
D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT;
D3D12_RESOURCE_DESC desc{}; desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; desc.Format = f;
desc.Width = width; desc.Height = height; desc.DepthOrArraySize = desc.MipLevels = 1; desc.SampleDesc.Count = 1;
if (isOutput) desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
if (f == DXGI_FORMAT_D32_FLOAT || f == DXGI_FORMAT_R32G8X24_TYPELESS || f == DXGI_FORMAT_D32_FLOAT_S8X24_UINT)
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
checkD3D(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc,
isOutput ? D3D12_RESOURCE_STATE_UNORDERED_ACCESS : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
nullptr, IID_PPV_ARGS(resource)));
};
for (size_t i = 0; i < inputs.size(); ++i) if (l.planes[i].enabled)
create(i == 2 && depthFormat != DXGI_FORMAT_UNKNOWN ? depthFormat : format(l.planes[i].format), l.rw, l.rh, false, inputs[i].GetAddressOf());
create(DXGI_FORMAT_R16G16B16A16_FLOAT, l.ow, l.oh, true, output.GetAddressOf());
}
void recordInputs(ID3D12GraphicsCommandList* cb, ID3D12Resource* upload, ID3D12Resource* shared, uint32_t optionalMask = 15) {
for (size_t i = 0; i < inputs.size(); ++i) {
if (!inputs[i]) continue;
if (i >= 7 && !(optionalMask & (1u << (i - 7)))) continue;
auto* input = inputs[i].Get(); const auto& plane = layout.planes[i];
auto copyFormat = format(plane.format);
if (i == 2) {
Microsoft::WRL::ComPtr<ID3D12Device> device; checkD3D(input->GetDevice(IID_PPV_ARGS(&device)));
D3D12_RESOURCE_DESC desc{};
#if defined(__MINGW32__)
input->GetDesc(&desc);
#else
desc = input->GetDesc();
#endif
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint{};
device->GetCopyableFootprints(&desc, 0, 1, 0, &footprint, nullptr, nullptr, nullptr);
copyFormat = footprint.Footprint.Format;
}
auto src = placed(upload, plane.offset, plane.pitch, copyFormat, layout.rw, layout.rh);
auto image = texture(input);
barrier(cb, input, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COPY_DEST);
cb->CopyTextureRegion(&image, 0, 0, 0, &src, nullptr);
barrier(cb, input, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COPY_SOURCE);
auto dst = placed(shared, plane.offset, plane.pitch, copyFormat, layout.rw, layout.rh);
cb->CopyTextureRegion(&dst, 0, 0, 0, &image, nullptr);
barrier(cb, input, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
}
}
void recordOutput(ID3D12GraphicsCommandList* cb, ID3D12Resource* shared, ID3D12Resource* readback) {
auto src = placed(shared, layout.outputOffset, layout.outputPitch, DXGI_FORMAT_R16G16B16A16_FLOAT, layout.ow, layout.oh);
auto image = texture(output.Get());
barrier(cb, output.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_DEST);
cb->CopyTextureRegion(&image, 0, 0, 0, &src, nullptr);
barrier(cb, output.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COPY_SOURCE);
auto dst = placed(readback, layout.outputOffset, layout.outputPitch, DXGI_FORMAT_R16G16B16A16_FLOAT, layout.ow, layout.oh);
cb->CopyTextureRegion(&dst, 0, 0, 0, &image, nullptr);
barrier(cb, output.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
}
};
struct Image {
VkFormat format = VK_FORMAT_UNDEFINED; bool initialized = false;
VkDevice device; VkImage image = VK_NULL_HANDLE; VkDeviceMemory memory = VK_NULL_HANDLE; VkImageView view = VK_NULL_HANDLE;
~Image() { if (view) vkDestroyImageView(device, view, nullptr); if (image) vkDestroyImage(device, image, nullptr); if (memory) vkFreeMemory(device, memory, nullptr); }
};
struct Replay {
const Layout& layout;
VkPhysicalDevice physical; VkDevice device;
std::array<std::unique_ptr<Image>, 11> images;
std::array<std::vector<std::unique_ptr<Image>>, 11> alternatives;
std::unique_ptr<d4r::RModel> model;
std::unique_ptr<d4r::REngine> engine;
bool first = true;
Replay(const Layout& l, VkPhysicalDevice physical, VkDevice device, const std::string& directory)
: layout(l), physical(physical), device(device) {
const auto data = read(std::filesystem::path(directory) / "rr.txt");
const std::string manifest(data.begin(), data.end());
if (manifest.rfind("D4RR0001", 0) != 0 || manifest.find("arithmetic gfx11") == std::string::npos)
throw std::runtime_error("This canonical RR bridge gate requires a D/E gfx11 model");
for (size_t i = 0; i < images.size(); ++i) if (l.planes[i].enabled) configure(i, l.planes[i].format);
model = std::make_unique<d4r::RModel>(physical, device, directory);
engine = std::make_unique<d4r::REngine>(*model, l.rw, l.rh, l.ow, l.oh);
}
// Cache views by format: REngine caches descriptors referencing old views,
// so they must remain alive until the engine is destroyed.
void configure(size_t i, VkFormat format) {
if (images.at(i) && images[i]->format == format) return;
for (auto& previous : alternatives[i]) if (previous->format == format) {
images[i].swap(previous); return;
}
VkFormatProperties properties{}; vkGetPhysicalDeviceFormatProperties(physical, format, &properties);
const auto required = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT;
if ((properties.optimalTilingFeatures & required) != required)
throw std::runtime_error("RR input format lacks Vulkan sampled/transfer support");
VkPhysicalDeviceMemoryProperties memory; vkGetPhysicalDeviceMemoryProperties(physical, &memory);
auto image = std::make_unique<Image>(); image->device = device; image->format = format;
VkImageCreateInfo ci{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; ci.imageType = VK_IMAGE_TYPE_2D;
ci.format = format; ci.extent = {layout.rw, layout.rh, 1}; ci.mipLevels = ci.arrayLayers = 1;
ci.samples = VK_SAMPLE_COUNT_1_BIT; ci.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
check(vkCreateImage(device, &ci, nullptr, &image->image), "RR input image");
VkMemoryRequirements req; vkGetImageMemoryRequirements(device, image->image, &req);
uint32_t type = memory.memoryTypeCount;
for (uint32_t n = 0; n < memory.memoryTypeCount; ++n)
if ((req.memoryTypeBits & (1u << n)) && (memory.memoryTypes[n].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { type = n; break; }
if (type == memory.memoryTypeCount) throw std::runtime_error("No RR image memory type");
VkMemoryAllocateInfo ai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; ai.allocationSize = req.size; ai.memoryTypeIndex = type;
check(vkAllocateMemory(device, &ai, nullptr, &image->memory), "RR image memory");
check(vkBindImageMemory(device, image->image, image->memory, 0), "Bind RR image");
VkImageViewCreateInfo vi{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; vi.image = image->image;
vi.viewType = VK_IMAGE_VIEW_TYPE_2D; vi.format = ci.format; vi.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
check(vkCreateImageView(device, &vi, nullptr, &image->view), "RR input view");
if (images[i]) alternatives[i].push_back(std::move(images[i]));
images[i] = std::move(image);
}
void record(VkCommandBuffer cb, VkBuffer shared, unsigned frame) {
uint32_t mask = 0;
for (unsigned i = 0; i < 4; ++i) if (layout.frames.at(frame).optional[i]) mask |= 1u << i;
record(cb, shared, layout.frames.at(frame).settings, mask);
}
// Own settings at the NGX submission boundary; never retain game pointers.
void record(VkCommandBuffer cb, VkBuffer shared, const d4r::RFrameSettings& settings, uint32_t optionalMask = 15) {
for (size_t i = 0; i < images.size(); ++i) {
if (!layout.planes[i].enabled) continue;
if (i >= 7 && !(optionalMask & (1u << (i - 7)))) continue;
VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; b.image = images[i]->image;
b.oldLayout = !images[i]->initialized ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_GENERAL; b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
b.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
b.srcAccessMask = !images[i]->initialized ? 0 : VK_ACCESS_SHADER_READ_BIT; b.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
const auto& plane = layout.planes[i];
VkBufferImageCopy copy{}; copy.bufferOffset = plane.offset; copy.bufferRowLength = uint32_t(plane.pitch / plane.bytes);
copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; copy.imageExtent = {layout.rw, layout.rh, 1};
vkCmdCopyBufferToImage(cb, shared, images[i]->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy);
images[i]->initialized = true;
b.oldLayout = b.newLayout; b.newLayout = VK_IMAGE_LAYOUT_GENERAL;
b.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; b.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
}
d4r::RFrameImages inputs{}; inputs.color = images[0]->view; inputs.motion = images[1]->view; inputs.depth = images[2]->view;
inputs.normals = images[3]->view; inputs.roughness = images[4]->view; inputs.diffuseAlbedo = images[5]->view; inputs.specularAlbedo = images[6]->view;
VkImageView* optional[] = {&inputs.specularHitDistance, &inputs.disocclusionMask, &inputs.subsurfaceGuide, &inputs.colorBeforeParticles};
for (size_t i = 7; i < images.size(); ++i) if (layout.planes[i].enabled && (optionalMask & (1u << (i - 7)))) *optional[i - 7] = images[i]->view;
engine->setFrame(inputs, settings);
if (first) { model->recordUpload(cb); engine->recordInitialize(cb); first = false; }
engine->recordFrame(cb);
VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; b.image = engine->outputImage();
b.oldLayout = b.newLayout = VK_IMAGE_LAYOUT_GENERAL; b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
b.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; b.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; b.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
VkBufferImageCopy copy{}; copy.bufferOffset = layout.outputOffset; copy.bufferRowLength = uint32_t(layout.outputPitch / 8);
copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; copy.imageExtent = {layout.ow, layout.oh, 1};
vkCmdCopyImageToBuffer(cb, engine->outputImage(), VK_IMAGE_LAYOUT_GENERAL, shared, 1, &copy);
}
};
}
+79
View File
@@ -0,0 +1,79 @@
// Native D3D12/Vulkan prerequisite query for the RR engine adapter. No dispatch.
#include <windows.h>
#include <d3d12.h>
#include <dxgi1_4.h>
#include <volk.h>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
int main() {
IDXGIFactory4* factory = nullptr;
if (FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&factory)))) return 1;
IDXGIAdapter1* selected = nullptr;
DXGI_ADAPTER_DESC1 adapter{};
for (UINT i = 0;; ++i) {
IDXGIAdapter1* next = nullptr;
if (factory->EnumAdapters1(i, &next) == DXGI_ERROR_NOT_FOUND) break;
if (!next) break;
DXGI_ADAPTER_DESC1 desc{};
next->GetDesc1(&desc);
if (desc.VendorId == 0x1002 && !(desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)) {
selected = next; adapter = desc; break;
}
next->Release();
}
factory->Release();
if (!selected) { std::puts("FAIL no native AMD D3D12 adapter"); return 1; }
ID3D12Device* device = nullptr;
const HRESULT created = D3D12CreateDevice(selected, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device));
selected->Release();
if (FAILED(created)) { std::puts("FAIL D3D12 device creation"); return 1; }
device->Release();
if (volkInitialize() != VK_SUCCESS) return 1;
VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO}; app.apiVersion = VK_API_VERSION_1_3;
VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; info.pApplicationInfo = &app;
VkInstance instance = VK_NULL_HANDLE;
if (vkCreateInstance(&info, nullptr, &instance) != VK_SUCCESS) return 1;
volkLoadInstance(instance);
uint32_t count = 0;
vkEnumeratePhysicalDevices(instance, &count, nullptr);
std::vector<VkPhysicalDevice> devices(count);
vkEnumeratePhysicalDevices(instance, &count, devices.data());
bool ready = false;
for (auto physical : devices) {
VkPhysicalDeviceIDProperties id{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES};
VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &id};
vkGetPhysicalDeviceProperties2(physical, &properties);
if (!id.deviceLUIDValid || std::memcmp(id.deviceLUID, &adapter.AdapterLuid, VK_LUID_SIZE)) continue;
uint32_t extensionCount = 0;
vkEnumerateDeviceExtensionProperties(physical, nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> extensions(extensionCount);
vkEnumerateDeviceExtensionProperties(physical, nullptr, &extensionCount, extensions.data());
auto has = [&](const char* name) {
return std::any_of(extensions.begin(), extensions.end(), [&](const auto& e) { return !std::strcmp(name, e.extensionName); });
};
VkPhysicalDeviceExternalBufferInfo buffer{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO};
buffer.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
buffer.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT;
VkExternalBufferProperties memory{VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES};
vkGetPhysicalDeviceExternalBufferProperties(physical, &buffer, &memory);
VkPhysicalDeviceExternalSemaphoreInfo semaphore{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO};
semaphore.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT;
VkExternalSemaphoreProperties fence{VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES};
vkGetPhysicalDeviceExternalSemaphoreProperties(physical, &semaphore, &fence);
const bool memoryImport = (memory.externalMemoryProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT) != 0;
const bool fenceImport = (fence.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT) != 0;
const bool memoryExtension = has("VK_KHR_external_memory_win32");
const bool fenceExtension = has("VK_KHR_external_semaphore_win32");
std::printf("RR_INTEROP device=%s luid_match=1 memory_win32=%u d3d12_buffer_import=%u dedicated_only=%u semaphore_win32=%u d3d12_fence_import=%u\n",
properties.properties.deviceName, memoryExtension, memoryImport,
unsigned((memory.externalMemoryProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT) != 0),
fenceExtension, fenceImport);
ready = memoryExtension && memoryImport && fenceExtension && fenceImport;
}
vkDestroyInstance(instance, nullptr);
std::puts(ready ? "PASS RR_INTEROP_CAPABILITIES import_or_dispatch_executed=0" : "FAIL RR_INTEROP_CAPABILITIES");
return ready ? 0 : 1;
}
+307
View File
@@ -0,0 +1,307 @@
// D3D12 -> Vulkan -> D3D12 shared-buffer/fence gate for native RR integration.
// Synthetic transfers and optional local RR model replay. No NGX, CUDA, or game.
#define WIDL_EXPLICIT_AGGREGATE_RETURNS
#include <windows.h>
#include <d3d12.h>
#include <dxgi1_4.h>
#include <wrl/client.h>
#include <volk.h>
#include <vulkan/vulkan_win32.h>
#include <cstdio>
#include <cstring>
#include <stdexcept>
#include <string>
#include <vector>
#include "rr_bridge_replay.h"
#include "rr_native_packet.h"
using Microsoft::WRL::ComPtr;
namespace {
void dx(HRESULT value, const char* operation) {
if (FAILED(value)) throw std::runtime_error(std::string(operation) + " HRESULT=" + std::to_string(value));
}
void vk(VkResult value, const char* operation) {
if (value != VK_SUCCESS) throw std::runtime_error(std::string(operation) + " VkResult=" + std::to_string(value));
}
struct Handle { HANDLE value = nullptr; ~Handle() { if (value) CloseHandle(value); } };
ComPtr<ID3D12Resource> buffer(ID3D12Device* device, UINT64 bytes, D3D12_HEAP_TYPE type,
D3D12_HEAP_FLAGS flags, D3D12_RESOURCE_STATES state) {
D3D12_HEAP_PROPERTIES heap{}; heap.Type = type;
D3D12_RESOURCE_DESC desc{}; desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Width = bytes; desc.Height = desc.DepthOrArraySize = desc.MipLevels = 1;
desc.SampleDesc.Count = 1; desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
if (type == D3D12_HEAP_TYPE_DEFAULT) desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
ComPtr<ID3D12Resource> result;
dx(device->CreateCommittedResource(&heap, flags, &desc, state, nullptr, IID_PPV_ARGS(&result)), "Create buffer");
return result;
}
void transition(ID3D12GraphicsCommandList* list, ID3D12Resource* resource,
D3D12_RESOURCE_STATES from, D3D12_RESOURCE_STATES to) {
D3D12_RESOURCE_BARRIER b{}; b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
b.Transition = {resource, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, from, to};
list->ResourceBarrier(1, &b);
}
struct Vulkan {
std::unique_ptr<rr_bridge::NativeDevice> native;
std::unique_ptr<rr_bridge::NativePacket> packet;
VkDevice device = VK_NULL_HANDLE;
VkBuffer shared = VK_NULL_HANDLE, readback = VK_NULL_HANDLE;
VkDeviceMemory host = VK_NULL_HANDLE;
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory imageMemory = VK_NULL_HANDLE;
VkSemaphore semaphore = VK_NULL_HANDLE;
VkCommandPool pool = VK_NULL_HANDLE;
VkFence fence = VK_NULL_HANDLE;
bool submitted = false;
std::unique_ptr<rr_bridge::Replay> replay;
~Vulkan() {
// The outer process timeout also bounds a stalled driver call. Never
// destroy resources that a failed/timed-out submission might still use.
if (submitted) { (void)replay.release(); (void)packet.release(); (void)native.release(); return; }
replay.reset();
if (device) {
if (fence) vkDestroyFence(device, fence, nullptr);
if (pool) vkDestroyCommandPool(device, pool, nullptr);
if (readback) vkDestroyBuffer(device, readback, nullptr);
if (image) vkDestroyImage(device, image, nullptr);
if (host) vkFreeMemory(device, host, nullptr);
if (imageMemory) vkFreeMemory(device, imageMemory, nullptr);
}
}
};
}
int main(int argc, char** argv) try {
std::setvbuf(stdout, nullptr, _IONBF, 0);
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
UINT64 bytes = 1u << 20;
unsigned iterations = 8;
uint32_t imageWidth = 0, imageHeight = 0;
std::string rrModel, rrInputs, rrOutput, rrReference;
for (int i = 1; i < argc; i += 2) {
if (i + 1 == argc) throw std::runtime_error("Missing argument value");
const std::string key = argv[i];
size_t end = 0;
const std::string text = argv[i + 1];
if (key == "--rr-model") { rrModel = text; continue; }
if (key == "--rr-inputs") { rrInputs = text; continue; }
if (key == "--rr-output") { rrOutput = text; continue; }
if (key == "--rr-reference") { rrReference = text; continue; }
if (text.empty() || text[0] == '-') throw std::runtime_error("Expected positive argument");
const auto value = std::stoull(text, &end);
if (end != text.size()) throw std::runtime_error("Invalid numeric argument");
if (key == "--bytes") bytes = value;
else if (key == "--iterations" && value <= 32) iterations = unsigned(value);
else if (key == "--image-width" && value <= 8192) imageWidth = uint32_t(value);
else if (key == "--image-height" && value <= 8192) imageHeight = uint32_t(value);
else throw std::runtime_error("Unknown or out-of-range argument");
}
std::unique_ptr<rr_bridge::Layout> rrLayout;
if (!rrModel.empty()) {
if (rrInputs.empty() || rrOutput.empty() || imageWidth || imageHeight) throw std::runtime_error("RR replay needs model/inputs/output and no image-clear mode");
rrLayout = std::make_unique<rr_bridge::Layout>(rrInputs, rrOutput, rrReference);
bytes = rrLayout->bytes; iterations = unsigned(rrLayout->frames.size());
} else if (!rrInputs.empty() || !rrOutput.empty() || !rrReference.empty()) throw std::runtime_error("Missing RR model");
if (!bytes || bytes % 4 || bytes > (64u << 20) || !iterations)
throw std::runtime_error("Need a 4-byte-aligned buffer (4 bytes..64MiB) and 1..32 iterations");
const UINT64 rowPitch = (UINT64(imageWidth) * 8 + 255) & ~UINT64(255);
const bool imageMode = imageWidth && imageHeight;
if (bool(imageWidth) != bool(imageHeight) || (imageMode && bytes != rowPitch * imageHeight))
throw std::runtime_error("Image dimensions must match the 256-byte-padded RGBA16F buffer size");
ComPtr<IDXGIFactory4> factory; dx(CreateDXGIFactory1(IID_PPV_ARGS(&factory)), "DXGI factory");
ComPtr<IDXGIAdapter1> adapter;
DXGI_ADAPTER_DESC1 description{};
bool selected = false;
for (UINT i = 0; factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND; ++i) {
dx(adapter->GetDesc1(&description), "DXGI adapter description");
if (description.VendorId == 0x1002 && !(description.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)) { selected = true; break; }
adapter.Reset();
}
if (!selected) throw std::runtime_error("No AMD D3D12 adapter");
ComPtr<ID3D12Device> d3d; dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&d3d)), "D3D12 device");
ComPtr<ID3D12CommandQueue> queue;
D3D12_COMMAND_QUEUE_DESC q{}; q.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
dx(d3d->CreateCommandQueue(&q, IID_PPV_ARGS(&queue)), "D3D12 queue");
auto shared = buffer(d3d.Get(), bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_SHARED, D3D12_RESOURCE_STATE_COMMON);
auto upload = buffer(d3d.Get(), bytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ);
auto readback = buffer(d3d.Get(), bytes, D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
std::unique_ptr<rr_bridge::D3D12Frames> d3dFrames;
if (rrLayout) d3dFrames = std::make_unique<rr_bridge::D3D12Frames>(*rrLayout, d3d.Get());
ComPtr<ID3D12Fence> d3dFence; dx(d3d->CreateFence(0, D3D12_FENCE_FLAG_SHARED, IID_PPV_ARGS(&d3dFence)), "Shared D3D12 fence");
Handle memoryHandle, fenceHandle, event;
dx(d3d->CreateSharedHandle(shared.Get(), nullptr, GENERIC_ALL, nullptr, &memoryHandle.value), "Shared buffer handle");
dx(d3d->CreateSharedHandle(d3dFence.Get(), nullptr, GENERIC_ALL, nullptr, &fenceHandle.value), "Shared fence handle");
event.value = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if (!event.value) throw std::runtime_error("Fence event creation failed");
Vulkan v;
v.native = std::make_unique<rr_bridge::NativeDevice>();
v.native->initialize(description.AdapterLuid, bool(rrLayout));
v.device = v.native->device;
const auto physical = v.native->physical;
const auto family = v.native->family;
const auto vkQueue = v.native->queue;
if (rrLayout) v.replay = std::make_unique<rr_bridge::Replay>(*rrLayout, physical, v.device, rrModel);
v.packet = std::make_unique<rr_bridge::NativePacket>(*v.native);
v.packet->initialize(bytes, memoryHandle.value, fenceHandle.value);
v.shared = v.packet->buffer; v.semaphore = v.packet->semaphore;
VkPhysicalDeviceMemoryProperties memory{}; vkGetPhysicalDeviceMemoryProperties(physical, &memory);
auto memoryType = [&](uint32_t bits, VkMemoryPropertyFlags flags) {
for (uint32_t i = 0; i < memory.memoryTypeCount; ++i)
if ((bits & (1u << i)) && (memory.memoryTypes[i].propertyFlags & flags) == flags) return i;
throw std::runtime_error("No compatible Vulkan memory type");
};
VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; bi.size = bytes;
bi.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VkMemoryRequirements req{};
VkMemoryAllocateInfo ai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
vk(vkCreateBuffer(v.device, &bi, nullptr, &v.readback), "Vulkan readback buffer");
vkGetBufferMemoryRequirements(v.device, v.readback, &req);
ai.pNext = nullptr; ai.allocationSize = req.size;
ai.memoryTypeIndex = memoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
vk(vkAllocateMemory(v.device, &ai, nullptr, &v.host), "Vulkan readback memory");
vk(vkBindBufferMemory(v.device, v.readback, v.host, 0), "Bind readback");
if (imageMode) {
VkImageCreateInfo image{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
image.imageType = VK_IMAGE_TYPE_2D; image.format = VK_FORMAT_R16G16B16A16_SFLOAT;
image.extent = {imageWidth, imageHeight, 1}; image.mipLevels = image.arrayLayers = 1;
image.samples = VK_SAMPLE_COUNT_1_BIT;
image.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
vk(vkCreateImage(v.device, &image, nullptr, &v.image), "Canonical RR image");
vkGetImageMemoryRequirements(v.device, v.image, &req);
ai.allocationSize = req.size; ai.memoryTypeIndex = memoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
vk(vkAllocateMemory(v.device, &ai, nullptr, &v.imageMemory), "Canonical RR image memory");
vk(vkBindImageMemory(v.device, v.image, v.imageMemory, 0), "Bind canonical image");
}
VkCommandPoolCreateInfo pci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; pci.queueFamilyIndex = family;
vk(vkCreateCommandPool(v.device, &pci, nullptr, &v.pool), "Vulkan command pool");
VkCommandBufferAllocateInfo cai{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; cai.commandPool = v.pool; cai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cai.commandBufferCount = 1;
VkCommandBuffer cb; vk(vkAllocateCommandBuffers(v.device, &cai, &cb), "Vulkan command buffer");
VkFenceCreateInfo fci{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; vk(vkCreateFence(v.device, &fci, nullptr, &v.fence), "Vulkan completion fence");
std::puts("RR_INTEROP_IMPORTED memory=dedicated_d3d12_resource semaphore=d3d12_timeline_fence luid_match=1");
ComPtr<ID3D12CommandAllocator> producerAllocator, consumerAllocator;
ComPtr<ID3D12GraphicsCommandList> producer, consumer;
dx(d3d->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&producerAllocator)), "Producer allocator");
dx(d3d->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&consumerAllocator)), "Consumer allocator");
dx(d3d->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, producerAllocator.Get(), nullptr, IID_PPV_ARGS(&producer)), "Producer list");
dx(d3d->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, consumerAllocator.Get(), nullptr, IID_PPV_ARGS(&consumer)), "Consumer list");
dx(producer->Close(), "Initial producer close"); dx(consumer->Close(), "Initial consumer close");
auto pattern = [](unsigned iteration, uint32_t word) { return (word * 2654435761u) ^ (0x13579bdfu + iteration * 97u); };
for (unsigned iteration = 0; iteration < iterations; ++iteration) {
uint32_t* input = nullptr; D3D12_RANGE noRead{};
dx(upload->Map(0, &noRead, reinterpret_cast<void**>(&input)), "Upload map");
if (rrLayout) rrLayout->upload(input, iteration);
else for (uint32_t word = 0; word < bytes / 4; ++word) input[word] = pattern(iteration, word);
upload->Unmap(0, nullptr);
dx(producerAllocator->Reset(), "Producer allocator reset"); dx(producer->Reset(producerAllocator.Get(), nullptr), "Producer reset");
transition(producer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST);
if (d3dFrames) d3dFrames->recordInputs(producer.Get(), upload.Get(), shared.Get());
else producer->CopyBufferRegion(shared.Get(), 0, upload.Get(), 0, bytes);
transition(producer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COMMON);
dx(producer->Close(), "Producer close"); ID3D12CommandList* p[] = {producer.Get()}; queue->ExecuteCommandLists(1, p);
const uint64_t ready = iteration * 3 + 1, done = ready + 1, complete = ready + 2;
dx(queue->Signal(d3dFence.Get(), ready), "D3D12 producer signal");
vk(vkResetCommandPool(v.device, v.pool, 0), "Vulkan pool reset");
VkCommandBufferBeginInfo begin{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; vk(vkBeginCommandBuffer(cb, &begin), "Vulkan begin");
VkBufferMemoryBarrier acquire{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; acquire.buffer = v.shared; acquire.size = VK_WHOLE_SIZE;
acquire.srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL; acquire.dstQueueFamilyIndex = family; acquire.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
if (rrLayout) acquire.dstAccessMask |= VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &acquire, 0, nullptr);
VkBufferImageCopy imageRegion{};
imageRegion.bufferRowLength = uint32_t(rowPitch / 8);
imageRegion.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
imageRegion.imageExtent = {imageWidth, imageHeight, 1};
auto imageBarrier = [&](VkImageLayout from, VkImageLayout to, VkAccessFlags source, VkAccessFlags destination) {
VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
b.image = v.image; b.oldLayout = from; b.newLayout = to;
b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
b.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; b.srcAccessMask = source; b.dstAccessMask = destination;
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
};
if (rrLayout) {
v.replay->record(cb, v.shared, iteration);
} else if (imageMode) {
vkCmdFillBuffer(cb, v.readback, 0, bytes, 0xdeadbeefu);
VkBufferMemoryBarrier fill{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; fill.buffer = v.readback; fill.size = VK_WHOLE_SIZE;
fill.srcQueueFamilyIndex = fill.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
fill.srcAccessMask = fill.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &fill, 0, nullptr);
imageBarrier(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_ACCESS_TRANSFER_WRITE_BIT);
vkCmdCopyBufferToImage(cb, v.shared, v.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageRegion);
imageBarrier(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
vkCmdCopyImageToBuffer(cb, v.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, v.readback, 1, &imageRegion);
imageBarrier(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
VkClearColorValue clear{}; clear.float32[0] = iteration % 2 ? .5f : .25f;
clear.float32[1] = .5f; clear.float32[2] = .75f; clear.float32[3] = 1.f;
VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
vkCmdClearColorImage(cb, v.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear, 1, &range);
imageBarrier(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
} else {
VkBufferCopy region{0, 0, bytes}; vkCmdCopyBuffer(cb, v.shared, v.readback, 1, &region);
}
VkBufferMemoryBarrier afterRead{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; afterRead.buffer = v.shared; afterRead.size = VK_WHOLE_SIZE;
afterRead.srcQueueFamilyIndex = afterRead.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
afterRead.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; afterRead.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
if (!rrLayout) vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &afterRead, 0, nullptr);
const uint32_t output = 0xa5a50000u | iteration;
if (!rrLayout) vkCmdFillBuffer(cb, v.shared, 0, bytes, output);
if (imageMode) {
afterRead.srcAccessMask = afterRead.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &afterRead, 0, nullptr);
vkCmdCopyImageToBuffer(cb, v.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, v.shared, 1, &imageRegion);
}
VkBufferMemoryBarrier release = acquire; release.srcQueueFamilyIndex = family; release.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL;
release.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; release.dstAccessMask = 0;
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 1, &release, 0, nullptr);
VkBufferMemoryBarrier hostRead{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; hostRead.buffer = v.readback; hostRead.size = VK_WHOLE_SIZE;
hostRead.srcQueueFamilyIndex = hostRead.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
hostRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; hostRead.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 0, nullptr, 1, &hostRead, 0, nullptr);
vk(vkEndCommandBuffer(cb), "Vulkan end");
VkTimelineSemaphoreSubmitInfo values{VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO};
values.waitSemaphoreValueCount = values.signalSemaphoreValueCount = 1; values.pWaitSemaphoreValues = &ready; values.pSignalSemaphoreValues = &done;
VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
VkSubmitInfo submit{VK_STRUCTURE_TYPE_SUBMIT_INFO, &values}; submit.waitSemaphoreCount = submit.signalSemaphoreCount = 1;
submit.pWaitSemaphores = submit.pSignalSemaphores = &v.semaphore; submit.pWaitDstStageMask = &waitStage; submit.commandBufferCount = 1; submit.pCommandBuffers = &cb;
vk(vkResetFences(v.device, 1, &v.fence), "Vulkan fence reset");
vk(vkQueueSubmit(vkQueue, 1, &submit, v.fence), "Vulkan shared-buffer submission"); v.submitted = true;
dx(queue->Wait(d3dFence.Get(), done), "D3D12 wait for Vulkan");
dx(consumerAllocator->Reset(), "Consumer allocator reset"); dx(consumer->Reset(consumerAllocator.Get(), nullptr), "Consumer reset");
transition(consumer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_SOURCE);
if (d3dFrames) d3dFrames->recordOutput(consumer.Get(), shared.Get(), readback.Get());
else consumer->CopyBufferRegion(readback.Get(), 0, shared.Get(), 0, bytes);
transition(consumer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COMMON);
dx(consumer->Close(), "Consumer close"); ID3D12CommandList* c[] = {consumer.Get()}; queue->ExecuteCommandLists(1, c);
dx(queue->Signal(d3dFence.Get(), complete), "D3D12 consumer signal");
dx(d3dFence->SetEventOnCompletion(complete, event.value), "Completion event");
if (WaitForSingleObject(event.value, 5000) != WAIT_OBJECT_0) throw std::runtime_error("D3D12/Vulkan handoff timeout");
vk(vkWaitForFences(v.device, 1, &v.fence, VK_TRUE, 5000000000ull), "Vulkan completion wait"); v.submitted = false;
if (rrLayout) {
void* data = nullptr; D3D12_RANGE range{0, SIZE_T(bytes)};
dx(readback->Map(0, &range, &data), "RR D3D12 output map");
try { rrLayout->validate(data, iteration); } catch (...) { D3D12_RANGE empty{}; readback->Unmap(0, &empty); throw; }
D3D12_RANGE empty{}; readback->Unmap(0, &empty);
continue;
}
uint32_t* first = nullptr; vk(vkMapMemory(v.device, v.host, 0, bytes, 0, reinterpret_cast<void**>(&first)), "Vulkan readback map");
auto padding = [&](uint32_t word) { return imageMode && word % (rowPitch / 4) >= imageWidth * 2; };
size_t mismatches = 0;
for (uint32_t word = 0; word < bytes / 4; ++word)
mismatches += first[word] != (padding(word) ? 0xdeadbeefu : pattern(iteration, word));
vkUnmapMemory(v.device, v.host);
uint32_t* second = nullptr; D3D12_RANGE readRange{0, SIZE_T(bytes)};
dx(readback->Map(0, &readRange, reinterpret_cast<void**>(&second)), "D3D12 readback map");
for (uint32_t word = 0; word < bytes / 4; ++word) {
const uint32_t imageWord = word % 2 ? 0x3c003a00u : (iteration % 2 ? 0x38003800u : 0x38003400u);
const uint32_t expected = imageMode && !padding(word) ? imageWord : output;
mismatches += second[word] != expected;
}
D3D12_RANGE noWrite{}; readback->Unmap(0, &noWrite);
if (mismatches) throw std::runtime_error("Shared-buffer contents differ: " + std::to_string(mismatches));
std::printf("RR_INTEROP_ITERATION index=%u bytes=%llu exact=1 producer_value=%llu vulkan_value=%llu consumer_value=%llu\n",
iteration, static_cast<unsigned long long>(bytes), static_cast<unsigned long long>(ready), static_cast<unsigned long long>(done), static_cast<unsigned long long>(complete));
}
if (rrLayout) std::printf("PASS RR_BRIDGED_INFERENCE frames=%u guides=7 d3d12_textures=1 gpu_fence_handoff=1 standalone_reference=%u\n", iterations, !rrReference.empty());
else std::printf("PASS RR_INTEROP_TRANSFER iterations=%u d3d12_to_vulkan_exact=1 vulkan_to_d3d12_exact=1 gpu_fence_handoff=1 canonical_image=%u inference_executed=0\n", iterations, imageMode);
return 0;
} catch (const std::exception& error) { std::fprintf(stderr, "FAIL RR_INTEROP_TRANSFER %s\n", error.what()); return 1; }
+291
View File
@@ -0,0 +1,291 @@
#include <volk.h>
#include "rr_native_backend.h"
#include "rr_native_packet.h"
#include "rr_bridge_replay.h"
#include "d3d12_command_hooks.h"
#include <mutex>
namespace d4r::win::rr {
namespace {
// The engine uses Volk's global dispatch. Serialize device creation, recording,
// submission and destruction; reload the owning dispatch before each operation.
std::mutex dispatchMutex;
using Packet = rr_bridge::NativePacket;
using Device = rr_bridge::NativeDevice;
size_t resource_index(size_t plane) { return plane < 7 ? plane : plane + 2; }
rr_bridge::Plane input_format(DXGI_FORMAT format, size_t plane) {
// Match Linux's direct sampled-image route. These formats preserve the
// stored texel values; no CPU conversion or reinterpretation is involved.
if (plane == Snapshot::Motion) {
if (format == DXGI_FORMAT_R16G16_FLOAT) return {nullptr, VK_FORMAT_R16G16_SFLOAT, 4};
if (format == DXGI_FORMAT_R32G32_FLOAT) return {nullptr, VK_FORMAT_R32G32_SFLOAT, 8};
} else if (plane == Snapshot::Depth || plane == Snapshot::HitDistance || plane == Snapshot::Disocclusion || plane == Snapshot::Subsurface) {
if (plane == Snapshot::Depth && (format == DXGI_FORMAT_R32_TYPELESS || format == DXGI_FORMAT_D32_FLOAT ||
format == DXGI_FORMAT_R32G8X24_TYPELESS || format == DXGI_FORMAT_D32_FLOAT_S8X24_UINT || format == DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS))
return {nullptr, VK_FORMAT_R32_SFLOAT, 4};
if (format == DXGI_FORMAT_R32_FLOAT) return {nullptr, VK_FORMAT_R32_SFLOAT, 4};
if (format == DXGI_FORMAT_R16_FLOAT) return {nullptr, VK_FORMAT_R16_SFLOAT, 2};
if (plane != Snapshot::Depth && format == DXGI_FORMAT_R8_UNORM) return {nullptr, VK_FORMAT_R8_UNORM, 1};
} else {
if (format == DXGI_FORMAT_R16G16B16A16_FLOAT) return {nullptr, VK_FORMAT_R16G16B16A16_SFLOAT, 8};
if (format == DXGI_FORMAT_R32G32B32A32_FLOAT) return {nullptr, VK_FORMAT_R32G32B32A32_SFLOAT, 16};
if (format == DXGI_FORMAT_R8G8B8A8_UNORM) return {nullptr, VK_FORMAT_R8G8B8A8_UNORM, 4};
if (format == DXGI_FORMAT_R10G10B10A2_UNORM) return {nullptr, VK_FORMAT_A2B10G10R10_UNORM_PACK32, 4};
if (plane == Snapshot::Roughness) {
if (format == DXGI_FORMAT_R16_FLOAT) return {nullptr, VK_FORMAT_R16_SFLOAT, 2};
if (format == DXGI_FORMAT_R32_FLOAT) return {nullptr, VK_FORMAT_R32_SFLOAT, 4};
if (format == DXGI_FORMAT_R8_UNORM) return {nullptr, VK_FORMAT_R8_UNORM, 1};
}
}
throw std::runtime_error("Unsupported native RR sampled input format");
}
DXGI_FORMAT copy_format(ID3D12Device* device, ID3D12Resource* texture, size_t plane) {
const auto desc = resource_desc(texture);
if (plane != Snapshot::Depth) return desc.Format;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint{}; UINT rows = 0; UINT64 rowBytes = 0, bytes = 0;
device->GetCopyableFootprints(&desc, 0, 1, 0, &footprint, &rows, &rowBytes, &bytes);
const auto format = input_format(desc.Format, plane);
if (!bytes || bytes == UINT64_MAX || rows != desc.Height || rowBytes != desc.Width * format.bytes ||
footprint.Footprint.Width != desc.Width || footprint.Footprint.Height != desc.Height)
throw std::runtime_error("Unsupported RR depth-plane footprint");
return footprint.Footprint.Format;
}
}
struct Backend::Impl {
Creation creation;
rr_bridge::Layout layout;
ComPtr<ID3D12Device> d3d;
ComPtr<ID3D12CommandQueue> queue;
ComPtr<ID3D12Resource> shared;
ComPtr<ID3D12Fence> fence;
ComPtr<ID3D12CommandAllocator> producerAllocator, consumerAllocator;
ComPtr<ID3D12GraphicsCommandList> producer, consumer;
Handle event;
std::unique_ptr<Device> native;
std::unique_ptr<Packet> packet;
std::unique_ptr<rr_bridge::Replay> replay;
VkCommandPool pool = VK_NULL_HANDLE;
VkCommandBuffer command = VK_NULL_HANDLE;
VkFence completion = VK_NULL_HANDLE;
uint64_t sequence = 0;
bool pending = false, failed = false;
std::unique_ptr<Snapshot> retained;
Impl(ID3D12Device* device, const Creation& c)
: creation(c), layout(c.width, c.height, c.outWidth, c.outHeight), d3d(device) {
// Reserve each guide's largest supported texel size once. Format
// changes can then preserve the packet, model and temporal history.
VkDeviceSize offset = 0;
for (auto& plane : layout.planes) {
plane.offset = (offset + 511) & ~VkDeviceSize(511);
plane.pitch = (VkDeviceSize(layout.rw) * 16 + 255) & ~VkDeviceSize(255);
offset = plane.offset + plane.pitch * layout.rh;
}
layout.outputOffset = (offset + 511) & ~VkDeviceSize(511);
layout.bytes = layout.outputOffset + layout.outputPitch * layout.oh;
}
void dispatch() {
if (native && native->instance) volkLoadInstance(native->instance);
if (native && native->device) volkLoadDevice(native->device);
}
~Impl() {
dispatch();
replay.reset();
if (native && native->device) {
if (completion) vkDestroyFence(native->device, completion, nullptr);
if (pool) vkDestroyCommandPool(native->device, pool, nullptr);
}
packet.reset(); native.reset();
}
void initialize(const std::string& directory) {
if (!d3d) throw std::runtime_error("Null native RR D3D12 device");
// Match the Linux RR adapter's contract: HDR and render-resolution,
// unjittered motion. D/E already perform their own exposure reduction.
// NVIDIA/DLSS include/nvsdk_ngx_defs.h defines these public flag bits.
if ((creation.flags & 3u) != 3u)
throw std::runtime_error("Native RR requires HDR and render-resolution motion vectors");
if (creation.flags & ~(3u | 8u | 64u))
throw std::runtime_error("Native RR creation flags require an unimplemented conversion");
if (creation.quality < 0 || creation.quality > 5) throw std::runtime_error("Unsupported RR quality mode");
native = std::make_unique<Device>(); native->initialize(adapter_luid(d3d.Get()), true);
replay = std::make_unique<rr_bridge::Replay>(layout, native->physical, native->device, directory);
const unsigned indices[] = {3, 2, 1, 4, 5, 0};
const auto requested = creation.presets[indices[creation.quality]];
if (requested && requested != replay->model->preset()) throw std::runtime_error("Configured RR model differs from requested preset");
shared = make_buffer(d3d.Get(), layout.bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_SHARED, D3D12_RESOURCE_STATE_COMMON);
dx(d3d->CreateFence(0, D3D12_FENCE_FLAG_SHARED, IID_PPV_ARGS(&fence)), "RR shared fence");
Handle memoryHandle, fenceHandle;
dx(d3d->CreateSharedHandle(shared.Get(), nullptr, GENERIC_ALL, nullptr, &memoryHandle.value), "RR packet handle");
dx(d3d->CreateSharedHandle(fence.Get(), nullptr, GENERIC_ALL, nullptr, &fenceHandle.value), "RR fence handle");
packet = std::make_unique<Packet>(*native); packet->initialize(layout.bytes, memoryHandle.value, fenceHandle.value);
event.value = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if (!event.value) throw std::runtime_error("RR completion event creation failed");
auto createList = [&](auto& allocator, auto& list) {
dx(d3d->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&allocator)), "RR allocator");
dx(d3d->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator.Get(), nullptr, IID_PPV_ARGS(&list)), "RR command list");
dx(list->Close(), "RR initial close");
};
createList(producerAllocator, producer); createList(consumerAllocator, consumer);
VkCommandPoolCreateInfo pci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; pci.queueFamilyIndex = native->family;
Device::check(vkCreateCommandPool(native->device, &pci, nullptr, &pool), "RR command pool");
VkCommandBufferAllocateInfo cai{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
cai.commandPool = pool; cai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cai.commandBufferCount = 1;
Device::check(vkAllocateCommandBuffers(native->device, &cai, &command), "RR command buffer");
VkFenceCreateInfo fci{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
Device::check(vkCreateFence(native->device, &fci, nullptr, &completion), "RR Vulkan completion fence");
}
void validate(ID3D12CommandQueue* q, const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access) {
if (failed) throw std::runtime_error("Native RR backend is poisoned after failed submission");
if (!q) throw std::runtime_error("Null RR submission queue");
D3D12_COMMAND_QUEUE_DESC queueDesc{};
#if defined(__MINGW32__)
q->GetDesc(&queueDesc);
#else
queueDesc = q->GetDesc();
#endif
if (queueDesc.Type != D3D12_COMMAND_LIST_TYPE_DIRECT) throw std::runtime_error("RR requires a direct queue");
ComPtr<ID3D12Device> owner; dx(q->GetDevice(IID_PPV_ARGS(&owner)), "RR queue device");
if (owner.Get() != d3d.Get()) throw std::runtime_error("RR queue belongs to another device");
for (size_t i = 0; i < access.size(); ++i)
if (frame.resources[i] && (access[i].pending_split || access[i].inherited)) throw std::runtime_error("Unresolved RR texture state");
validate_frame(frame);
}
void validate_frame(const Snapshot& frame) {
if (failed) throw std::runtime_error("Native RR backend is poisoned after failed submission");
if (frame.renderWidth != layout.rw || frame.renderHeight != layout.rh)
throw std::runtime_error("Dynamic RR render extent requires engine reconfiguration");
if (frame.resources[Snapshot::Exposure]) throw std::runtime_error("RR exposure texture is not implemented");
if (frame.exposureScale != 1 || frame.depthType || frame.normalsType || frame.roughnessType || frame.diffuseType || frame.specularType)
throw std::runtime_error("RR guide encoding or exposure scale requires conversion");
for (size_t i = 0; i < Snapshot::Count; ++i) {
if (i == Snapshot::Exposure) continue;
auto* resource = frame.resources[i].Get();
if (!resource && i >= Snapshot::HitDistance) continue;
if (!resource) throw std::runtime_error("Missing required native RR texture");
ComPtr<ID3D12Device> resourceOwner; dx(resource->GetDevice(IID_PPV_ARGS(&resourceOwner)), "RR texture device");
if (resourceOwner.Get() != d3d.Get()) throw std::runtime_error("RR texture belongs to another device");
const auto desc = resource_desc(resource);
if (i != Snapshot::Output) (void)input_format(desc.Format, i);
if (i == Snapshot::Depth) {
(void)copy_format(d3d.Get(), resource, i);
// D3D12 requires whole-subresource copies for depth-stencil.
// The copied subresource is depth plane 0, never stencil.
if ((desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) &&
(frame.origins[i][0] || frame.origins[i][1] || layout.rw != desc.Width || layout.rh != desc.Height))
throw std::runtime_error("RR depth-stencil requires a full depth-plane rectangle");
}
const uint32_t width = i == Snapshot::Output ? layout.ow : layout.rw;
const uint32_t height = i == Snapshot::Output ? layout.oh : layout.rh;
if (desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D || (i == Snapshot::Output && desc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT) || desc.SampleDesc.Count != 1 || desc.DepthOrArraySize != 1 ||
frame.origins[i][0] > desc.Width || width > desc.Width - frame.origins[i][0] ||
frame.origins[i][1] > desc.Height || height > desc.Height - frame.origins[i][1])
throw std::runtime_error("Unsupported native RR texture format or subrect");
if (i == Snapshot::Output && !(desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS))
throw std::runtime_error("RR output lacks UAV capability");
}
for (size_t i = 0; i < Snapshot::Count; ++i)
if (i != Snapshot::Output && frame.resources[i] == frame.resources[Snapshot::Output]) throw std::runtime_error("RR input/output alias is unsupported");
}
void evaluate(ID3D12CommandQueue* q, const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access) {
validate(q, frame, access); dispatch();
for (size_t i = 0; i < layout.planes.size(); ++i) {
const auto resource = resource_index(i);
layout.planes[i].enabled = frame.resources[resource] != nullptr;
if (!layout.planes[i].enabled) continue;
const auto format = input_format(resource_desc(frame.resources[resource].Get()).Format, resource);
replay->configure(i, format.format);
layout.planes[i].format = format.format; layout.planes[i].bytes = format.bytes;
}
// Recording mutates engine upload/history bookkeeping. Any later error
// requires recreation, even if no GPU submission has started yet.
failed = true;
commands::InternalScope internal;
dx(producerAllocator->Reset(), "RR producer allocator reset"); dx(producer->Reset(producerAllocator.Get(), nullptr), "RR producer reset");
transition(producer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST);
for (size_t i = 0; i < layout.planes.size(); ++i) {
const auto resource = resource_index(i);
auto* texture = frame.resources[resource].Get(); const auto& plane = layout.planes[i];
if (!plane.enabled) continue;
texture_copy_barrier(producer.Get(), texture, access[resource], false, false);
auto source = rr_bridge::D3D12Frames::texture(texture);
auto dest = rr_bridge::D3D12Frames::placed(shared.Get(), plane.offset, plane.pitch,
copy_format(d3d.Get(), texture, resource), layout.rw, layout.rh);
const auto x = frame.origins[resource][0], y = frame.origins[resource][1];
D3D12_BOX box{x, y, 0, x + layout.rw, y + layout.rh, 1};
const bool depthStencil = (resource_desc(texture).Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) != 0;
producer->CopyTextureRegion(&dest, 0, 0, 0, &source, depthStencil ? nullptr : &box);
texture_copy_barrier(producer.Get(), texture, access[resource], false, true);
}
transition(producer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COMMON);
dx(producer->Close(), "RR producer close");
dx(consumerAllocator->Reset(), "RR consumer allocator reset"); dx(consumer->Reset(consumerAllocator.Get(), nullptr), "RR consumer reset");
transition(consumer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_SOURCE);
auto* output = frame.resources[Snapshot::Output].Get();
texture_copy_barrier(consumer.Get(), output, access[Snapshot::Output], true, false);
auto source = rr_bridge::D3D12Frames::placed(shared.Get(), layout.outputOffset, layout.outputPitch,
DXGI_FORMAT_R16G16B16A16_FLOAT, layout.ow, layout.oh);
auto dest = rr_bridge::D3D12Frames::texture(output);
consumer->CopyTextureRegion(&dest, frame.origins[Snapshot::Output][0], frame.origins[Snapshot::Output][1], 0, &source, nullptr);
texture_copy_barrier(consumer.Get(), output, access[Snapshot::Output], true, true);
transition(consumer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COMMON);
dx(consumer->Close(), "RR consumer close");
Device::check(vkResetCommandPool(native->device, pool, 0), "RR Vulkan pool reset");
VkCommandBufferBeginInfo begin{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
Device::check(vkBeginCommandBuffer(command, &begin), "RR Vulkan begin");
VkBufferMemoryBarrier acquire{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; acquire.buffer = packet->buffer; acquire.size = VK_WHOLE_SIZE;
acquire.srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL; acquire.dstQueueFamilyIndex = native->family;
acquire.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(command, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &acquire, 0, nullptr);
replay->record(command, packet->buffer, frame.settings);
VkBufferMemoryBarrier release = acquire; release.srcQueueFamilyIndex = native->family; release.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL;
release.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; release.dstAccessMask = 0;
vkCmdPipelineBarrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 1, &release, 0, nullptr);
Device::check(vkEndCommandBuffer(command), "RR Vulkan end");
const uint64_t ready = sequence + 1, done = sequence + 2, complete = sequence + 3;
VkTimelineSemaphoreSubmitInfo values{VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO};
values.waitSemaphoreValueCount = values.signalSemaphoreValueCount = 1; values.pWaitSemaphoreValues = &ready; values.pSignalSemaphoreValues = &done;
VkPipelineStageFlags stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
VkSubmitInfo submit{VK_STRUCTURE_TYPE_SUBMIT_INFO, &values}; submit.waitSemaphoreCount = submit.signalSemaphoreCount = 1;
submit.pWaitSemaphores = submit.pSignalSemaphores = &packet->semaphore; submit.pWaitDstStageMask = &stage;
submit.commandBufferCount = 1; submit.pCommandBuffers = &command;
Device::check(vkResetFences(native->device, 1, &completion), "RR completion reset");
retained = std::make_unique<Snapshot>(frame); queue = q; pending = true;
try {
ID3D12CommandList* inputs[] = {producer.Get()}; q->ExecuteCommandLists(1, inputs);
dx(q->Signal(fence.Get(), ready), "RR input ready signal");
Device::check(vkQueueSubmit(native->queue, 1, &submit, completion), "RR model submission");
dx(q->Wait(fence.Get(), done), "RR output queue wait");
ID3D12CommandList* outputs[] = {consumer.Get()}; q->ExecuteCommandLists(1, outputs);
dx(q->Signal(fence.Get(), complete), "RR completion signal");
dx(fence->SetEventOnCompletion(complete, event.value), "RR completion event");
if (WaitForSingleObject(event.value, 5000) != WAIT_OBJECT_0) throw std::runtime_error("RR D3D12 completion timeout");
Device::check(vkWaitForFences(native->device, 1, &completion, VK_TRUE, 5000000000ull), "RR Vulkan completion wait");
if (fence->GetCompletedValue() == UINT64_MAX) throw std::runtime_error("RR D3D12 device removed");
pending = false; failed = false; retained.reset(); queue.Reset(); sequence = complete;
} catch (...) { failed = true; throw; }
}
};
Backend::Backend(ID3D12Device* device, const Creation& creation, const std::string& directory) {
std::lock_guard<std::mutex> lock(dispatchMutex);
auto candidate = std::make_unique<Impl>(device, creation);
candidate->initialize(directory); impl = std::move(candidate);
}
Backend::~Backend() {
std::lock_guard<std::mutex> lock(dispatchMutex);
if (impl && impl->pending) {
std::fprintf(stderr, "D4R_RR_QUARANTINE incomplete GPU work; retaining backend and guide resources\n");
(void)impl.release();
} else impl.reset();
}
void Backend::evaluate(ID3D12CommandQueue* queue, const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access) {
std::lock_guard<std::mutex> lock(dispatchMutex);
impl->evaluate(queue, frame, access);
}
void Backend::validate_frame(const Snapshot& frame) {
std::lock_guard<std::mutex> lock(dispatchMutex);
impl->validate_frame(frame);
}
void Backend::drain() {
std::lock_guard<std::mutex> lock(dispatchMutex);
if (impl->pending || impl->failed) throw std::runtime_error("RR backend cannot retire failed GPU work");
}
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "d3d12_external.h"
#include "rr_ngx_snapshot.h"
#include <memory>
namespace d4r::win::rr {
// Native RR execution on borrowed game resources. No fixture files, synthetic
// guide creation, child process, NVIDIA runtime, or CPU tensor transfer.
class Backend {
struct Impl;
std::unique_ptr<Impl> impl;
public:
Backend(ID3D12Device* device, const Creation& creation, const std::string& modelDirectory);
~Backend();
Backend(const Backend&) = delete;
Backend& operator=(const Backend&) = delete;
void validate_frame(const Snapshot& frame);
void evaluate(ID3D12CommandQueue* queue, const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access);
void drain();
};
}
+78
View File
@@ -0,0 +1,78 @@
#pragma once
#include <windows.h>
#include <volk.h>
#include <cstring>
#include <stdexcept>
#include <string>
#include <vector>
namespace rr_bridge {
// Owns only Vulkan objects. The caller supplies the actual D3D12 adapter LUID;
// production callers must obtain it from the borrowed game's device.
struct NativeDevice {
VkInstance instance = VK_NULL_HANDLE;
VkPhysicalDevice physical = VK_NULL_HANDLE;
VkDevice device = VK_NULL_HANDLE;
VkQueue queue = VK_NULL_HANDLE;
uint32_t family = 0;
NativeDevice() = default;
NativeDevice(const NativeDevice&) = delete;
NativeDevice& operator=(const NativeDevice&) = delete;
static void check(VkResult result, const char* operation) {
if (result != VK_SUCCESS)
throw std::runtime_error(std::string(operation) + " VkResult=" + std::to_string(result));
}
void initialize(const LUID& luid, bool inference) {
if (instance) throw std::runtime_error("RR Vulkan device already initialized");
check(volkInitialize(), "Vulkan loader");
VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO}; app.apiVersion = VK_API_VERSION_1_3;
VkInstanceCreateInfo ici{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; ici.pApplicationInfo = &app;
check(vkCreateInstance(&ici, nullptr, &instance), "Vulkan instance"); volkLoadInstance(instance);
uint32_t count = 0;
check(vkEnumeratePhysicalDevices(instance, &count, nullptr), "Vulkan device count");
std::vector<VkPhysicalDevice> devices(count);
check(vkEnumeratePhysicalDevices(instance, &count, devices.data()), "Vulkan devices");
for (auto candidate : devices) {
VkPhysicalDeviceIDProperties id{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES};
VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &id};
vkGetPhysicalDeviceProperties2(candidate, &properties);
if (id.deviceLUIDValid && !std::memcmp(id.deviceLUID, &luid, VK_LUID_SIZE)) physical = candidate;
}
if (!physical) throw std::runtime_error("D3D12/Vulkan adapter LUID mismatch");
uint32_t countFamilies = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physical, &countFamilies, nullptr);
std::vector<VkQueueFamilyProperties> properties(countFamilies);
vkGetPhysicalDeviceQueueFamilyProperties(physical, &countFamilies, properties.data());
family = countFamilies;
for (uint32_t i = 0; i < countFamilies; ++i)
if (properties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) { family = i; break; }
if (family == countFamilies) throw std::runtime_error("No Vulkan compute queue");
VkPhysicalDeviceCooperativeMatrixFeaturesKHR cm{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_KHR};
VkPhysicalDeviceVulkan13Features v13{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, &cm};
VkPhysicalDeviceVulkan12Features v12{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, &v13};
VkPhysicalDeviceVulkan11Features v11{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES, &v12};
VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &v11};
vkGetPhysicalDeviceFeatures2(physical, &features);
if (!v12.timelineSemaphore) throw std::runtime_error("Timeline semaphore unsupported");
if (inference && (!cm.cooperativeMatrix || !v12.shaderFloat16 || !v11.storageBuffer16BitAccess || !v13.subgroupSizeControl))
throw std::runtime_error("Missing RR cooperative-matrix/fp16/subgroup feature");
if (!inference) v13.pNext = nullptr;
float priority = 1;
VkDeviceQueueCreateInfo qi{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
qi.queueFamilyIndex = family; qi.queueCount = 1; qi.pQueuePriorities = &priority;
std::vector<const char*> extensions{"VK_KHR_external_memory_win32", "VK_KHR_external_semaphore_win32"};
if (inference) extensions.push_back(VK_KHR_COOPERATIVE_MATRIX_EXTENSION_NAME);
VkDeviceCreateInfo di{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, &features};
di.queueCreateInfoCount = 1; di.pQueueCreateInfos = &qi;
di.enabledExtensionCount = uint32_t(extensions.size()); di.ppEnabledExtensionNames = extensions.data();
check(vkCreateDevice(physical, &di, nullptr, &device), "Vulkan device"); volkLoadDevice(device);
vkGetDeviceQueue(device, family, 0, &queue);
}
// Callers must retire submitted work before destroying this owner. A failed
// submission may instead quarantine the owner and its dependent resources.
~NativeDevice() {
if (device) vkDestroyDevice(device, nullptr);
if (instance) vkDestroyInstance(instance, nullptr);
}
};
}
+65
View File
@@ -0,0 +1,65 @@
#pragma once
#include "rr_native_device.h"
#include <vulkan/vulkan_win32.h>
namespace rr_bridge {
// Imports the caller's D3D12 shared packet and fence. Exported Win32 handles
// remain caller-owned; Vulkan keeps its own references after successful import.
struct NativePacket {
NativeDevice& native;
VkBuffer buffer = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
VkSemaphore semaphore = VK_NULL_HANDLE;
explicit NativePacket(NativeDevice& owner) : native(owner) {}
NativePacket(const NativePacket&) = delete;
NativePacket& operator=(const NativePacket&) = delete;
void initialize(VkDeviceSize bytes, HANDLE resource, HANDLE fence) {
if (buffer || !bytes || !resource || !fence)
throw std::runtime_error("Invalid RR shared packet import");
auto memoryProperties = reinterpret_cast<PFN_vkGetMemoryWin32HandlePropertiesKHR>(
vkGetDeviceProcAddr(native.device, "vkGetMemoryWin32HandlePropertiesKHR"));
auto importSemaphore = reinterpret_cast<PFN_vkImportSemaphoreWin32HandleKHR>(
vkGetDeviceProcAddr(native.device, "vkImportSemaphoreWin32HandleKHR"));
if (!memoryProperties || !importSemaphore) throw std::runtime_error("Missing Win32 import entry points");
VkExternalMemoryBufferCreateInfo external{VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO};
external.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT;
VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, &external}; bi.size = bytes;
bi.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
NativeDevice::check(vkCreateBuffer(native.device, &bi, nullptr, &buffer), "External Vulkan buffer");
VkMemoryRequirements req; vkGetBufferMemoryRequirements(native.device, buffer, &req);
VkMemoryWin32HandlePropertiesKHR handleProperties{VK_STRUCTURE_TYPE_MEMORY_WIN32_HANDLE_PROPERTIES_KHR};
NativeDevice::check(memoryProperties(native.device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT,
resource, &handleProperties), "D3D12 resource memory types");
VkPhysicalDeviceMemoryProperties properties{};
vkGetPhysicalDeviceMemoryProperties(native.physical, &properties);
const uint32_t bits = req.memoryTypeBits & handleProperties.memoryTypeBits;
uint32_t type = properties.memoryTypeCount;
for (uint32_t i = 0; i < properties.memoryTypeCount; ++i)
if ((bits & (1u << i)) && (properties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
type = i; break;
}
if (type == properties.memoryTypeCount) throw std::runtime_error("No compatible RR packet memory type");
VkImportMemoryWin32HandleInfoKHR imported{VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR};
imported.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT; imported.handle = resource;
VkMemoryDedicatedAllocateInfo dedicated{VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, &imported};
dedicated.buffer = buffer;
VkMemoryAllocateInfo ai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &dedicated};
ai.allocationSize = req.size; ai.memoryTypeIndex = type;
NativeDevice::check(vkAllocateMemory(native.device, &ai, nullptr, &memory), "Dedicated D3D12 memory import");
NativeDevice::check(vkBindBufferMemory(native.device, buffer, memory, 0), "Bind imported buffer");
VkSemaphoreTypeCreateInfo timeline{VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO};
timeline.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE;
VkSemaphoreCreateInfo sci{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, &timeline};
NativeDevice::check(vkCreateSemaphore(native.device, &sci, nullptr, &semaphore), "Vulkan timeline semaphore");
VkImportSemaphoreWin32HandleInfoKHR si{VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_WIN32_HANDLE_INFO_KHR};
si.semaphore = semaphore; si.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT; si.handle = fence;
NativeDevice::check(importSemaphore(native.device, &si), "D3D12 fence import");
}
// The owner must retire GPU submissions before destroying an import.
~NativePacket() {
if (semaphore) vkDestroySemaphore(native.device, semaphore, nullptr);
if (buffer) vkDestroyBuffer(native.device, buffer, nullptr);
if (memory) vkFreeMemory(native.device, memory, nullptr);
}
};
}
+347
View File
@@ -0,0 +1,347 @@
// Synthetic NGX feature-13 lifecycle and delayed-submission test. No game access.
#define WIDL_EXPLICIT_AGGREGATE_RETURNS
#include <volk.h>
#include "d3d12_external.h"
#include "rr_bridge_replay.h"
#include "ngx_parameters.h"
#include "ngx_public_abi.h"
#include <dxgi1_4.h>
using namespace d4r::win;
int main(int argc, char** argv) try {
std::setvbuf(stdout, nullptr, _IONBF, 0);
if (argc != 6 && argc != 7) throw std::runtime_error("Usage: rr_ngx_probe SHIM INPUTS REFERENCE OUTPUT MODEL [contracts]");
const std::string mode = argc == 7 ? argv[6] : "";
const bool contracts = mode.find("contracts") != std::string::npos;
const bool inherited = mode.find("inherited") != std::string::npos;
const bool enhanced = mode.find("enhanced") != std::string::npos;
const bool split = mode.find("split") != std::string::npos;
const bool unavailable = mode.find("unavailable") != std::string::npos;
const bool withoutSr = mode.find("without-sr") != std::string::npos;
const bool floatGuides = mode.find("float-guides") != std::string::npos;
const bool switchGuides = mode.find("switch-guides") != std::string::npos;
const bool autoExposure = mode.find("auto-exposure") != std::string::npos;
DXGI_FORMAT depthFormat = DXGI_FORMAT_UNKNOWN;
if (mode.find("depth-d32s8-typeless") != std::string::npos) depthFormat = DXGI_FORMAT_R32G8X24_TYPELESS;
else if (mode.find("depth-d32s8") != std::string::npos) depthFormat = DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
else if (mode.find("depth-d32") != std::string::npos) depthFormat = DXGI_FORMAT_D32_FLOAT;
else if (mode.find("depth-r32-typeless") != std::string::npos) depthFormat = DXGI_FORMAT_R32_TYPELESS;
const bool stencil = depthFormat == DXGI_FORMAT_R32G8X24_TYPELESS || depthFormat == DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
if (enhanced && split) throw std::runtime_error("The split fixture currently tests legacy split barriers only");
rr_bridge::Layout layout(argv[2], argv[4], argv[3]);
const auto canonical = layout;
if (floatGuides) {
for (size_t i : {size_t(0), size_t(3)}) {
layout.planes[i].format = VK_FORMAT_R32G32B32A32_SFLOAT; layout.planes[i].bytes = 16;
}
layout.planes[1].format = VK_FORMAT_R32G32_SFLOAT; layout.planes[1].bytes = 8;
layout.planes[4].format = VK_FORMAT_R16_SFLOAT; layout.planes[4].bytes = 2;
layout.initializePacket();
}
const auto floatLayout = layout;
auto uploadFrame = [&](void* destination, unsigned index) {
if (!floatGuides) { layout.upload(destination, index); return; }
// CPU conversion belongs only to fixture initialization. The production
// backend transfers and samples the actual D3D12 formats on the GPU.
std::memset(destination, 0, size_t(layout.bytes));
for (size_t i = 0; i < layout.planes.size(); ++i) {
const auto& plane = layout.planes[i];
if (!plane.enabled || (i >= 7 && !layout.frames[index].optional[i - 7])) continue;
auto data = rr_bridge::read(layout.inputs / (std::to_string(layout.frames[index].id) + "-" + plane.name + ".bin"));
if (data.size() != size_t(layout.rw) * layout.rh * canonical.planes[i].bytes)
throw std::runtime_error("Invalid canonical input for float-guide fixture");
for (uint32_t y = 0; y < layout.rh; ++y) for (uint32_t x = 0; x < layout.rw; ++x) {
const auto* source = data.data() + (size_t(y) * layout.rw + x) * canonical.planes[i].bytes;
auto* dest = static_cast<char*>(destination) + plane.offset + y * plane.pitch + x * plane.bytes;
if ((i == 0 || i == 1 || i == 3) && plane.bytes != canonical.planes[i].bytes) {
for (uint32_t c = 0; c < plane.bytes / 4; ++c) {
uint16_t half; std::memcpy(&half, source + c * 2, 2);
const float value = float(d4r::diag::binary16::half_value(half)); std::memcpy(dest + c * 4, &value, 4);
}
} else std::memcpy(dest, source, plane.bytes);
}
}
};
ComPtr<IDXGIFactory4> factory; dx(CreateDXGIFactory1(IID_PPV_ARGS(&factory)), "NGX RR factory");
ComPtr<IDXGIAdapter1> adapter;
for (UINT i = 0; ; ++i) {
if (factory->EnumAdapters1(i, &adapter) == DXGI_ERROR_NOT_FOUND) throw std::runtime_error("No AMD adapter");
DXGI_ADAPTER_DESC1 desc{}; dx(adapter->GetDesc1(&desc), "NGX RR adapter");
if (desc.VendorId == 0x1002 && !(desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)) break;
adapter.Reset();
}
ComPtr<ID3D12Device> device; dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device)), "NGX RR device");
if (enhanced) {
D3D12_FEATURE_DATA_D3D12_OPTIONS12 support{};
dx(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS12, &support, sizeof(support)), "RR enhanced barrier support");
if (!support.EnhancedBarriersSupported) throw std::runtime_error("RR enhanced barriers unavailable");
}
auto enhancedAccess = [](bool output) {
ResourceAccess access; access.enhanced = true;
access.layout = output ? D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_UNORDERED_ACCESS : D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_SHADER_RESOURCE;
access.access = output ? D3D12_BARRIER_ACCESS_UNORDERED_ACCESS : D3D12_BARRIER_ACCESS_SHADER_RESOURCE;
return access;
};
auto switchBarrierModel = [&](ID3D12GraphicsCommandList* list, ID3D12Resource* texture, bool output, bool toEnhanced) {
// Microsoft requires COMMON at each legacy/enhanced API boundary.
// https://microsoft.github.io/DirectX-Specs/d3d/D3D12EnhancedBarriers.html
const auto legacy = output ? D3D12_RESOURCE_STATE_UNORDERED_ACCESS : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
if (toEnhanced) transition(list, texture, legacy, D3D12_RESOURCE_STATE_COMMON);
ComPtr<ID3D12GraphicsCommandList7> newer; dx(list->QueryInterface(IID_PPV_ARGS(&newer)), "RR enhanced fixture list");
const auto access = enhancedAccess(output);
D3D12_TEXTURE_BARRIER barrier{}; barrier.pResource = texture;
barrier.LayoutBefore = toEnhanced ? D3D12_BARRIER_LAYOUT_COMMON : access.layout;
barrier.LayoutAfter = toEnhanced ? access.layout : D3D12_BARRIER_LAYOUT_COMMON;
barrier.AccessBefore = toEnhanced ? D3D12_BARRIER_ACCESS_COMMON : access.access;
barrier.AccessAfter = toEnhanced ? access.access : D3D12_BARRIER_ACCESS_COMMON;
barrier.SyncBefore = barrier.SyncAfter = D3D12_BARRIER_SYNC_ALL;
D3D12_BARRIER_GROUP group{}; group.Type = D3D12_BARRIER_TYPE_TEXTURE; group.NumBarriers = 1; group.pTextureBarriers = &barrier;
newer->Barrier(1, &group);
if (!toEnhanced) transition(list, texture, D3D12_RESOURCE_STATE_COMMON, legacy);
};
ComPtr<ID3D12CommandQueue> queue; D3D12_COMMAND_QUEUE_DESC q{}; q.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
dx(device->CreateCommandQueue(&q, IID_PPV_ARGS(&queue)), "NGX RR queue");
d4r::diag::Library shim(d4r::diag::wide(std::filesystem::absolute(argv[1]).string()));
auto init = shim.symbol<unsigned(*)(const char*, int, const char*, const wchar_t*, ID3D12Device*, unsigned, const void*)>("NVSDK_NGX_D3D12_Init_ProjectID");
auto allocate = shim.symbol<unsigned(*)(void**)>("NVSDK_NGX_D3D12_AllocateParameters");
auto destroy = shim.symbol<unsigned(*)(void*)>("NVSDK_NGX_D3D12_DestroyParameters");
auto create = shim.symbol<unsigned(*)(ID3D12GraphicsCommandList*, unsigned, void*, void**)>("NVSDK_NGX_D3D12_CreateFeature");
auto evaluate = shim.symbol<unsigned(*)(ID3D12GraphicsCommandList*, const void*, void*, void*)>("NVSDK_NGX_D3D12_EvaluateFeature");
auto release = shim.symbol<unsigned(*)(void*)>("NVSDK_NGX_D3D12_ReleaseFeature");
auto shutdown = shim.symbol<unsigned(*)()>("NVSDK_NGX_D3D12_Shutdown");
auto check = [](unsigned r, const char* operation) { if (r != 1) throw std::runtime_error(std::string(operation) + " NGX=" + std::to_string(r)); };
auto require = [](bool condition, const char* operation) { if (!condition) throw std::runtime_error(operation); };
if (contracts || unavailable) {
auto requirements = shim.symbol<unsigned(*)(IDXGIAdapter*, const d4r::ngx::FeatureDiscoveryInfo*, d4r::ngx::FeatureRequirement*)>("NVSDK_NGX_D3D12_GetFeatureRequirements");
d4r::ngx::FeatureDiscoveryInfo discovery{}; discovery.FeatureID = 13;
d4r::ngx::FeatureRequirement result{};
check(requirements(adapter.Get(), &discovery, &result), "RR requirements");
require((result.FeatureSupported == 0) != unavailable, "RR requirement readiness disagrees with configured backend");
ComPtr<IDXGIAdapter> warp; dx(factory->EnumWarpAdapter(IID_PPV_ARGS(&warp)), "RR WARP adapter");
check(requirements(warp.Get(), &discovery, &result), "RR WARP requirements");
require(result.FeatureSupported != 0, "RR advertised on unsupported WARP adapter");
discovery.FeatureID = 14;
check(requirements(adapter.Get(), &discovery, &result), "Unknown feature requirements");
require(result.FeatureSupported != 0, "Unknown NGX feature advertised");
}
std::string projectId = "24480451-f00d-face-1304-0308dabad187", projectVersion = "1.0";
std::wstring featurePath = std::filesystem::absolute(argv[2]).wstring();
const wchar_t* featurePaths[] = {featurePath.c_str()};
d4r::ngx::FeatureCommonInfo common{}; common.PathListInfo = {featurePaths, 1};
check(init(projectId.c_str(), 0, projectVersion.c_str(), L"", device.Get(), 0x15, &common), "RR Init");
// Native RR defers SR startup. Its Init snapshot must survive caller-owned
// project/path strings and path-list descriptors changing before SR use.
projectId.assign("mutated-project"); projectVersion.assign("mutated-version");
featurePath.assign(L"mutated-feature-path"); featurePaths[0] = nullptr;
common.PathListInfo = {}; common.LoggingInfo.DisableOtherLoggingSinks = true;
if (contracts || unavailable) {
auto capabilities = shim.symbol<unsigned(*)(void**)>("NVSDK_NGX_D3D12_GetCapabilityParameters");
void* caps = nullptr; check(capabilities(&caps), "RR capabilities");
require(d4r::ngx::int_value(caps, "SuperSamplingDenoising.Available") == (unavailable ? 0 : 1) &&
(d4r::ngx::int_value(caps, "SuperSamplingDenoising.FeatureInitResult") == 1) != unavailable, "RR capability readiness failed");
require(d4r::ngx::int_value(caps, "SuperSampling.Available") == (withoutSr ? 0 : 1), "SR capability disagrees with dependency readiness");
check(destroy(caps), "RR destroy capabilities");
}
void* p = nullptr; check(allocate(&p), "RR Allocate");
d4r_ngx_set_uint(p, "Width", layout.rw); d4r_ngx_set_uint(p, "Height", layout.rh);
d4r_ngx_set_uint(p, "OutWidth", layout.ow); d4r_ngx_set_uint(p, "OutHeight", layout.oh);
d4r_ngx_set_int(p, "PerfQualityValue", 2);
const unsigned creationFlags = 3u | (layout.frames.front().settings.depthInverted ? 8u : 0u) | (autoExposure ? 64u : 0u);
d4r_ngx_set_uint(p, "DLSS.Feature.Create.Flags", creationFlags);
std::printf("RR_NGX_CREATION flags=%u\n", creationFlags);
if (unavailable) {
void* rejected = reinterpret_cast<void*>(uintptr_t(1));
require(create(nullptr, 13, p, &rejected) != 1 && !rejected, "Unavailable RR backend accepted creation");
check(destroy(p), "Unavailable RR destroy parameters");
check(shutdown(), "Unavailable RR shutdown");
std::puts("PASS RR_NGX_UNAVAILABLE capabilities=0 requirements=unsupported creation=rejected sr_available=1");
return 0;
}
if (contracts) {
void* rejected = reinterpret_cast<void*>(uintptr_t(1));
require(create(nullptr, 14, p, &rejected) != 1 && !rejected, "Unknown feature creation accepted");
d4r_ngx_set_uint(p, "Width", 0); rejected = reinterpret_cast<void*>(uintptr_t(1));
require(create(nullptr, 13, p, &rejected) != 1 && !rejected, "Invalid RR creation accepted");
d4r_ngx_set_uint(p, "Width", layout.rw);
for (unsigned flags : {0u, 1u, 2u, 7u, 131u}) {
d4r_ngx_set_uint(p, "DLSS.Feature.Create.Flags", flags); rejected = reinterpret_cast<void*>(uintptr_t(1));
require(create(nullptr, 13, p, &rejected) != 1 && !rejected, "Unsupported RR creation flags accepted");
}
d4r_ngx_set_uint(p, "DLSS.Feature.Create.Flags", creationFlags);
}
void* feature = nullptr; check(create(nullptr, 13, p, &feature), "RR CreateFeature");
auto textures = std::make_unique<rr_bridge::D3D12Frames>(layout, device.Get(), depthFormat);
ComPtr<ID3D12Resource> stencilUpload, stencilReadback;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT stencilFootprint{}; UINT64 stencilBytes = 0;
if (stencil) {
const auto desc = resource_desc(textures->inputs[2].Get()); UINT rows = 0; UINT64 rowBytes = 0;
device->GetCopyableFootprints(&desc, 1, 1, 0, &stencilFootprint, &rows, &rowBytes, &stencilBytes);
require(rows == layout.rh && rowBytes == layout.rw && stencilBytes && stencilBytes != UINT64_MAX, "Unexpected stencil-plane footprint");
stencilUpload = make_buffer(device.Get(), stencilBytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ);
stencilReadback = make_buffer(device.Get(), stencilBytes, D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
}
auto stencilTransition = [](ID3D12GraphicsCommandList* list, ID3D12Resource* image, D3D12_RESOURCE_STATES from, D3D12_RESOURCE_STATES to) {
D3D12_RESOURCE_BARRIER barrier{}; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition = {image, 1, from, to}; list->ResourceBarrier(1, &barrier);
};
auto stencilCopy = [&](ID3D12GraphicsCommandList* list, ID3D12Resource* image, bool upload) {
D3D12_TEXTURE_COPY_LOCATION buffer{}; buffer.pResource = upload ? stencilUpload.Get() : stencilReadback.Get();
buffer.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; buffer.PlacedFootprint = stencilFootprint;
auto texture = rr_bridge::D3D12Frames::texture(image); texture.SubresourceIndex = 1;
const auto copy = upload ? D3D12_RESOURCE_STATE_COPY_DEST : D3D12_RESOURCE_STATE_COPY_SOURCE;
stencilTransition(list, image, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, copy);
list->CopyTextureRegion(upload ? &texture : &buffer, 0, 0, 0, upload ? &buffer : &texture, nullptr);
stencilTransition(list, image, copy, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
};
auto upload = make_buffer(device.Get(), layout.bytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ);
auto scratch = make_buffer(device.Get(), layout.bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
auto readback = make_buffer(device.Get(), layout.bytes, D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
ComPtr<ID3D12Fence> fence; dx(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)), "NGX RR fixture fence");
Handle event; event.value = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if (!event.value) throw std::runtime_error("NGX RR fixture event");
for (unsigned index = 0; index < layout.frames.size(); ++index) {
if (switchGuides) {
layout = index % 2 ? floatLayout : canonical;
textures = std::make_unique<rr_bridge::D3D12Frames>(layout, device.Get(), depthFormat);
}
void* mapped = nullptr; D3D12_RANGE noRead{};
dx(upload->Map(0, &noRead, &mapped), "NGX RR upload map"); uploadFrame(mapped, index); upload->Unmap(0, nullptr);
ComPtr<ID3D12CommandAllocator> allocator; ComPtr<ID3D12GraphicsCommandList> list;
dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&allocator)), "NGX RR fixture allocator");
dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator.Get(), nullptr, IID_PPV_ARGS(&list)), "NGX RR fixture list");
if (stencil) {
dx(stencilUpload->Map(0, &noRead, &mapped), "RR stencil upload map");
std::memset(mapped, 0, size_t(stencilBytes));
for (uint32_t y = 0; y < layout.rh; ++y) for (uint32_t x = 0; x < layout.rw; ++x)
static_cast<uint8_t*>(mapped)[stencilFootprint.Offset + y * stencilFootprint.Footprint.RowPitch + x] = uint8_t(x * 13 + y * 17 + index * 29);
stencilUpload->Unmap(0, nullptr); stencilCopy(list.Get(), textures->inputs[2].Get(), true);
}
uint32_t optionalMask = 0;
for (unsigned i = 0; i < 4; ++i) if (layout.frames[index].optional[i]) optionalMask |= 1u << i;
textures->recordInputs(list.Get(), upload.Get(), scratch.Get(), optionalMask);
if (enhanced) {
for (size_t i = 0; i < textures->inputs.size(); ++i)
if (textures->inputs[i] && (i < 7 || (optionalMask & (1u << (i - 7)))))
switchBarrierModel(list.Get(), textures->inputs[i].Get(), false, true);
switchBarrierModel(list.Get(), textures->output.Get(), true, true);
}
ComPtr<ID3D12CommandAllocator> prefixAllocator;
ComPtr<ID3D12GraphicsCommandList> prefix;
if (inherited) {
// The preceding list publishes every resource state, including an
// output which has not otherwise been used by a fixture upload.
if (!enhanced) {
transition(list.Get(), textures->output.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COMMON);
transition(list.Get(), textures->output.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
}
dx(list->Close(), "NGX RR prefix close"); prefix = list; prefixAllocator = allocator;
list.Reset(); allocator.Reset();
dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&allocator)), "NGX RR inherited allocator");
dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator.Get(), nullptr, IID_PPV_ARGS(&list)), "NGX RR inherited list");
}
const char* names[] = {"Color", "MotionVectors", "Depth", "GBuffer.Normals", "GBuffer.Roughness", "DLSS.Input.DiffuseAlbedo", "DLSS.Input.SpecularAlbedo"};
for (size_t i = 0; i < 7; ++i) d4r_ngx_set_d3d12_resource(p, names[i], textures->inputs[i].Get());
d4r_ngx_set_d3d12_resource(p, "Output", textures->output.Get());
const char* optionalNames[] = {"DLSSD.SpecularHitDistance", "DLSS.DisocclusionMask", "DLSSD.ScreenSpaceSubsurfaceScatteringGuide", "DLSSD.ColorBeforeParticles"};
for (size_t i = 0; i < 4; ++i)
d4r_ngx_set_d3d12_resource(p, optionalNames[i], layout.frames[index].optional[i] ? textures->inputs[i + 7].Get() : nullptr);
auto settings = layout.frames[index].settings;
d4r_ngx_set_float(p, "Jitter.Offset.X", settings.jitter[0]); d4r_ngx_set_float(p, "Jitter.Offset.Y", settings.jitter[1]);
d4r_ngx_set_float(p, "MV.Scale.X", settings.motionScale[0]); d4r_ngx_set_float(p, "MV.Scale.Y", settings.motionScale[1]);
d4r_ngx_set_float(p, "DLSS.Pre.Exposure", settings.preExposure);
d4r_ngx_set_uint(p, "Reset", settings.reset); d4r_ngx_set_uint(p, "DLSS.Use.HW.Depth", settings.hardwareDepth);
d4r_ngx_set_uint(p, "DLSS.Roughness.Mode", settings.roughnessPacked);
d4r_ngx_set_void(p, "WorldToViewMatrix", settings.worldToView); d4r_ngx_set_void(p, "ViewToClipMatrix", settings.viewToClip);
const char* states[] = {"D4R.Color.State", "D4R.Motion.State", "D4R.Depth.State", "D4R.Normals.State", "D4R.Roughness.State", "D4R.Diffuse.State", "D4R.Specular.State"};
if (!inherited) {
for (const auto* state : states) d4r_ngx_set_uint(p, state, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
d4r_ngx_set_uint(p, "D4R.Output.State", D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
}
if (contracts && index == 0) {
auto rejects = [&](const char* name) { require(evaluate(list.Get(), feature, p, nullptr) != 1, name); };
if (depthFormat == DXGI_FORMAT_D32_FLOAT || stencil) {
d4r_ngx_set_uint(p, "DLSS.Input.Depth.Subrect.Base.X", 1); rejects("Cropped depth-stencil accepted");
d4r_ngx_set_uint(p, "DLSS.Input.Depth.Subrect.Base.X", 0);
std::puts("RR_NGX_DEPTH_SUBRECT cropped_rejected=1");
}
d4r_ngx_set_void(p, "ViewToClipMatrix", nullptr); rejects("Missing RR matrix accepted");
d4r_ngx_set_void(p, "ViewToClipMatrix", settings.viewToClip);
d4r_ngx_set_d3d12_resource(p, "DLSSD.SpecularHitDistance", textures->inputs[0].Get()); rejects("Invalid RR scalar guide format accepted");
d4r_ngx_set_d3d12_resource(p, "DLSSD.SpecularHitDistance", layout.frames[index].optional[0] ? textures->inputs[7].Get() : nullptr);
d4r_ngx_set_uint(p, "DLSS.Render.Subrect.Dimensions.Width", layout.rw + 1); rejects("Unsupported RR dynamic extent accepted");
d4r_ngx_set_uint(p, "DLSS.Render.Subrect.Dimensions.Width", layout.rw);
d4r_ngx_set_uint(p, "DLSS.Depth.Type", 1); rejects("Unsupported RR encoding accepted");
d4r_ngx_set_uint(p, "DLSS.Depth.Type", 0);
d4r_ngx_set_uint(p, "DLSS.Input.Color.Subrect.Base.X", layout.rw); rejects("Invalid RR subrect accepted");
d4r_ngx_set_uint(p, "DLSS.Input.Color.Subrect.Base.X", 0);
d4r_ngx_set_d3d12_resource(p, "Output", textures->inputs[0].Get()); rejects("Aliased or invalid RR output accepted");
d4r_ngx_set_d3d12_resource(p, "Output", textures->output.Get());
std::puts("RR_NGX_NEGATIVE rejected=13 command_list_preserved=1");
}
if (split && index == 0) {
D3D12_RESOURCE_BARRIER barrier{}; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition = {textures->inputs[0].Get(), 0, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COPY_SOURCE};
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_BEGIN_ONLY; list->ResourceBarrier(1, &barrier);
require(evaluate(list.Get(), feature, p, nullptr) != 1, "NGX RR accepted unfinished split barrier");
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_END_ONLY; list->ResourceBarrier(1, &barrier);
transition(list.Get(), textures->inputs[0].Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
std::puts("RR_NGX_SPLIT unfinished_rejected=1 paired_before_submit=1");
}
check(evaluate(list.Get(), feature, p, nullptr), "RR EvaluateFeature");
// Both pointer payloads and parameter scalars change before submission.
settings.worldToView[0] = 999; settings.viewToClip[5] = 999;
d4r_ngx_set_float(p, "DLSS.Pre.Exposure", 999);
d4r_ngx_set_d3d12_resource(p, "Color", nullptr);
auto output = textures->output;
if (enhanced) texture_copy_barrier(list.Get(), output.Get(), enhancedAccess(true), false, false);
else transition(list.Get(), output.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE);
auto source = rr_bridge::D3D12Frames::texture(output.Get());
auto dest = rr_bridge::D3D12Frames::placed(readback.Get(), layout.outputOffset, layout.outputPitch,
DXGI_FORMAT_R16G16B16A16_FLOAT, layout.ow, layout.oh);
list->CopyTextureRegion(&dest, 0, 0, 0, &source, nullptr);
if (enhanced) {
texture_copy_barrier(list.Get(), output.Get(), enhancedAccess(true), false, true);
for (size_t i = 0; i < textures->inputs.size(); ++i)
if (textures->inputs[i] && (i < 7 || (optionalMask & (1u << (i - 7)))))
switchBarrierModel(list.Get(), textures->inputs[i].Get(), false, false);
switchBarrierModel(list.Get(), output.Get(), true, false);
} else transition(list.Get(), output.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
if (stencil) stencilCopy(list.Get(), textures->inputs[2].Get(), false);
if (index + 1 == layout.frames.size()) {
check(release(feature), "RR Release before submission"); feature = nullptr;
check(destroy(p), "RR Destroy parameters before submission"); p = nullptr;
textures.reset(); output.Reset();
}
dx(list->Close(), "NGX RR fixture close");
ID3D12CommandList* lists[] = {prefix ? prefix.Get() : list.Get(), list.Get()};
queue->ExecuteCommandLists(inherited ? 2 : 1, lists);
dx(queue->Signal(fence.Get(), index + 1), "NGX RR fixture signal");
dx(fence->SetEventOnCompletion(index + 1, event.value), "NGX RR fixture completion");
if (WaitForSingleObject(event.value, 10000) != WAIT_OBJECT_0) throw std::runtime_error("NGX RR fixture timeout");
D3D12_RANGE range{0, SIZE_T(layout.bytes)}; dx(readback->Map(0, &range, &mapped), "NGX RR readback map");
try { layout.validate(mapped, index); } catch (...) { readback->Unmap(0, nullptr); throw; }
readback->Unmap(0, nullptr);
if (stencil) {
D3D12_RANGE stencilRange{0, SIZE_T(stencilBytes)};
dx(stencilReadback->Map(0, &stencilRange, &mapped), "RR stencil verification map");
bool exact = true;
for (uint32_t y = 0; y < layout.rh; ++y) for (uint32_t x = 0; x < layout.rw; ++x)
exact &= static_cast<const uint8_t*>(mapped)[stencilFootprint.Offset + y * stencilFootprint.Footprint.RowPitch + x] == uint8_t(x * 13 + y * 17 + index * 29);
stencilReadback->Unmap(0, nullptr); require(exact, "RR bridge changed stencil values");
}
std::printf("RR_NGX_FRAME index=%u snapshot_mutated=1 exact=1 optional_mask=%u hardware_depth=%u depth_inverted=%u\n",
index, optionalMask, settings.hardwareDepth, settings.depthInverted);
}
check(shutdown(), "RR Shutdown");
if (contracts) std::puts("PASS RR_NGX_CONTRACTS capabilities=1 requirements=1 warp_rejected=1 invalid_inputs_rejected=1");
if (inherited) std::puts("PASS RR_NGX_INHERITED prefix_list=1 caller_states=0");
if (enhanced) std::puts("PASS RR_NGX_ENHANCED tracked_access=1 restored=1");
if (split) std::puts("PASS RR_NGX_SPLIT unfinished_rejected=1");
if (withoutSr) std::puts("PASS RR_NGX_WITHOUT_SR rr_executed=1 sr_available=0");
if (floatGuides) std::puts("PASS RR_NGX_FLOAT_GUIDES color_rgba32=1 motion_rg32=1 normals_rgba32=1 roughness_r16=1");
if (switchGuides) std::puts("PASS RR_NGX_SWITCH_GUIDES alternate_each_frame=1 feature_recreated=0");
if (autoExposure) std::puts("PASS RR_NGX_FLAGS hdr=1 render_motion=1 auto_exposure=1");
if (depthFormat != DXGI_FORMAT_UNKNOWN) std::printf("PASS RR_NGX_DEPTH format=%u stencil_preserved=%u\n", unsigned(depthFormat), stencil);
std::puts("PASS RR_NGX_LIFECYCLE feature=13 snapshot_mutation=1 release_before_submit=1 parameter_destroy_before_submit=1");
return 0;
} catch (const std::exception& error) { std::fprintf(stderr, "FAIL RR_NGX %s\n", error.what()); return 1; }
+122
View File
@@ -0,0 +1,122 @@
#pragma once
#include "ngx_parameters.h"
#include "../../engine/runtime.h"
#include <d3d12.h>
#include <wrl/client.h>
#include <array>
#include <cmath>
#include <cstring>
#include <stdexcept>
#include <string>
namespace d4r::win::rr {
inline unsigned number(void* parameters, const char* name, unsigned fallback = 0) {
unsigned value = 0;
if (d4r_ngx_get_uint(parameters, name, &value) == 1) return value;
int signedValue = 0;
if (d4r_ngx_get_int(parameters, name, &signedValue) == 1 && signedValue >= 0) return unsigned(signedValue);
return fallback;
}
struct Creation {
unsigned width = 0, height = 0, outWidth = 0, outHeight = 0;
unsigned flags = 0, outputSubrects = 0, hardwareDepth = 0, roughnessMode = 0;
unsigned depthType = 0, normalsType = 0, roughnessType = 0, diffuseType = 0, specularType = 0;
int quality = 0;
std::array<unsigned, 6> presets{};
explicit Creation(void* parameters) {
if (!parameters) throw std::runtime_error("Null RR creation parameters");
width = number(parameters, "Width"); height = number(parameters, "Height");
outWidth = number(parameters, "OutWidth"); outHeight = number(parameters, "OutHeight");
if (!width || !height || !outWidth || !outHeight || width > 16384 || height > 16384 || outWidth > 16384 || outHeight > 16384)
throw std::runtime_error("Invalid RR creation geometry");
flags = number(parameters, "DLSS.Feature.Create.Flags");
outputSubrects = number(parameters, "DLSS.Enable.Output.Subrects");
hardwareDepth = number(parameters, "DLSS.Use.HW.Depth");
roughnessMode = number(parameters, "DLSS.Roughness.Mode");
depthType = number(parameters, "DLSS.Depth.Type"); normalsType = number(parameters, "DLSS.Normals.Type");
roughnessType = number(parameters, "DLSS.Roughness.Type"); diffuseType = number(parameters, "DLSS.Diffuse.Type");
specularType = number(parameters, "DLSS.Specular.Type");
quality = ngx::int_value(parameters, "PerfQualityValue");
const char* names[] = {"RayReconstruction.Hint.Render.Preset.DLAA", "RayReconstruction.Hint.Render.Preset.Quality",
"RayReconstruction.Hint.Render.Preset.Balanced", "RayReconstruction.Hint.Render.Preset.Performance",
"RayReconstruction.Hint.Render.Preset.UltraPerformance", "RayReconstruction.Hint.Render.Preset.UltraQuality"};
for (size_t i = 0; i < presets.size(); ++i) presets[i] = number(parameters, names[i]);
}
};
// All data that may outlive EvaluateFeature is owned here. No matrix pointer
// or resource pointer depends on the caller's parameter object's lifetime.
struct Snapshot {
enum Plane : size_t { Color, Motion, Depth, Normals, Roughness, Diffuse, Specular, Output,
Exposure, HitDistance, Disocclusion, Subsurface, BeforeParticles, Count };
std::array<Microsoft::WRL::ComPtr<ID3D12Resource>, Count> resources;
std::array<std::array<unsigned, 2>, Count> origins{};
RFrameSettings settings;
unsigned renderWidth = 0, renderHeight = 0;
unsigned depthType = 0, normalsType = 0, roughnessType = 0, diffuseType = 0, specularType = 0;
float exposureScale = 1;
static ID3D12Resource* resource(void* parameters, const char* name, const char* alias = nullptr) {
ID3D12Resource* value = nullptr;
(void)d4r_ngx_get_d3d12_resource(parameters, name, &value);
if (!value && alias) (void)d4r_ngx_get_d3d12_resource(parameters, alias, &value);
return value;
}
Snapshot(const Creation& creation, void* parameters) {
if (!parameters) throw std::runtime_error("Null RR frame parameters");
const char* names[] = {"Color", "MotionVectors", "Depth", "GBuffer.Normals", "GBuffer.Roughness",
"DLSS.Input.DiffuseAlbedo", "DLSS.Input.SpecularAlbedo", "Output", "ExposureTexture",
"DLSSD.SpecularHitDistance", "DLSS.DisocclusionMask", "DLSSD.ScreenSpaceSubsurfaceScatteringGuide", "DLSSD.ColorBeforeParticles"};
for (size_t i = 0; i < Count; ++i)
resources[i] = resource(parameters, names[i], i == Diffuse ? "GBuffer.DiffuseAlbedo" : i == Specular ? "GBuffer.SpecularAlbedo" : nullptr);
renderWidth = number(parameters, "DLSS.Render.Subrect.Dimensions.Width", creation.width);
renderHeight = number(parameters, "DLSS.Render.Subrect.Dimensions.Height", creation.height);
if (!renderWidth) renderWidth = creation.width;
if (!renderHeight) renderHeight = creation.height;
const char* prefixes[] = {"DLSS.Input.Color", "DLSS.Input.MV", "DLSS.Input.Depth", "DLSS.Input.Normals",
"DLSS.Input.Roughness", "DLSS.Input.DiffuseAlbedo", "DLSS.Input.SpecularAlbedo", "DLSS.Output"};
for (size_t i = 0; i < 8; ++i) {
origins[i][0] = number(parameters, (std::string(prefixes[i]) + ".Subrect.Base.X").c_str());
origins[i][1] = number(parameters, (std::string(prefixes[i]) + ".Subrect.Base.Y").c_str());
}
const char* optionalPrefixes[] = {"DLSSD.SpecularHitDistance", "DLSS.DisocclusionMask",
"DLSSD.ScreenSpaceSubsurfaceScatteringGuide", "DLSSD.ColorBeforeParticles"};
for (size_t i = HitDistance; i < Count; ++i) {
origins[i][0] = number(parameters, (std::string(optionalPrefixes[i - HitDistance]) + ".Subrect.Base.X").c_str());
origins[i][1] = number(parameters, (std::string(optionalPrefixes[i - HitDistance]) + ".Subrect.Base.Y").c_str());
}
settings.jitter[0] = ngx::float_value(parameters, "Jitter.Offset.X");
settings.jitter[1] = ngx::float_value(parameters, "Jitter.Offset.Y");
settings.motionScale[0] = ngx::float_value(parameters, "MV.Scale.X", 1);
settings.motionScale[1] = ngx::float_value(parameters, "MV.Scale.Y", 1);
settings.preExposure = ngx::float_value(parameters, "DLSS.Pre.Exposure", 1);
settings.frameTimeMs = ngx::float_value(parameters, "FrameTimeDeltaInMsec", 16.666667f);
exposureScale = ngx::float_value(parameters, "DLSS.Exposure.Scale", 1);
settings.reset = number(parameters, "Reset") != 0;
settings.depthInverted = (creation.flags & 8) != 0;
settings.hardwareDepth = number(parameters, "DLSS.Use.HW.Depth", creation.hardwareDepth) != 0;
// D/E consume a bound roughness texture even in packed mode. Only the
// absent-texture case uses normals.w; preset F has a different contract.
settings.roughnessPacked = number(parameters, "DLSS.Roughness.Mode", creation.roughnessMode) == 1 && !resources[Roughness];
if (settings.roughnessPacked) { resources[Roughness] = resources[Normals]; origins[Roughness] = origins[Normals]; }
depthType = number(parameters, "DLSS.Depth.Type", creation.depthType);
normalsType = number(parameters, "DLSS.Normals.Type", creation.normalsType);
roughnessType = number(parameters, "DLSS.Roughness.Type", creation.roughnessType);
diffuseType = number(parameters, "DLSS.Diffuse.Type", creation.diffuseType);
specularType = number(parameters, "DLSS.Specular.Type", creation.specularType);
const char* matrixNames[] = {"WorldToViewMatrix", "ViewToClipMatrix"};
float* matrices[] = {settings.worldToView, settings.viewToClip};
for (unsigned i = 0; i < 2; ++i) {
void* source = nullptr;
if (d4r_ngx_get_void(parameters, matrixNames[i], &source) != 1 || !source)
throw std::runtime_error(std::string("Missing RR matrix: ") + matrixNames[i]);
std::memcpy(matrices[i], source, sizeof(float) * 16);
for (unsigned n = 0; n < 16; ++n)
if (!std::isfinite(matrices[i][n])) throw std::runtime_error("Nonfinite RR matrix");
}
for (float value : {settings.jitter[0], settings.jitter[1], settings.motionScale[0], settings.motionScale[1],
settings.preExposure, settings.frameTimeMs, exposureScale})
if (!std::isfinite(value)) throw std::runtime_error("Nonfinite RR frame scalar");
if (settings.preExposure <= 0 || exposureScale <= 0 || settings.frameTimeMs < 0)
throw std::runtime_error("Invalid RR exposure or frame time");
}
};
}
+88
View File
@@ -0,0 +1,88 @@
// CPU-only checks of the NGX RR snapshot contract. No model or GPU dispatch.
#include <volk.h>
#include "rr_ngx_snapshot.h"
#include <cstdio>
#include <limits>
namespace {
struct Parameters {
void* value = d4r_ngx_parameters_create();
Parameters() { if (!value) throw std::runtime_error("Parameter allocation failed"); }
~Parameters() { if (value) d4r_ngx_parameters_destroy(value); }
};
void require(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
template<class F> void rejects(F operation, const char* message) {
try { operation(); } catch (const std::runtime_error&) { return; }
throw std::runtime_error(message);
}
}
int main() try {
using namespace d4r::win::rr;
Parameters parameters;
void* p = parameters.value;
// RR callers can publish dimensions as signed NGX integers.
d4r_ngx_set_int(p, "Width", 64); d4r_ngx_set_uint(p, "Height", 48);
d4r_ngx_set_uint(p, "OutWidth", 128); d4r_ngx_set_uint(p, "OutHeight", 96);
d4r_ngx_set_int(p, "DLSS.Feature.Create.Flags", 8);
d4r_ngx_set_int(p, "DLSS.Use.HW.Depth", 1);
d4r_ngx_set_int(p, "DLSS.Roughness.Mode", 1);
d4r_ngx_set_uint(p, "RayReconstruction.Hint.Render.Preset.Quality", 4);
Creation creation(p);
require(creation.width == 64 && creation.presets[1] == 4, "RR creation values lost");
float view[16]{}, clip[16]{};
for (unsigned i = 0; i < 16; ++i) { view[i] = float(i + 1); clip[i] = float(i + 32); }
d4r_ngx_set_void(p, "WorldToViewMatrix", view); d4r_ngx_set_void(p, "ViewToClipMatrix", clip);
d4r_ngx_set_float(p, "Jitter.Offset.X", .25f);
d4r_ngx_set_float(p, "DLSS.Pre.Exposure", 2.f);
d4r_ngx_set_uint(p, "DLSS.Input.Normals.Subrect.Base.X", 7);
d4r_ngx_set_uint(p, "DLSSD.SpecularHitDistance.Subrect.Base.X", 5);
d4r_ngx_set_uint(p, "DLSSD.ColorBeforeParticles.Subrect.Base.Y", 9);
d4r_ngx_set_uint(p, "DLSS.Use.HW.Depth", 0);
Snapshot frame(creation, p);
view[0] = -91; clip[15] = -93;
d4r_ngx_set_float(p, "DLSS.Pre.Exposure", 3.f);
d4r_ngx_set_uint(p, "Width", 777);
d4r_ngx_set_uint(p, "RayReconstruction.Hint.Render.Preset.Quality", 5);
require(frame.settings.worldToView[0] == 1 && frame.settings.viewToClip[15] == 47, "RR matrices were not copied");
require(frame.settings.preExposure == 2 && frame.settings.jitter[0] == .25f, "RR scalars were not copied");
require(creation.width == 64 && creation.presets[1] == 4, "RR creation depends on mutable parameters");
require(frame.settings.depthInverted && !frame.settings.hardwareDepth, "RR frame override lost");
require(frame.renderWidth == 64 && frame.renderHeight == 48, "RR render dimensions lost creation fallback");
require(frame.settings.roughnessPacked && frame.origins[Snapshot::Roughness][0] == 7, "RR packed roughness origin lost");
require(frame.origins[Snapshot::HitDistance][0] == 5 && frame.origins[Snapshot::BeforeParticles][1] == 9,
"RR optional guide origins lost");
auto copied = frame;
require(copied.settings.worldToView[0] == 1, "Copied RR snapshot lost matrix ownership");
{
Parameters frameParameters;
d4r_ngx_set_void(frameParameters.value, "WorldToViewMatrix", view);
d4r_ngx_set_void(frameParameters.value, "ViewToClipMatrix", clip);
Snapshot baseline(creation, frameParameters.value);
require(baseline.settings.hardwareDepth && baseline.settings.roughnessPacked,
"RR frame settings lost creation baseline");
require(baseline.settings.preExposure == 1 && baseline.settings.motionScale[0] == 1,
"RR frame scalar defaults incorrect");
}
view[3] = std::numeric_limits<float>::infinity();
rejects([&] { Snapshot bad(creation, p); }, "Nonfinite RR matrix accepted");
view[3] = 4;
d4r_ngx_set_float(p, "DLSS.Pre.Exposure", 0);
rejects([&] { Snapshot bad(creation, p); }, "Zero RR pre-exposure accepted");
d4r_ngx_set_float(p, "DLSS.Pre.Exposure", 1);
d4r_ngx_set_float(p, "Jitter.Offset.Y", std::numeric_limits<float>::quiet_NaN());
rejects([&] { Snapshot bad(creation, p); }, "Nonfinite RR jitter accepted");
d4r_ngx_set_float(p, "Jitter.Offset.Y", 0);
d4r_ngx_set_void(p, "ViewToClipMatrix", nullptr);
rejects([&] { Snapshot bad(creation, p); }, "Missing RR matrix accepted");
d4r_ngx_set_uint(p, "Width", 0);
rejects([&] { Creation bad(p); }, "Zero RR creation width accepted");
// The completed snapshot must still be valid after the parameter object dies.
d4r_ngx_parameters_destroy(parameters.value); parameters.value = nullptr;
require(copied.settings.viewToClip[15] == 47 && copied.settings.preExposure == 2, "RR snapshot retained parameter storage");
std::puts("PASS RR_SNAPSHOT cpu_only=1 matrices_owned=1 scalars_owned=1 creation_fallback=1 invalid_rejected=1");
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "FAIL RR_SNAPSHOT %s\n", error.what()); return 1;
}
+179
View File
@@ -0,0 +1,179 @@
"""Run the synthetic production NGX feature-13 harness in a bounded process."""
import argparse
import hashlib
import json
import os
import shutil
from pathlib import Path
import subprocess
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--contracts', action='store_true', help='also check capability readiness and negative contracts')
parser.add_argument('--inherited', action='store_true', help='inherit access from a preceding list in the same submission batch')
parser.add_argument('--enhanced', action='store_true', help='exercise enhanced texture access and restoration')
parser.add_argument('--split', action='store_true', help='reject a legacy split barrier, then complete the pair and evaluate')
parser.add_argument('--unavailable', choices=('disabled', 'missing-model', 'fsr'), help='test unavailable RR configuration without inference')
parser.add_argument('--without-sr', action='store_true', help='remove SR dependency configuration and require native RR execution')
parser.add_argument('--float-guides', action='store_true', help='exercise equivalent float32 color/motion/normals and scalar roughness')
parser.add_argument('--switch-guides', action='store_true', help='alternate canonical and float guide formats without recreating feature')
parser.add_argument('--auto-exposure', action='store_true', help='also set the public NGX AutoExposure creation flag')
parser.add_argument('--depth-format', choices=('r32', 'r32-typeless', 'd32', 'd32s8', 'd32s8-typeless'), default='r32', help='exercise native depth-plane copy format')
parser.add_argument('--player-config', action='store_true', help='stage a private player folder and select RR only through d4r.ini')
parser.add_argument('--use-existing-ini', action='store_true', help='preserve the selected runtime INI when staging player validation')
for name in ('probe', 'shim', 'runtime', 'core', 'dlss', 'model', 'inputs', 'reference', 'output'):
parser.add_argument('--' + name, required=True, type=lambda value: Path(value).resolve())
args = parser.parse_args()
if args.use_existing_ini and not args.player_config:
parser.error('--use-existing-ini requires --player-config')
if args.player_config and (args.without_sr or args.unavailable or not args.contracts):
parser.error('--player-config requires --contracts, configured SR dependencies and native RR')
if args.enhanced and args.split:
parser.error('--split currently covers legacy barriers; use a separate --enhanced run')
if args.without_sr and (args.unavailable or not args.contracts):
parser.error('--without-sr requires --contracts and a configured native RR backend')
if args.switch_guides and not args.float_guides:
parser.error('--switch-guides requires --float-guides')
build = Path(__file__).resolve().parents[2] / 'build'
if not args.output.is_relative_to(build.resolve()):
parser.error('output must stay under ignored build/')
args.output.mkdir(parents=True, exist_ok=False)
env = {key: value for key, value in os.environ.items()
if not key.startswith('D4R_') and key not in ('ZLUDA_CUDA_LIB', 'ZLUDA_LOG_DIR', 'ZLUDA_CACHE_DIR')}
runtime = args.runtime
native = runtime / 'native'
if (native / 'gfx1101').is_dir():
native = native / 'gfx1101'
env.update(HIP_PATH=str(runtime / 'hip'), D4R_HIP_ROOT=str(runtime / 'hip'), D4R_NVCUDA_DLL=str(runtime / 'zluda/nvcuda.dll'),
ZLUDA_CUDA_LIB=str(runtime / 'zluda/nvcuda.dll'), D4R_DLSS_DLL=str(args.dlss),
D4R_NGX_CORE=str(args.core), D4R_NVAPI_DLL=str(runtime / 'nvapi/nvapi64.dll'),
D4R_NVAPI_BACKEND=str(runtime / 'nvapi/backend/nvapi64.dll'),
D4R_FORMAT_MODULE=str(runtime / 'pixel_convert_gfx1101.hsaco'),
D4R_ZLUDA_NATIVE_DIR=str(native), D4R_QUIET_API='1',
D4R_ZLUDA_WMMA='1', D4R_ZLUDA_WMMA_F16_REFERENCE='1',
D4R_NGX_DATA_DIR=str(args.output / 'ngx-data'), D4R_RR_BACKEND='native', D4R_RR_MODEL_DIR=str(args.model))
if args.unavailable == 'disabled':
env.pop('D4R_RR_BACKEND')
elif args.unavailable == 'missing-model':
missing = args.output / 'missing-model'
env['D4R_RR_MODEL_DIR'] = str(missing)
elif args.unavailable == 'fsr':
env['D4R_RR_BACKEND'] = 'fsr'
if args.without_sr:
for key in ('HIP_PATH', 'D4R_HIP_ROOT', 'D4R_NVCUDA_DLL', 'ZLUDA_CUDA_LIB', 'D4R_DLSS_DLL',
'D4R_NGX_CORE', 'D4R_NVAPI_DLL', 'D4R_NVAPI_BACKEND', 'D4R_FORMAT_MODULE', 'D4R_ZLUDA_NATIVE_DIR'):
env.pop(key, None)
else:
env['PATH'] = str(runtime / 'hip/bin') + os.pathsep + str(runtime / 'zluda') + os.pathsep + env['PATH']
command = [str(args.probe), str(args.shim), str(args.inputs), str(args.reference),
str(args.output / 'frames'), str(args.model)]
if args.player_config:
staged = args.output / 'player'
shutil.copytree(runtime, staged)
shutil.copy2(args.shim, staged / 'd4r_nvngx.dll')
for source, relative in ((args.core, 'ngx/_nvngx.dll'), (args.dlss, 'dlss/nvngx_dlss.dll')):
target = staged / relative
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, target)
if not args.use_existing_ini:
config = (Path(__file__).resolve().parents[2] / 'packaging/windows/d4r.ini').read_text()
config = config.replace('Backend = off', 'Backend = native').replace('ModelDir = auto', 'ModelDir = ' + str(args.model))
(staged / 'd4r.ini').write_text(config)
env = {key: value for key, value in os.environ.items()
if not key.startswith('D4R_') and key not in ('HIP_PATH', 'ZLUDA_CUDA_LIB', 'ZLUDA_LOG_DIR', 'ZLUDA_CACHE_DIR')}
command[1] = str(staged / 'd4r_nvngx.dll')
mode = '-'.join(name for name in ('contracts', 'inherited', 'enhanced', 'split', 'unavailable') if getattr(args, name))
if args.without_sr:
mode += '-without-sr'
if args.float_guides:
mode += '-float-guides'
if args.switch_guides:
mode += '-switch-guides'
if args.auto_exposure:
mode += '-auto-exposure'
if args.depth_format != 'r32':
mode += '-depth-' + args.depth_format
if mode:
command.append(mode)
report = {'passed': False, 'gameAccessed': False, 'qualityReference': False,
'artifacts': {str(path): hashlib.sha256(path.read_bytes()).hexdigest()
for path in (args.probe, args.shim, args.core, args.dlss)}}
try:
with (args.output / 'stdout.log').open('wb') as out, (args.output / 'stderr.log').open('wb') as err:
result = subprocess.run(command, env=env, stdout=out, stderr=err, timeout=180)
report['exitCode'] = result.returncode
text = (args.output / 'stdout.log').read_text(errors='replace')
if args.unavailable:
if result.returncode or 'PASS RR_NGX_UNAVAILABLE' not in text:
raise RuntimeError('Unavailable NGX RR checks failed; inspect ' + str(args.output))
report.update(passed=True, unavailable=args.unavailable, rrAvailable=False, srAvailable=True,
creationRejected=True, frames=0)
print('PASS NGX RR unavailable=' + args.unavailable + ' sr_available=1')
return
if result.returncode or 'PASS RR_NGX_LIFECYCLE' not in text:
raise RuntimeError('NGX RR harness failed; inspect ' + str(args.output))
if args.contracts and 'PASS RR_NGX_CONTRACTS' not in text:
raise RuntimeError('NGX RR contract checks incomplete')
if args.inherited and 'PASS RR_NGX_INHERITED' not in text:
raise RuntimeError('NGX RR inherited access checks incomplete')
for name in ('enhanced', 'split'):
if getattr(args, name) and 'PASS RR_NGX_' + name.upper() not in text:
raise RuntimeError('NGX RR ' + name + ' access checks incomplete')
if args.without_sr and ('PASS RR_NGX_WITHOUT_SR' not in text or 'D4R_RUNTIME platform=Windows' in text):
raise RuntimeError('NGX RR did not prove execution without initialized SR runtime')
if args.float_guides and 'PASS RR_NGX_FLOAT_GUIDES' not in text:
raise RuntimeError('NGX RR alternate float guide checks incomplete')
if args.switch_guides and 'PASS RR_NGX_SWITCH_GUIDES' not in text:
raise RuntimeError('NGX RR temporal guide-format switching checks incomplete')
if args.auto_exposure and 'PASS RR_NGX_FLAGS' not in text:
raise RuntimeError('NGX RR creation flag checks incomplete')
depth_values = {'r32-typeless': 39, 'd32': 40, 'd32s8': 20, 'd32s8-typeless': 19}
if args.depth_format != 'r32':
marker = f'PASS RR_NGX_DEPTH format={depth_values[args.depth_format]} stencil_preserved={int(args.depth_format.startswith("d32s8"))}'
if marker not in text:
raise RuntimeError('NGX RR depth-format/stencil checks incomplete')
if args.contracts and args.depth_format.startswith('d32') and 'RR_NGX_DEPTH_SUBRECT cropped_rejected=1' not in text:
raise RuntimeError('NGX RR cropped depth-stencil rejection missing')
expected = [line.split()[1] + '-out.bin' for line in (args.inputs / 'frames.txt').read_text().splitlines()
if line.startswith('frame ')]
outputs = sorted(path.name for path in (args.output / 'frames').glob('*-out.bin'))
if outputs != sorted(expected):
raise RuntimeError('Incomplete NGX RR output set')
for name in expected:
if (args.output / 'frames' / name).read_bytes() != (args.reference / name).read_bytes():
raise RuntimeError('NGX RR reference mismatch: ' + name)
optional_names = ('hitdistance', 'disocclusion', 'sss', 'particles')
optional_frames = {name: sum((args.inputs / (frame.removesuffix('-out.bin') + '-' + name + '.bin')).is_file()
for frame in expected) for name in optional_names}
masks = [sum(1 << index for index, name in enumerate(optional_names)
if (args.inputs / (frame.removesuffix('-out.bin') + '-' + name + '.bin')).is_file())
for frame in expected]
frame_lines = [line.split() for line in (args.inputs / 'frames.txt').read_text().splitlines() if line.startswith('frame ')]
for index, mask in enumerate(masks):
depth_mode = int(frame_lines[index][8])
marker = (f'RR_NGX_FRAME index={index} snapshot_mutated=1 exact=1 optional_mask={mask}'
f' hardware_depth={int(bool(depth_mode & 2))} depth_inverted={int(bool(depth_mode & 1))}')
if marker not in text:
raise RuntimeError('Incomplete NGX optional-guide/depth setting checks')
report.update(passed=True, frames=len(expected), standaloneExact=True, contracts=args.contracts, inherited=args.inherited,
enhanced=args.enhanced, split=args.split,
withoutSr=args.without_sr,
floatGuides=args.float_guides,
switchGuides=args.switch_guides,
creationFlags=int(next(line.split('flags=')[1] for line in text.splitlines()
if line.startswith('RR_NGX_CREATION flags='))),
optionalGuideFrames=optional_frames, optionalMasks=masks,
depthFormat=args.depth_format, stencilPreserved=args.depth_format.startswith('d32s8'),
hardwareDepthFrames=sum(bool(int(line[8]) & 2) for line in frame_lines),
playerConfig=args.player_config,
preservedPlayerIni=args.use_existing_ini,
invertedDepthFrames=sum(bool(int(line[8]) & 1) for line in frame_lines),
snapshotMutation=True, releaseBeforeSubmit=True, parameterDestroyBeforeSubmit=True)
print('PASS NGX RR frames=' + str(len(expected)) + ' standalone_exact=1')
finally:
(args.output / 'summary.json').write_text(json.dumps(report, indent=2) + '\n')
if __name__ == '__main__':
main()