Expand Windows GPU support, add native FSR RR, and improve SR interop

Add a native D3D12 FSR Ray Regeneration route for gfx1200/gfx1201,
using the game's device and queue with embedded HLSL guide preparation
and output composition. Preserve the vkd3d compatibility route for
RDNA3 and expose explicit route overrides and diagnostics.

Improve Windows runtime compatibility:
- Retry rejected application-ID initialization with a project identity.
- Defer expensive RR backend creation until feature creation.
- Recover from transient submission failures without disabling later frames.
- Add shared-buffer export fallbacks through explicit heaps and a private
  device on the same adapter.
- Safely evict old RR descriptor sets when input views change.
- Treat requested RR presets as hints when a configured model is available.
- Expand SR/RR input formats and handle diffuse/specular hit distances,
  packed normals, depth-stencil resources, and unused FSR guides.

Reduce SR overhead by batching output downloads and format conversion
under one completion wait. Optimize strict M enc2/tube attention with
bounded FP32 accumulation and the original FP64 fallback for ambiguous
rounding cases.

Extend RDNA4 tooling with gfx1200/gfx1201 native FP8 RR model builds,
capability checks, architecture-aware deployment, and experimental K/M
kernel paths. Require matching model and runtime arithmetic families.

Add optional embedded FSR shader optimizations keyed by shader hash and
exact original SPIR-V contents. Preserve reflected metadata and record
optimizer provenance, artifact hashes, and cache identity.

Simplify Windows installation with automatic backend dependency downloads,
download progress and ETA, backed-up absolute OptiScaler library paths,
and recovery from interrupted RR model builds.

Add build-kernels.bat and a kernels-only build matrix for all eleven
registered RDNA targets, with code-object target validation and SHA256
manifests. Expand conversion probes, numerical stress tests, temporal
benchmark tooling, installer/deployment tests, and setup documentation.

Document synthetic regression and timing results, experimental build
options, and remaining RDNA4 hardware validation requirements.
This commit is contained in:
ayo committed 2026-10-17 07:40:34 -04:00
1 parent a617c818de
commit ac4ee6c225
67 files changed
+3147 -326

No files matched your search

+777
View File
@@ -0,0 +1,777 @@
// Diagnostic per-dispatch GPU timing of AMD's denoiser (D4R_FSR_RR_DISPATCH_PROFILE=1, off by default).
//
// While ffxDispatch records into the game's list, the list's Dispatch/ExecuteIndirect entries (vkd3d-proton's
// shared command-list vtable) are wrapped to put a timestamp before every dispatch; the device's compute PSO
// creation is wrapped to tag each pipeline with vkd3d's shader hash (FNV-1 64 of the DXBC blob, the name
// VKD3D_SHADER_DUMP_PATH and VKD3D_SHADER_OVERRIDE use). Stamp i..i+1 is the time from the end of the work
// before dispatch i to the end of dispatch i (bottom-of-pipe), which is dispatch i's cost when the denoiser
// separates its passes with barriers. Wrappers forward unchanged outside the recording thread's window.
#pragma once
#include <d3d12.h>
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <map>
#include <mutex>
#include <unordered_map>
#include <vector>
namespace fsr_rr_dp
{
constexpr UINT kSlots = 4;
constexpr UINT kStampsPerSlot = 2048;
using DispatchFn = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList*, UINT, UINT, UINT);
using ExecuteIndirectFn = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList*, ID3D12CommandSignature*, UINT,
ID3D12Resource*, UINT64, ID3D12Resource*, UINT64);
using ResourceBarrierFn = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList*, UINT, const D3D12_RESOURCE_BARRIER*);
using SetRootCbvFn = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList*, UINT, D3D12_GPU_VIRTUAL_ADDRESS);
using SetPipelineStateFn = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList*, ID3D12PipelineState*);
using CreateComputeFn = HRESULT(STDMETHODCALLTYPE*)(ID3D12Device*, const D3D12_COMPUTE_PIPELINE_STATE_DESC*, REFIID,
void**);
using CreateStreamFn = HRESULT(STDMETHODCALLTYPE*)(ID3D12Device*, const D3D12_PIPELINE_STATE_STREAM_DESC*, REFIID,
void**);
struct Record
{
uint64_t hash;
UINT x, y, z;
bool indirect;
uint32_t barriers, uavBarriers; // recorded by the denoiser between the previous dispatch and this one
};
struct Stat
{
uint64_t hash = 0;
UINT x = 0, y = 0, z = 0;
bool indirect = false;
uint32_t barriers = 0, uavBarriers = 0;
double sum = 0;
uint32_t count = 0;
};
struct State
{
std::mutex mutex;
std::unordered_map<const void*, uint64_t> psoHash;
DispatchFn dispatch = nullptr;
ExecuteIndirectFn executeIndirect = nullptr;
SetPipelineStateFn setPipelineState = nullptr;
ResourceBarrierFn resourceBarrier = nullptr;
SetRootCbvFn setRootCbv = nullptr;
uint32_t barriersSinceDispatch = 0, uavBarriersSinceDispatch = 0;
CreateComputeFn createCompute = nullptr;
CreateStreamFn createStream = nullptr;
bool listHooked = false, deviceHooked = false, failed = false;
ID3D12QueryHeap* heap = nullptr;
ID3D12Resource* readback = nullptr;
volatile uint64_t* values = nullptr;
double ticksPerMs = 0;
std::vector<Record> records[kSlots];
UINT used[kSlots] = {};
std::map<UINT, Stat> stats; // by dispatch index within the frame
uint32_t frames = 0;
uint64_t lastHash = 0;
};
inline State& state()
{
static State s;
return s;
}
thread_local ID3D12GraphicsCommandList* t_list = nullptr;
thread_local UINT t_slot = 0;
thread_local uint64_t t_pso = 0;
thread_local D3D12_GPU_VIRTUAL_ADDRESS t_cbv = 0;
inline void snapshot_after_dispatch(ID3D12GraphicsCommandList* list);
inline uint64_t fnv1(const void* data, size_t size)
{
uint64_t h = 0xcbf29ce484222325ull;
const auto* p = static_cast<const uint8_t*>(data);
for (size_t i = 0; i < size; ++i)
h = (h * 0x100000001b3ull) ^ p[i];
return h;
}
inline bool patch(void** slot, void* hook, void** original)
{
if (*slot == hook)
return true;
DWORD old = 0;
if (!VirtualProtect(slot, sizeof(void*), PAGE_READWRITE, &old))
return false;
*original = *slot;
*slot = hook;
VirtualProtect(slot, sizeof(void*), old, &old);
return true;
}
inline void stamp(ID3D12GraphicsCommandList* list, uint64_t hash, UINT x, UINT y, UINT z, bool indirect)
{
State& s = state();
UINT& used = s.used[t_slot];
if (used + 1 >= kStampsPerSlot)
return;
list->EndQuery(s.heap, D3D12_QUERY_TYPE_TIMESTAMP, t_slot * kStampsPerSlot + used);
s.records[t_slot].push_back({hash, x, y, z, indirect, s.barriersSinceDispatch, s.uavBarriersSinceDispatch});
s.barriersSinceDispatch = s.uavBarriersSinceDispatch = 0;
++used;
}
static void STDMETHODCALLTYPE hook_set_pipeline_state(ID3D12GraphicsCommandList* list, ID3D12PipelineState* pso)
{
State& s = state();
if (t_list == list)
{
std::lock_guard<std::mutex> lock(s.mutex);
auto found = s.psoHash.find(pso);
t_pso = found != s.psoHash.end() ? found->second : 0;
}
s.setPipelineState(list, pso);
}
inline bool serialize()
{
static const bool on = env_uint("D4R_FSR_RR_DP_SERIALIZE", 0) != 0;
return on;
}
static void STDMETHODCALLTYPE hook_resource_barrier(ID3D12GraphicsCommandList* list, UINT count,
const D3D12_RESOURCE_BARRIER* barriers)
{
State& s = state();
if (t_list == list)
for (UINT i = 0; i < count; ++i)
{
++s.barriersSinceDispatch;
if (barriers[i].Type == D3D12_RESOURCE_BARRIER_TYPE_UAV)
++s.uavBarriersSinceDispatch;
}
s.resourceBarrier(list, count, barriers);
}
// Diagnostic: a full UAV barrier before every dispatch of the denoiser (tests for missing barriers).
inline void serialize_barrier(ID3D12GraphicsCommandList* list)
{
if (!serialize())
return;
D3D12_RESOURCE_BARRIER uav = {};
uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
state().resourceBarrier(list, 1, &uav);
}
static void STDMETHODCALLTYPE hook_set_root_cbv(ID3D12GraphicsCommandList* list, UINT index,
D3D12_GPU_VIRTUAL_ADDRESS va)
{
if (t_list == list)
t_cbv = va;
state().setRootCbv(list, index, va);
}
static void STDMETHODCALLTYPE hook_dispatch(ID3D12GraphicsCommandList* list, UINT x, UINT y, UINT z)
{
State& s = state();
if (t_list == list)
{
serialize_barrier(list);
stamp(list, t_pso, x, y, z, false);
}
s.dispatch(list, x, y, z);
if (t_list == list)
snapshot_after_dispatch(list);
}
static void STDMETHODCALLTYPE hook_execute_indirect(ID3D12GraphicsCommandList* list, ID3D12CommandSignature* sig,
UINT maxCount, ID3D12Resource* args, UINT64 argsOffset,
ID3D12Resource* count, UINT64 countOffset)
{
State& s = state();
if (t_list == list)
{
serialize_barrier(list);
stamp(list, t_pso, maxCount, 0, 0, true);
}
s.executeIndirect(list, sig, maxCount, args, argsOffset, count, countOffset);
if (t_list == list)
snapshot_after_dispatch(list);
}
static HRESULT STDMETHODCALLTYPE hook_create_compute(ID3D12Device* device, const D3D12_COMPUTE_PIPELINE_STATE_DESC* desc,
REFIID riid, void** pso)
{
State& s = state();
const HRESULT hr = s.createCompute(device, desc, riid, pso);
if (SUCCEEDED(hr) && pso != nullptr && *pso != nullptr && desc != nullptr && desc->CS.pShaderBytecode != nullptr)
{
const uint64_t hash = fnv1(desc->CS.pShaderBytecode, desc->CS.BytecodeLength);
std::lock_guard<std::mutex> lock(s.mutex);
s.psoHash[*pso] = hash;
}
return hr;
}
// The CS bytecode of a pipeline state stream (D3DX12-style subobjects, each aligned to a pointer).
inline const D3D12_SHADER_BYTECODE* stream_cs(const D3D12_PIPELINE_STATE_STREAM_DESC* desc)
{
auto* p = static_cast<const uint8_t*>(desc->pPipelineStateSubobjectStream);
const uint8_t* end = p + desc->SizeInBytes;
while (p + sizeof(UINT) <= end)
{
const UINT type = *reinterpret_cast<const UINT*>(p);
size_t size;
switch (type)
{
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_ROOT_SIGNATURE: size = 16; break;
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_CS:
return reinterpret_cast<const D3D12_SHADER_BYTECODE*>(p + 8);
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VS:
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PS:
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DS:
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_HS:
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_GS:
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_CACHED_PSO: size = 24; break;
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_NODE_MASK:
case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_FLAGS: size = 8; break;
default: return nullptr;
}
p += size;
}
return nullptr;
}
static HRESULT STDMETHODCALLTYPE hook_create_stream(ID3D12Device* device, const D3D12_PIPELINE_STATE_STREAM_DESC* desc,
REFIID riid, void** pso)
{
State& s = state();
const HRESULT hr = s.createStream(device, desc, riid, pso);
const D3D12_SHADER_BYTECODE* cs = desc != nullptr ? stream_cs(desc) : nullptr;
if (SUCCEEDED(hr) && pso != nullptr && *pso != nullptr && cs != nullptr && cs->pShaderBytecode != nullptr)
{
const uint64_t hash = fnv1(cs->pShaderBytecode, cs->BytecodeLength);
std::lock_guard<std::mutex> lock(s.mutex);
s.psoHash[*pso] = hash;
}
return hr;
}
// D4R_FSR_RR_DP_SNAPSHOT=k1,k2,...: on frame D4R_FSR_RR_DP_SNAPSHOT_FRAME (default 1), copy every UAV buffer the
// denoiser created after its dispatch k (0-based), to <D4R_FSR_RR_DUMP_DIR>/snap_<k>_<buffer>.raw at teardown.
using CreateCommittedFn = HRESULT(STDMETHODCALLTYPE*)(ID3D12Device*, const D3D12_HEAP_PROPERTIES*, D3D12_HEAP_FLAGS,
const D3D12_RESOURCE_DESC*, D3D12_RESOURCE_STATES,
const D3D12_CLEAR_VALUE*, REFIID, void**);
using CreatePlacedFn = HRESULT(STDMETHODCALLTYPE*)(ID3D12Device*, ID3D12Heap*, UINT64, const D3D12_RESOURCE_DESC*,
D3D12_RESOURCE_STATES, const D3D12_CLEAR_VALUE*, REFIID, void**);
using CreateCommitted1Fn = HRESULT(STDMETHODCALLTYPE*)(ID3D12Device*, const D3D12_HEAP_PROPERTIES*, D3D12_HEAP_FLAGS,
const D3D12_RESOURCE_DESC*, D3D12_RESOURCE_STATES,
const D3D12_CLEAR_VALUE*, ID3D12ProtectedResourceSession*,
REFIID, void**);
struct Created
{
ID3D12Resource* resource;
D3D12_RESOURCE_DESC desc;
const char* how;
};
inline std::vector<Created>& created()
{
static std::vector<Created> c;
return c;
}
thread_local bool t_creating = false;
inline CreateCommittedFn& orig_committed() { static CreateCommittedFn f = nullptr; return f; }
inline CreatePlacedFn& orig_placed() { static CreatePlacedFn f = nullptr; return f; }
inline CreateCommitted1Fn& orig_committed1() { static CreateCommitted1Fn f = nullptr; return f; }
inline void note_created(HRESULT hr, const D3D12_RESOURCE_DESC* desc, void** out, const char* how)
{
if (!t_creating || FAILED(hr) || out == nullptr || *out == nullptr || desc == nullptr)
return;
// Only ID3D12Resource-compatible pointers are recorded; every resource interface starts with ID3D12Resource.
created().push_back({static_cast<ID3D12Resource*>(*out), *desc, how});
logf("D4R_FSR_RR_DP resource %zu (%s): dim %d %llux%ux%u format %d flags 0x%x", created().size() - 1, how,
desc->Dimension, static_cast<unsigned long long>(desc->Width), desc->Height, desc->DepthOrArraySize,
desc->Format, desc->Flags);
}
static HRESULT STDMETHODCALLTYPE hook_committed(ID3D12Device* d, const D3D12_HEAP_PROPERTIES* hp, D3D12_HEAP_FLAGS hf,
const D3D12_RESOURCE_DESC* desc, D3D12_RESOURCE_STATES st,
const D3D12_CLEAR_VALUE* cv, REFIID riid, void** out)
{
const HRESULT hr = orig_committed()(d, hp, hf, desc, st, cv, riid, out);
note_created(hr, desc, out, "committed");
return hr;
}
static HRESULT STDMETHODCALLTYPE hook_placed(ID3D12Device* d, ID3D12Heap* heap, UINT64 offset,
const D3D12_RESOURCE_DESC* desc, D3D12_RESOURCE_STATES st,
const D3D12_CLEAR_VALUE* cv, REFIID riid, void** out)
{
const HRESULT hr = orig_placed()(d, heap, offset, desc, st, cv, riid, out);
note_created(hr, desc, out, "placed");
return hr;
}
static HRESULT STDMETHODCALLTYPE hook_committed1(ID3D12Device* d, const D3D12_HEAP_PROPERTIES* hp, D3D12_HEAP_FLAGS hf,
const D3D12_RESOURCE_DESC* desc, D3D12_RESOURCE_STATES st,
const D3D12_CLEAR_VALUE* cv, ID3D12ProtectedResourceSession* ps,
REFIID riid, void** out)
{
const HRESULT hr = orig_committed1()(d, hp, hf, desc, st, cv, ps, riid, out);
note_created(hr, desc, out, "committed1");
return hr;
}
struct Snapshot
{
UINT dispatch;
std::vector<std::pair<size_t, ID3D12Resource*>> copies; // created() index, readback buffer
};
inline std::vector<Snapshot>& snapshots()
{
static std::vector<Snapshot> s;
return s;
}
inline const std::vector<UINT>& snapshot_points()
{
static const std::vector<UINT> points = [] {
std::vector<UINT> p;
const std::string list = env_string("D4R_FSR_RR_DP_SNAPSHOT");
size_t at = 0;
while (at < list.size())
{
const size_t comma = list.find(',', at);
p.push_back(static_cast<UINT>(std::stoul(list.substr(at, comma - at))));
at = comma == std::string::npos ? list.size() : comma + 1;
}
return p;
}();
return points;
}
thread_local uint32_t t_frame = 0;
thread_local UINT t_dispatchIndex = 0;
thread_local ID3D12Device* t_device = nullptr;
inline bool enabled()
{
static const bool on = env_uint("D4R_FSR_RR_DISPATCH_PROFILE", 0) != 0;
return on;
}
// Before the denoiser's context is created: tag the PSOs it builds.
inline void hook_device(ID3D12Device* device)
{
State& s = state();
if (!enabled() || s.deviceHooked)
return;
void** vtable = *reinterpret_cast<void***>(device);
ID3D12Device2* device2 = nullptr;
bool ok = patch(&vtable[11], reinterpret_cast<void*>(&hook_create_compute), reinterpret_cast<void**>(&s.createCompute));
if (SUCCEEDED(device->QueryInterface(__uuidof(ID3D12Device2), reinterpret_cast<void**>(&device2))))
{
void** vtable2 = *reinterpret_cast<void***>(device2);
ok = ok && patch(&vtable2[47], reinterpret_cast<void*>(&hook_create_stream), reinterpret_cast<void**>(&s.createStream));
device2->Release();
}
if (!snapshot_points().empty())
{
ID3D12Device4* device4 = nullptr;
ok = ok && patch(&vtable[27], reinterpret_cast<void*>(&hook_committed), reinterpret_cast<void**>(&orig_committed())) &&
patch(&vtable[29], reinterpret_cast<void*>(&hook_placed), reinterpret_cast<void**>(&orig_placed()));
if (SUCCEEDED(device->QueryInterface(__uuidof(ID3D12Device4), reinterpret_cast<void**>(&device4))))
{
void** vtable4 = *reinterpret_cast<void***>(device4);
ok = ok && patch(&vtable4[53], reinterpret_cast<void*>(&hook_committed1),
reinterpret_cast<void**>(&orig_committed1()));
device4->Release();
}
}
s.deviceHooked = ok;
logf("D4R_FSR_RR_DISPATCH_PROFILE: PSO creation hooks %s", ok ? "installed" : "FAILED");
}
inline bool ensure(ID3D12Device* device, ID3D12GraphicsCommandList* list)
{
State& s = state();
if (s.failed)
return false;
if (!s.listHooked)
{
void** vtable = *reinterpret_cast<void***>(list);
const bool ok =
patch(&vtable[14], reinterpret_cast<void*>(&hook_dispatch), reinterpret_cast<void**>(&s.dispatch)) &&
patch(&vtable[25], reinterpret_cast<void*>(&hook_set_pipeline_state),
reinterpret_cast<void**>(&s.setPipelineState)) &&
patch(&vtable[59], reinterpret_cast<void*>(&hook_execute_indirect),
reinterpret_cast<void**>(&s.executeIndirect)) &&
patch(&vtable[26], reinterpret_cast<void*>(&hook_resource_barrier),
reinterpret_cast<void**>(&s.resourceBarrier)) &&
patch(&vtable[37], reinterpret_cast<void*>(&hook_set_root_cbv), reinterpret_cast<void**>(&s.setRootCbv));
if (!ok)
{
s.failed = true;
logf("D4R_FSR_RR_DISPATCH_PROFILE: command-list hooks FAILED");
return false;
}
s.listHooked = true;
}
if (s.heap == nullptr)
{
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
ID3D12CommandQueue* queue = nullptr;
UINT64 frequency = 0;
if (SUCCEEDED(device->CreateCommandQueue(&queueDesc, __uuidof(ID3D12CommandQueue), reinterpret_cast<void**>(&queue))))
{
queue->GetTimestampFrequency(&frequency);
queue->Release();
}
D3D12_QUERY_HEAP_DESC heapDesc = {};
heapDesc.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP;
heapDesc.Count = kSlots * kStampsPerSlot;
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = D3D12_HEAP_TYPE_READBACK;
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Width = 8ull * kSlots * kStampsPerSlot;
desc.Height = 1;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
void* mapped = nullptr;
if (frequency == 0 ||
FAILED(device->CreateQueryHeap(&heapDesc, __uuidof(ID3D12QueryHeap), reinterpret_cast<void**>(&s.heap))) ||
FAILED(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COPY_DEST,
nullptr, __uuidof(ID3D12Resource),
reinterpret_cast<void**>(&s.readback))) ||
FAILED(s.readback->Map(0, nullptr, &mapped)))
{
s.failed = true;
logf("D4R_FSR_RR_DISPATCH_PROFILE: query heap FAILED");
return false;
}
s.values = static_cast<volatile uint64_t*>(mapped);
for (UINT i = 0; i < kSlots * kStampsPerSlot; ++i)
s.values[i] = 0;
s.ticksPerMs = static_cast<double>(frequency) / 1000.0;
}
return true;
}
inline void log_stats()
{
State& s = state();
double total = 0;
for (const auto& [index, stat] : s.stats)
total += stat.sum / std::max(1u, stat.count);
logf("D4R_FSR_RR_DISPATCH_PROFILE: %u frames, %zu dispatches/frame, %.3f ms GPU/frame (mean)", s.frames,
s.stats.size(), total);
for (const auto& [index, stat] : s.stats)
logf("D4R_FSR_RR_DISPATCH %4u %016llx %s %5u %5u %5u %.4f ms (n=%u) barriers %u uav %u", index,
static_cast<unsigned long long>(stat.hash), stat.indirect ? "ind" : "dir", stat.x, stat.y, stat.z,
stat.sum / std::max(1u, stat.count), stat.count, stat.barriers, stat.uavBarriers);
std::map<uint64_t, std::pair<double, uint32_t>> byHash;
for (const auto& [index, stat] : s.stats)
{
auto& entry = byHash[stat.hash];
entry.first += stat.sum / std::max(1u, stat.count);
++entry.second;
}
std::vector<std::pair<double, uint64_t>> ranked;
for (const auto& [hash, entry] : byHash)
ranked.push_back({entry.first, hash});
std::sort(ranked.rbegin(), ranked.rend());
for (const auto& [ms, hash] : ranked)
logf("D4R_FSR_RR_SHADER %016llx %.4f ms/frame over %u dispatches (%.1f%%)", static_cast<unsigned long long>(hash),
ms, byHash[hash].second, total > 0 ? 100.0 * ms / total : 0.0);
}
// Around the denoiser's ffxDispatch. frame selects a slot; the slot recorded kSlots frames ago has retired.
inline void begin(ID3D12Device* device, ID3D12GraphicsCommandList* list, uint32_t frame)
{
if (!enabled() || !ensure(device, list))
return;
State& s = state();
const UINT slot = frame % kSlots;
volatile uint64_t* values = s.values + slot * kStampsPerSlot;
const UINT used = s.used[slot];
if (used > 1 && values[0] != 0 && values[used - 1] >= values[0])
{
// Frames 1-5 carry the cold start; the stats keep warm frames only.
if (frame > 5 + kSlots)
{
for (UINT i = 0; i + 1 < used; ++i)
{
Stat& stat = s.stats[i];
const Record& r = s.records[slot][i];
stat.hash = r.hash, stat.x = r.x, stat.y = r.y, stat.z = r.z, stat.indirect = r.indirect;
stat.barriers = r.barriers, stat.uavBarriers = r.uavBarriers;
stat.sum += static_cast<double>(values[i + 1] - values[i]) / s.ticksPerMs;
++stat.count;
}
if (++s.frames % profile_log_every() == 0)
log_stats();
}
}
for (UINT i = 0; i < kStampsPerSlot; ++i)
values[i] = 0;
s.records[slot].clear();
s.used[slot] = 0;
t_slot = slot;
t_pso = 0;
t_list = list;
t_frame = frame;
t_dispatchIndex = 0;
t_device = device;
s.barriersSinceDispatch = s.uavBarriersSinceDispatch = 0;
stamp(list, 0, 0, 0, 0, false); // the start
}
inline void end(ID3D12GraphicsCommandList* list)
{
State& s = state();
if (t_list != list)
return;
t_list = nullptr;
stamp(list, 0, 0, 0, 0, false); // after the last dispatch
list->ResolveQueryData(s.heap, D3D12_QUERY_TYPE_TIMESTAMP, t_slot * kStampsPerSlot, s.used[t_slot], s.readback,
8ull * t_slot * kStampsPerSlot);
}
// D4R_FSR_RR_DUMP_DIR=<Windows dir>: also copy the denoiser's two outputs of every frame (RGBA16F, rows packed)
// to <dir>/denoised_<frame>_<diffuse|specular>.raw, for checking replacement shaders against the original.
// The copies are written once the frame has retired (4 frames later, or at teardown).
struct Dump
{
uint32_t frame = 0;
ID3D12Resource* buffer = nullptr;
UINT count = 0;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint[8] = {};
UINT rows[8] = {};
UINT64 rowBytes[8] = {};
const char* names[8] = {};
bool pending = false;
};
inline std::vector<Dump>& dumps(int set)
{
static std::vector<Dump> d[2] = {std::vector<Dump>(8), std::vector<Dump>(8)};
return d[set];
}
inline void dump_write(Dump& d)
{
if (!d.pending)
return;
d.pending = false;
static const std::string dir = env_string("D4R_FSR_RR_DUMP_DIR");
void* mapped = nullptr;
if (FAILED(d.buffer->Map(0, nullptr, &mapped)))
return;
for (UINT i = 0; i < d.count; ++i)
{
char name[512];
std::snprintf(name, sizeof(name), "%s\\%s_%03u.raw", dir.c_str(), d.names[i], d.frame);
FILE* f = std::fopen(name, "wb");
if (f == nullptr)
continue;
const auto* base = static_cast<const uint8_t*>(mapped) + d.footprint[i].Offset;
for (UINT row = 0; row < d.rows[i]; ++row)
std::fwrite(base + static_cast<size_t>(row) * d.footprint[i].Footprint.RowPitch, 1, d.rowBytes[i], f);
std::fclose(f);
}
d.buffer->Unmap(0, nullptr);
}
// set 0/1: independent rings (e.g. the denoiser's inputs and its outputs). Textures are in `state` and return to it.
inline void dump_record(int set, ID3D12Device* device, ID3D12GraphicsCommandList* list, uint32_t frame, UINT count,
ID3D12Resource* const* textures, const char* const* names, const D3D12_RESOURCE_STATES state)
{
static const bool on = !env_string("D4R_FSR_RR_DUMP_DIR").empty();
if (!on || count > 8)
return;
Dump& d = dumps(set)[frame % dumps(set).size()];
dump_write(d); // the frame that used this slot has retired
if (d.buffer == nullptr)
{
UINT64 offset = 0;
for (UINT i = 0; i < count; ++i)
{
D3D12_RESOURCE_DESC desc = textures[i]->GetDesc();
UINT64 total = 0;
device->GetCopyableFootprints(&desc, 0, 1, offset, &d.footprint[i], &d.rows[i], &d.rowBytes[i], &total);
offset = (offset + total + 65535) & ~65535ull;
}
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = D3D12_HEAP_TYPE_READBACK;
D3D12_RESOURCE_DESC buffer = {};
buffer.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
buffer.Width = offset;
buffer.Height = 1;
buffer.DepthOrArraySize = 1;
buffer.MipLevels = 1;
buffer.SampleDesc.Count = 1;
buffer.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
if (FAILED(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &buffer, D3D12_RESOURCE_STATE_COPY_DEST,
nullptr, __uuidof(ID3D12Resource), reinterpret_cast<void**>(&d.buffer))))
{
d.buffer = nullptr;
return;
}
}
d.count = count;
D3D12_RESOURCE_BARRIER barriers[8] = {};
for (UINT i = 0; i < count; ++i)
{
d.names[i] = names[i];
barriers[i].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barriers[i].Transition.pResource = textures[i];
barriers[i].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barriers[i].Transition.StateBefore = state;
barriers[i].Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
}
list->ResourceBarrier(count, barriers);
for (UINT i = 0; i < count; ++i)
{
D3D12_TEXTURE_COPY_LOCATION dst = {}, src = {};
dst.pResource = d.buffer;
dst.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
dst.PlacedFootprint = d.footprint[i];
src.pResource = textures[i];
src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
}
for (UINT i = 0; i < count; ++i)
std::swap(barriers[i].Transition.StateBefore, barriers[i].Transition.StateAfter);
list->ResourceBarrier(count, barriers);
d.frame = frame;
d.pending = true;
}
inline void snapshot_after_dispatch(ID3D12GraphicsCommandList* list)
{
const UINT index = ++t_dispatchIndex; // 1-based: matches the D4R_FSR_RR_DISPATCH table (row 0 = start stamp)
static const uint32_t frame = env_uint("D4R_FSR_RR_DP_SNAPSHOT_FRAME", 1);
const auto& points = snapshot_points();
if (t_frame != frame || std::find(points.begin(), points.end(), index) == points.end())
return;
D3D12_RESOURCE_BARRIER uav = {};
uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
state().resourceBarrier(list, 1, &uav);
Snapshot snap{index, {}};
// the dispatch's root CBV, read through the CPU mapping of whichever recorded buffer holds it
for (size_t i = 0; i < created().size() && t_cbv != 0; ++i)
{
const Created& c = created()[i];
if (c.desc.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER)
continue;
const D3D12_GPU_VIRTUAL_ADDRESS va = c.resource->GetGPUVirtualAddress();
if (t_cbv < va || t_cbv + 1376 > va + c.desc.Width)
continue;
void* mapped = nullptr;
D3D12_RANGE range = {static_cast<SIZE_T>(t_cbv - va), static_cast<SIZE_T>(t_cbv - va + 1376)};
if (SUCCEEDED(c.resource->Map(0, &range, &mapped)))
{
static const std::string dir = env_string("D4R_FSR_RR_DUMP_DIR");
char name[512];
std::snprintf(name, sizeof(name), "%s\\snap_%03u_cbv.raw", dir.c_str(), index);
if (FILE* f = std::fopen(name, "wb"))
{
std::fwrite(static_cast<const uint8_t*>(mapped) + (t_cbv - va), 1, 1376, f);
std::fclose(f);
}
D3D12_RANGE none = {0, 0};
c.resource->Unmap(0, &none);
logf("D4R_FSR_RR_DP_SNAPSHOT: dispatch %u CBV in resource %zu at +%llu", index, i,
static_cast<unsigned long long>(t_cbv - va));
}
break;
}
// CPU-visible buffers (upload copies, e.g. the weights): read through their mapping
for (size_t i = 0; i < created().size(); ++i)
{
const Created& c = created()[i];
D3D12_HEAP_PROPERTIES props = {};
D3D12_HEAP_FLAGS flags = {};
if (c.desc.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER || FAILED(c.resource->GetHeapProperties(&props, &flags)) ||
props.Type != D3D12_HEAP_TYPE_UPLOAD)
continue;
void* mapped = nullptr;
if (SUCCEEDED(c.resource->Map(0, nullptr, &mapped)))
{
static const std::string dir = env_string("D4R_FSR_RR_DUMP_DIR");
char name[512];
std::snprintf(name, sizeof(name), "%s\\snap_%03u_upload%02zu.raw", dir.c_str(), index, i);
if (FILE* f = std::fopen(name, "wb"))
{
std::fwrite(mapped, 1, c.desc.Width, f);
std::fclose(f);
}
D3D12_RANGE none = {0, 0};
c.resource->Unmap(0, &none);
}
}
if (t_cbv != 0)
logf("D4R_FSR_RR_DP_SNAPSHOT: dispatch %u root CBV va 0x%llx", index, static_cast<unsigned long long>(t_cbv));
for (size_t i = 0; i < created().size(); ++i)
{
const Created& c = created()[i];
if (c.desc.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER ||
(c.desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS) == 0)
continue;
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = D3D12_HEAP_TYPE_READBACK;
D3D12_RESOURCE_DESC buffer = c.desc;
buffer.Flags = D3D12_RESOURCE_FLAG_NONE;
ID3D12Resource* readback = nullptr;
if (FAILED(t_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &buffer, D3D12_RESOURCE_STATE_COPY_DEST,
nullptr, __uuidof(ID3D12Resource), reinterpret_cast<void**>(&readback))))
continue;
D3D12_RESOURCE_BARRIER t = {};
t.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
t.Transition.pResource = c.resource;
t.Transition.StateBefore = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
t.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
state().resourceBarrier(list, 1, &t);
list->CopyBufferRegion(readback, 0, c.resource, 0, c.desc.Width);
std::swap(t.Transition.StateBefore, t.Transition.StateAfter);
state().resourceBarrier(list, 1, &t);
snap.copies.push_back({i, readback});
}
snapshots().push_back(std::move(snap));
}
inline void snapshot_flush()
{
static const std::string dir = env_string("D4R_FSR_RR_DUMP_DIR");
for (Snapshot& snap : snapshots())
{
for (auto& [index, readback] : snap.copies)
{
void* mapped = nullptr;
if (SUCCEEDED(readback->Map(0, nullptr, &mapped)))
{
char name[512];
std::snprintf(name, sizeof(name), "%s\\snap_%03u_res%02zu.raw", dir.c_str(), snap.dispatch, index);
if (FILE* f = std::fopen(name, "wb"))
{
std::fwrite(mapped, 1, created()[index].desc.Width, f);
std::fclose(f);
}
readback->Unmap(0, nullptr);
}
readback->Release();
}
logf("D4R_FSR_RR_DP_SNAPSHOT: dispatch %u written (%zu buffers)", snap.dispatch, snap.copies.size());
}
snapshots().clear();
}
inline void dump_flush()
{
snapshot_flush();
for (int set = 0; set < 2; ++set)
for (Dump& d : dumps(set))
dump_write(d);
}
} // namespace fsr_rr_dp
+24 -1
View File
@@ -37,6 +37,24 @@
#include "windows/fsr_rr_identity.h"
#include "fsr_rr_scene.h"
// Per-dispatch GPU timing of AMD's denoiser (D4R_FSR_RR_DISPATCH_PROFILE=1), shared with the Linux shim.
inline void logf(const char* format, ...)
{
va_list args;
va_start(args, format);
std::vfprintf(stderr, format, args);
va_end(args);
std::fputc('\n', stderr);
}
inline std::string env_string(const char* name) { const char* v = std::getenv(name); return v ? v : ""; }
inline uint32_t env_uint(const char* name, uint32_t fallback)
{
const char* v = std::getenv(name);
return v && *v ? static_cast<uint32_t>(std::strtoul(v, nullptr, 0)) : fallback;
}
inline uint32_t profile_log_every() { return env_uint("D4R_PROFILE_LOG_EVERY", 1000000); }
#include "d4r_fsr_rr_dispatch_profile.h"
namespace
{
using namespace fsr_scene;
@@ -425,6 +443,7 @@ int wmain(int argc, wchar_t** argv)
// 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();
fsr_rr_dp::hook_device(g_device);
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();
@@ -574,7 +593,9 @@ int wmain(int argc, wchar_t** argv)
}
list->EndQuery(timestamps, D3D12_QUERY_TYPE_TIMESTAMP, 0);
const auto cpuStart = std::chrono::steady_clock::now();
fsr_rr_dp::begin(g_device, list, f);
const ffxReturnCode_t dispatched = runtime.dispatch(&context, &desc.header);
fsr_rr_dp::end(list);
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);
@@ -666,7 +687,9 @@ int wmain(int argc, wchar_t** argv)
}
previousCamera = camera;
}
json_field("frame_log", frameLog + "\n ]");
if (fsr_rr_dp::enabled())
fsr_rr_dp::log_stats();
json_field("frame_log", frameLog +"\n ]");
if (dispatchOk)
{
std::vector<double> warm(gpuMs.begin() + std::min<size_t>(gpuMs.size(), 3), gpuMs.end());
+7 -4
View File
@@ -25,6 +25,7 @@ def main():
for name in ('runner', 'model', 'case', 'output'):
parser.add_argument('--' + name, required=True, type=lambda p: Path(p).resolve())
parser.add_argument('--candidate', action='append', default=[], metavar='LABEL=OBJECT_DIRECTORY')
parser.add_argument('--candidate-runner', action='append', default=[], metavar='LABEL=EXECUTABLE')
parser.add_argument('--repeats', type=int, default=25)
args = parser.parse_args()
if not args.output.is_relative_to((repo / 'build').resolve()):
@@ -43,14 +44,16 @@ def main():
'runnerSha256': hashlib.sha256(args.runner.read_bytes()).hexdigest(),
'caseSha256': hashlib.sha256(args.case.read_bytes()).hexdigest(), 'runs': []}
reference = None
variants = [('baseline', None)] + [tuple(s.split('=', 1)) for s in args.candidate]
for label, source in variants:
variants = [('baseline', None, args.runner)]
variants += [(label, source, args.runner) for label, source in (s.split('=', 1) for s in args.candidate)]
variants += [(label, None, Path(runner).resolve()) for label, runner in (s.split('=', 1) for s in args.candidate_runner)]
for label, source, runner in variants:
if not re.fullmatch(r'[a-zA-Z0-9_-]+', label):
parser.error('candidate label must be letters, digits, underscores or hyphens')
output = args.output / label
output.mkdir()
model = args.model
row = {'label': label, 'objects': {}}
row = {'label': label, 'objects': {}, 'runnerSha256': hashlib.sha256(runner.read_bytes()).hexdigest()}
if source:
model = output / 'model'
shutil.copytree(args.model, model)
@@ -68,7 +71,7 @@ def main():
manifest['sha256'][name] = digest
manifest['experimentalOverlay'] = row['objects']
(model / 'model.json').write_text(json.dumps(manifest, indent=2) + '\n')
command = [str(args.runner), str(model), str(args.case), str(output / 'frames'), str(args.repeats), 'hip']
command = [str(runner), str(model), str(args.case), str(output / 'frames'), str(args.repeats), 'hip']
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=240)
row['exitCode'] = result.returncode
+40 -15
View File
@@ -1,4 +1,4 @@
"""Compare K input batching using identical synthetic frames and alternating runs.
"""Compare K/M interop and native candidates on identical alternating synthetic 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.
@@ -19,8 +19,13 @@ def main():
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('--model', choices=('K', 'M'), default='K')
parser.add_argument('--pixel-profile', choices=('baseline', 'packed', 'unorm', 'depth-stencil'), default='baseline')
parser.add_argument('--candidate-shim', type=lambda s: Path(s).resolve(), help='compare a rebuilt shim with the same native kernels')
parser.add_argument('--candidate-batch-inputs', action='store_true', help='also batch input copies in candidate shim/native runs')
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('--timeout', type=int, default=300, help='per-process limit, including cold shader compilation')
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(),
@@ -41,8 +46,12 @@ def main():
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'))
preset = 11 if args.model == 'K' else 13
comparing = bool(args.candidate_native or args.candidate_shim)
if args.candidate_batch_inputs and not comparing:
parser.error('--candidate-batch-inputs requires a candidate shim or native directory')
if not args.input_resolution:
args.input_resolution = f'{ow * 2 // 3}x{oh * 2 // 3}'
args.input_resolution = f'{ow * 2 // 3}x{oh * 2 // 3}' if args.model == 'K' else f'{ow // 2}x{oh // 2}'
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'
@@ -53,7 +62,8 @@ def main():
'inputResolution': args.input_resolution,
'stageProfiling': args.profile_stages,
'kernelProfiling': args.profile_kernels,
'comparison': 'native kernels' if args.candidate_native else 'input batching',
'model': args.model, 'pixelProfile': args.pixel_profile,
'comparison': 'shim' if args.candidate_shim else 'native kernels' if args.candidate_native else 'input batching',
'timing': 'CPU interop including NGX and synchronization', 'runs': []}
runtime = args.runtime
native = runtime / 'native'
@@ -64,6 +74,7 @@ def main():
report['playerConfig'] = args.player_config
report['sequence'] = args.sequence
report['resetEveryFrame'] = args.reset_every_frame
report['candidateBatchInputs'] = args.candidate_batch_inputs
module = args.shim
if args.player_config:
runtime = args.output / 'player'
@@ -79,6 +90,8 @@ def main():
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)}
if args.candidate_shim:
report['candidateShimSha256'] = hashlib.sha256(args.candidate_shim.read_bytes()).hexdigest()
report['nativeArtifacts'] = {p.name: hashlib.sha256(p.read_bytes()).hexdigest()
for p in sorted(native.glob('*')) if p.is_file()}
if args.candidate_native:
@@ -108,26 +121,34 @@ def main():
reference = None
variants = {'abba': (False, True, True, False), 'controls': (False,) * 4, 'batches': (True,) * 4}
for index, batched in enumerate(variants[args.sequence]):
label = ('native-candidate' if args.candidate_native else 'batch') if batched else 'control'
use_batch = batched and (not comparing or args.candidate_batch_inputs)
label = ('candidate' if comparing 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:
if use_batch:
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)
if args.player_config:
config = (Path(__file__).resolve().parents[2] / 'packaging/windows/d4r.ini').read_text()
config = re.sub(r'(?m)^Model\s*=\s*K\s*$', 'Model = ' + args.model, config)
config = re.sub(r'(?m)^BatchInputCopies\s*=\s*(?:true|false)\s*$',
'BatchInputCopies = ' + str(batched and not args.candidate_native).lower(), config)
'BatchInputCopies = ' + str(use_batch).lower(), config)
selected_native = args.candidate_native if batched and args.candidate_native else native
config = config.replace('NativeDir = auto', 'NativeDir = ' + str(selected_native))
(runtime / 'd4r.ini').write_text(config)
command = [str(args.probe), '--module', str(module), '--hip-root', str(runtime / 'hip'),
if args.candidate_shim:
shutil.copy2(args.candidate_shim if batched else args.shim, module)
selected_module = module if args.player_config else args.candidate_shim if batched and args.candidate_shim else module
command = [str(args.probe), '--module', str(selected_module), '--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-abi', 'project', '--interop-mode', 'command-list', '--preset', str(preset),
'--ngx-quality', '2' if args.model == 'K' else '0',
'--ngx-create-flags', '0' if args.model == 'K' else '11',
'--pixel-profile', args.pixel_profile,
'--ngx-output-resolution', args.resolution,
'--ngx-input-resolution', args.input_resolution, '--iterations', str(args.frames)]
if args.player_config:
@@ -135,7 +156,7 @@ def main():
if args.reset_every_frame:
command += ['--reset-every-frame', '1']
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)
result = subprocess.run(command, env=env, stdout=out, stderr=err, timeout=args.timeout)
if args.player_config and (runtime / 'd4r_nvngx.log').exists():
shutil.copy2(runtime / 'd4r_nvngx.log', output / 'player.log')
text = (output / 'stdout.log').read_text(errors='replace')
@@ -143,17 +164,21 @@ def main():
raise RuntimeError(f'run {index} failed ({result.returncode}); inspect {output}')
if args.reset_every_frame and 'D3D12_RESET every_frame=1' not in text:
raise RuntimeError('Requested reset diagnostic was not applied')
if 'D3D12_PIXEL_PROFILE ' + args.pixel_profile not in text:
raise RuntimeError('Requested pixel formats were not applied')
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:
if expected_size not in text or f'D4R_INTEROP async=0 batch_inputs={int(use_batch)}' 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:
if len(re.findall(rf'D3D12_OUTPUT preset={preset} 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:
hashes = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in output.glob('output-*')
if p.suffix in ('.rgba16f', '.r11g11b10f', '.bgra8')}
expected_products = args.frames * (2 if args.pixel_profile in ('packed', 'unorm') else 1)
if len(hashes) != expected_products:
raise RuntimeError(f'run {index}: incomplete output readback')
if reference is None:
reference = hashes
@@ -162,7 +187,7 @@ def main():
report['differentFrames'] = [name for name, digest in hashes.items() if reference.get(name) != digest]
report['mismatchedOutputHashes'] = hashes
raise RuntimeError(f'run {index}: output differs from control')
report['runs'].append({'batch': batched and not bool(args.candidate_native),
report['runs'].append({'batch': use_batch,
'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)
@@ -170,7 +195,7 @@ def main():
values = [t for row in report['runs'] if row['candidate'] == mode for t in row['allFrameMs'][4:]]
if not values:
continue
key = ('candidateMedianMs' if args.candidate_native else 'batchMedianMs') if mode else 'controlMedianMs'
key = ('candidateMedianMs' if comparing else 'batchMedianMs') if mode else 'controlMedianMs'
report[key] = statistics.median(values)
report['passed'] = True
except Exception as error:
+107 -3
View File
@@ -16,6 +16,7 @@ import hashlib
import json
import os
import shutil
import struct
import subprocess
import sys
from pathlib import Path
@@ -44,11 +45,98 @@ def git(args, cwd, log, identity=False):
run(prefix + list(args), cwd=cwd, log=log)
def generate_shader_overrides(source, output, shader_dump, optimizer, hashes, log, variants=()):
"""Embed local, measured optimizations only when the original translation matches exactly."""
if not hashes:
(source / 'libs/vkd3d-shader/d4r_shader_overrides.h').write_text(
'/* Generated by tools/windows/build_fsr_payload.py. No local overrides selected. */\n'
'static bool d4r_shader_replace_builtin(vkd3d_shader_hash_t hash, const void *original,\n'
' size_t original_size, const void **data, size_t *size)\n{\n'
' (void)hash; (void)original; (void)original_size; (void)data; (void)size;\n'
' return false;\n}\n')
return {}
if shader_dump is None or optimizer is None:
raise ValueError('--optimize-shader requires --shader-dump and --spirv-opt')
records = {}
arrays = []
entries = []
inputs = output / 'shader-sources'
inputs.mkdir()
for shader_hash, variant in ((h, v) for h in sorted(set(hashes)) for v in range(1 + len(variants))):
if len(shader_hash) != 16 or any(c not in '0123456789abcdef' for c in shader_hash):
raise ValueError('shader hash must contain 16 lowercase hexadecimal digits')
original = ([shader_dump] + list(variants))[variant] / (shader_hash + '.spv')
variant_inputs = inputs if variant == 0 else inputs / ('variant-' + str(variant))
variant_inputs.mkdir(exist_ok=True)
symbol = shader_hash if variant == 0 else shader_hash + '_' + str(variant)
optimized = variant_inputs / (shader_hash + '.optimized.spv')
run([optimizer, '--target-env=vulkan1.3', '-O', original, '-o', optimized], log=log)
validator = optimizer.with_name('spirv-val.exe')
run([validator, '--target-env', 'vulkan1.3', optimized], log=log)
shutil.copy2(original, variant_inputs / original.name)
for kind, path in (('original', original), ('optimized', optimized)):
data = path.read_bytes()
if len(data) % 4 or data[:4] != b'\x03\x02\x23\x07':
raise ValueError('invalid SPIR-V: ' + str(path))
words = struct.unpack('<%dI' % (len(data) // 4), data)
rows = [', '.join('0x%08xu' % word for word in words[i:i + 8]) for i in range(0, len(words), 8)]
arrays.append('static const uint32_t d4r_%s_%s[] = {\n%s\n};\n' %
(kind, symbol, ',\n'.join(rows)))
entries.append(' {0x%sULL, d4r_original_%s, sizeof(d4r_original_%s), '
'd4r_optimized_%s, sizeof(d4r_optimized_%s)},' % (shader_hash, symbol, symbol, symbol, symbol))
records[symbol] = {'originalSha256': sha(original), 'optimizedSha256': sha(optimized)}
header = '''/* Local shader optimizations. Generated by tools/windows/build_fsr_payload.py. */
''' + ''.join(arrays) + '''
static const struct d4r_shader_override {
vkd3d_shader_hash_t hash;
const void *original;
size_t original_size;
const void *optimized;
size_t optimized_size;
} d4r_shader_overrides[] = {
''' + '\n'.join(entries) + '''
};
static bool d4r_shader_replace_builtin(vkd3d_shader_hash_t hash, const void *original,
size_t original_size, const void **data, size_t *size)
{
char disabled[2];
unsigned int i;
if (vkd3d_get_env_var("D4R_FSR_DISABLE_BUILTIN_SHADERS", disabled, sizeof(disabled)))
return false;
for (i = 0; i < ARRAY_SIZE(d4r_shader_overrides); ++i)
{
const struct d4r_shader_override *entry = &d4r_shader_overrides[i];
void *buffer;
if (entry->hash != hash || entry->original_size != original_size ||
memcmp(entry->original, original, original_size))
continue;
if (!(buffer = vkd3d_malloc(entry->optimized_size)))
return false;
memcpy(buffer, entry->optimized, entry->optimized_size);
*data = buffer;
*size = entry->optimized_size;
INFO("d4r: using validated local optimization for shader %016"PRIx64".\\n", hash);
return true;
}
return false;
}
'''
(source / 'libs/vkd3d-shader/d4r_shader_overrides.h').write_text(header)
(inputs / 'd4r_shader_overrides.h').write_text(header)
records['optimizerSha256'] = sha(optimizer)
(inputs / 'manifest.json').write_text(json.dumps(records, indent=2) + '\n')
return records
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output', required=True, type=lambda p: Path(p).resolve())
parser.add_argument('--source', type=lambda p: Path(p).resolve(),
help='reuse an existing vkd3d-proton clone (must be a checkout of the pinned commit); default: fresh clone under --output/_work')
parser.add_argument('--shader-dump', type=lambda p: Path(p).resolve(), help='original SPIR-V from this translator/device')
parser.add_argument('--shader-variant', action='append', default=[], type=lambda p: Path(p).resolve(), help='additional exact translation variant directory')
parser.add_argument('--spirv-opt', type=lambda p: Path(p).resolve(), help='local spirv-opt.exe used for measured optimizations')
parser.add_argument('--optimize-shader', action='append', default=[], metavar='HASH', help='embed a measured -O shader; repeat for each validated winner')
args = parser.parse_args()
if not args.output.is_relative_to((repo / 'build').resolve()):
parser.error('output must stay under ignored build/')
@@ -85,12 +173,25 @@ def main():
if not ancestor:
git(['cherry-pick', '-x'] + listing, dxil, log, identity=True)
# libc++ include fixes (GCC pulled these in transitively; libc++ does not)
# and late shader replacement, preserving dxil-spirv's reflected metadata.
for patch, where in (('0001-dxil-spirv-include-exception-for-libcxx.patch', dxil),
('0002-dxbc-spirv-include-new-for-libcxx.patch', dxil / 'subprojects/dxbc-spirv')):
('0002-dxbc-spirv-include-new-for-libcxx.patch', dxil / 'subprojects/dxbc-spirv'),
('0003-preserve-meta-for-shader-overrides.patch', source),
('0004-support-local-optimized-shaders.patch', source)):
full = repo / 'patches/vkd3d-proton-windows' / patch
check = subprocess.run(['git', 'apply', '--check', '--reverse', str(full)], cwd=where, capture_output=True)
if check.returncode: # not applied yet
git(['apply', full], where, log)
optimized_shaders = generate_shader_overrides(source, args.output, args.shader_dump, args.spirv_opt,
args.optimize_shader, log, args.shader_variant)
# Cache identities must include local patches and shader contents, not only upstream HEAD.
build_identity = hashlib.sha256()
build_identity.update((VKD3D_COMMIT + DXIL_FSR_MERGE).encode())
for patch in sorted((repo / 'patches/vkd3d-proton-windows').glob('*.patch')):
build_identity.update(patch.read_bytes())
build_identity.update(json.dumps(optimized_shaders, sort_keys=True).encode())
build_id = build_identity.hexdigest()[:16]
(source / 'vkd3d_build.h.in').write_text('#include <stdint.h>\nstatic const uint64_t vkd3d_build = 0x' + build_id + ';\n')
escaped = lambda path: str(path).replace('\\', '/')
cross = work / 'cross.txt'
cross.write_text('\n'.join([
@@ -129,15 +230,18 @@ def main():
'fsr/d4r_vkd3d_core.dll is vkd3d-proton (LGPL-2.1) built for Windows by tools/windows/build_fsr_payload.py.',
'Corresponding source: ' + VKD3D_URL + ' at ' + VKD3D_COMMIT + ',',
'dxil-spirv with upstream PR 307 (merge ' + DXIL_FSR_MERGE + ') cherry-picked, and the patches in',
'patches/vkd3d-proton-windows/ of the d4r repository (libc++ include fixes). Build: meson cross build with',
'patches/vkd3d-proton-windows/ of the d4r repository (libc++ fixes and metadata-preserving shader overrides).',
'Build: meson cross build with',
'llvm-mingw clang, -Denable_extended_emulation=true, buildtype release; the DLL is stripped and renamed.',
'Built HEADs: ' + json.dumps(heads), 'Patches: ' + json.dumps(patches),
'Cache build ID: ' + build_id, 'Local optimized shaders: ' + json.dumps(optimized_shaders),
'Local shader source inputs and generated header, when selected: shader-sources/.',
'fsr/amdxc64.dll is d4r original code (tools/windows/amdxc64_shim). It is not AMD software.', '']))
files = {str(p.relative_to(args.output)).replace('\\', '/'): sha(p) for p in sorted(args.output.rglob('*'))
if p.is_file() and '_work' not in p.relative_to(args.output).parts}
(args.output / 'fsr-payload.json').write_text(json.dumps(dict(
schema=1, kind='d4r-fsr-payload', amdDenoiserBundled=False, vkd3dProtonCommit=VKD3D_COMMIT, dxilSpirvFsrMerge=DXIL_FSR_MERGE,
builtHeads=heads, patches=patches, files=files), indent=2) + '\n')
builtHeads=heads, patches=patches, optimizedShaders=optimized_shaders, cacheBuildId=build_id, files=files), indent=2) + '\n')
print(json.dumps(files, indent=2))
print('FSR_PAYLOAD_BUILT ' + str(args.output))
+14 -4
View File
@@ -34,10 +34,14 @@ def main():
parser.add_argument('--output-directory', type=pathlib.Path, required=True)
parser.add_argument('--gpu-arch', choices=tuple(TARGETS), default='gfx1201')
parser.add_argument('--shader-mode', choices=['cu', 'wgp'], default='cu')
parser.add_argument('--wave64', action='store_true', help='experimental wave64 for M post kernels without matrix operations')
args = parser.parse_args()
if not re.fullmatch(r'hiluma_engine_(?:output_depth(?:inv|reg)_mv(?:hi|lo)_(?:hdr|ldr)(?:_max)?|'
r'input_depth(?:inv|reg)_mv(?:hi|lo)_(?:hdr|ldr))_v2_rel', args.kernel):
parser.error('this private experimental build recipe supports K v2 input/output variants only')
is_m_post = bool(re.fullmatch(r'rrlite_post_3_[12]_mv(?:hi|lo)_(?:hdr|ldr)_folded', args.kernel))
if not is_m_post and not re.fullmatch(r'hiluma_engine_(?:output_depth(?:inv|reg)_mv(?:hi|lo)_(?:hdr|ldr)(?:_max)?|'
r'input_depth(?:inv|reg)_mv(?:hi|lo)_(?:hdr|ldr))_v2_rel', args.kernel):
parser.error('this private experimental build recipe supports K v2 input/output and M folded post variants only')
if args.wave64 and not is_m_post:
parser.error('--wave64 is restricted to M post kernels without matrix operations')
repo = pathlib.Path(__file__).resolve().parents[2]
output = args.output_directory.resolve()
if not output.is_relative_to((repo / 'build').resolve()):
@@ -70,6 +74,8 @@ def main():
for name in ('D4R_ZLUDA_IGNORE_DENORMAL', 'D4R_ZLUDA_FAST_MATH', 'D4R_ZLUDA_WMMA_F32ACC',
'D4R_ZLUDA_WAVE64', 'D4R_ZLUDA_PROFILE'):
env.pop(name, None)
if args.wave64:
env['D4R_ZLUDA_WAVE64'] = '1'
env['PATH'] = str(zluda) + os.pathsep + str(hip / 'bin') + os.pathsep + env['PATH']
def run(label, command):
@@ -94,7 +100,8 @@ def main():
raise RuntimeError('PTX recipe made no changes; refuse a no-op override')
raw, ir, extra = work / 'raw.bc', work / 'raw.ll', work / 'extra.bc'
run('compile-helper', [tools['clang++'], '-x', 'hip', '-std=c++20', '-nogpuinc', '-nogpulib', '-O3',
'-mno-wavefrontsize64', '--offload-device-only', '--offload-arch=' + args.gpu_arch,
'-mwavefrontsize64' if args.wave64 else '-mno-wavefrontsize64',
'--offload-device-only', '--offload-arch=' + args.gpu_arch,
'-fgpu-rdc', '-emit-llvm', '-c', '-Xclang', '-fdenormal-fp-math=dynamic',
'-DD4R_ACCURACY', '-DD4R_KERNEL_NAME=' + args.kernel,
'-o', raw, repo / 'kernels/tex/sust_only.hip'])
@@ -122,10 +129,13 @@ def main():
expected_mode = '-cumode' if args.shader_mode == 'wgp' else '+cumode'
if not attributes or expected_mode not in attributes[1]:
raise RuntimeError(f'Emitter did not honor {args.shader_mode} mode; build the corresponding ZLUDA patch first')
if args.wave64 and '+wavefrontsize64' not in attributes[1]:
raise RuntimeError('Emitter did not honor wave64; candidate is not accepted')
require_code_object_target(work / 'emitted/module.hsaco', args.gpu_arch)
shutil.copyfile(work / 'emitted/module.hsaco', output / (args.kernel + '.hsaco'))
run('manifest', [sys.executable, repo / 'kernels/tools/kernel_manifest.py', output, args.dlss_dll])
metadata = dict(architecture=args.gpu_arch, accuracy=True, nativeFP8=False, shaderMode=args.shader_mode,
wave64=args.wave64,
lastBuiltKernel=args.kernel, lastRecipeRewrites=rewrite_counts,
privateNvidiaDerivedCode=True, validation='not yet validated; do not install before comparison',
dlssSha256=hashlib.sha256(args.dlss_dll.read_bytes()).hexdigest(),
+43 -9
View File
@@ -1,4 +1,4 @@
"""Build exact gfx11 RR D/E models natively on Windows, without Git Bash.
"""Build exact gfx11/gfx12 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.
@@ -10,6 +10,7 @@ import os
from pathlib import Path
import re
import shutil
import struct
import subprocess
import sys
@@ -19,11 +20,33 @@ def sha(path):
return hashlib.file_digest(stream, 'sha256').hexdigest()
def require_fp8_matrices(path):
"""Reject an RDNA4 candidate that lost native FP8 cooperative matrices."""
data = path.read_bytes()
if len(data) < 20 or len(data) % 4:
raise RuntimeError('Invalid SPIR-V: ' + str(path))
words = struct.unpack('<' + 'I' * (len(data) // 4), data)
if words[0] != 0x07230203:
raise RuntimeError('Invalid SPIR-V magic: ' + str(path))
capabilities = set()
offset = 5
while offset < len(words):
size, opcode = words[offset] >> 16, words[offset] & 0xffff
if not size or offset + size > len(words):
raise RuntimeError('Invalid SPIR-V instruction: ' + str(path))
if opcode == 17 and size == 2: # OpCapability
capabilities.add(words[offset + 1])
offset += size
if not {4212, 4213, 6022}.issubset(capabilities):
raise RuntimeError('Native FP8 cooperative matrices are absent: ' + str(path))
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('--gpu-arch', choices=('gfx1101', 'gfx1200', 'gfx1201'), default='gfx1101')
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')
@@ -38,7 +61,9 @@ def main():
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'))
gfx11 = args.gpu_arch.startswith('gfx11')
env.update(D4R_RR_GFX11='1' if gfx11 else '0', 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__))}
@@ -46,7 +71,8 @@ def main():
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',
identity = {'schema': 1, 'preset': args.preset, 'architecture': args.gpu_arch,
'arithmetic': 'exact-gfx11' if gfx11 else 'exact-gfx12',
'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'
@@ -91,16 +117,24 @@ def main():
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 [])
manifest = (out / 'rr.txt').read_text()
arithmetic = 'gfx11' if gfx11 else 'gfx12'
manifest = manifest.replace(f'preset {4 if args.preset == "D" else 5}\n',
f'preset {4 if args.preset == "D" else 5}\narithmetic {arithmetic}\n')
defines = ['-DRR_EXACT_CONVERT'] + (['-DRR_GFX11'] if gfx11 else [])
defines += ['-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',
widening = []
if gfx11:
wide = int(python(rr / 'widen_weights.py', weights, label='widen-' + stage).splitlines()[-1])
widening = [f'-DW_WIDE={wide}u']
run([args.glslang, '--target-env', 'vulkan1.3', '-V', *defines, *widening,
'-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)
if not gfx11:
require_fp8_matrices(out / f'{stage}_m.spv')
pattern = rf'^stage {stage} {stage}\.spv 32 weights-{stage}\.bin (.*)$'
def replacement(match):
tail = match[1].removesuffix(' noopt')
@@ -139,7 +173,7 @@ def main():
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:
if len(stages) != 12 or f'arithmetic {arithmetic}' not in manifest.splitlines():
raise RuntimeError('Incomplete native manifest')
for fields in stages:
if not (out / fields[2]).is_file() or not (out / fields[4]).is_file():
+6 -2
View File
@@ -145,13 +145,17 @@ public:
api_.cuda.check(api_.cuMemcpy2DAsync_v2(&copy, nullptr), "cuMemcpy2DAsync_v2(external VRAM to array)");
} else api_.cuda.check(api_.cuMemcpy2D_v2(&copy), "cuMemcpy2D_v2(external VRAM to array)");
}
void download_device(CUdeviceptr destination, size_t pitch) {
void download_device(CUdeviceptr destination, size_t pitch, bool deferred = false) {
if (pitch < rowBytes_) throw std::runtime_error("Device output pitch is smaller than its row");
Copy2D copy{};
copy.srcType = 3; copy.srcArray = array_;
copy.dstType = 2; copy.dstDevice = destination; copy.dstPitch = pitch;
copy.widthBytes = rowBytes_; copy.height = height_;
api_.cuda.check(api_.cuMemcpy2D_v2(&copy), "cuMemcpy2D_v2(array to external VRAM)");
if (deferred) {
// Match input batching: conversion follows on the shared legacy
// default stream, and the caller completes the batch before D3D12.
api_.cuda.check(api_.cuMemcpy2DAsync_v2(&copy, nullptr), "cuMemcpy2DAsync_v2(array to external VRAM)");
} else api_.cuda.check(api_.cuMemcpy2D_v2(&copy), "cuMemcpy2D_v2(array to external VRAM)");
}
};
}
+3 -1
View File
@@ -525,7 +525,9 @@ void STDMETHODCALLTYPE hook_execute(ID3D12CommandQueue* queue, UINT count, ID3D1
milliseconds(submitStart), boundaryMs, publishMs, drainMs);
}
} catch (const std::exception& failure) {
{ std::lock_guard<std::mutex> lock(error_mutex); last_error = failure.what(); }
// Only this submission fails. Command tracking is intact, so later frames
// record and run normally; a transient failure (one refused shared-buffer
// export after a feature recreate) must not disable DLSS for the session.
std::fprintf(stderr, "D4R_D3D12_SUBMIT_FAILURE %s\n", failure.what());
try { complete(queue); } catch (...) {}
// Do not submit a consumer suffix after a failed DLSS boundary.
+7
View File
@@ -52,6 +52,13 @@ int main(int argc, char** argv) {
if (!found) throw std::runtime_error("HIP adapter missing from DXGI");
ComPtr<ID3D12Device> device;
dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(device.GetAddressOf())), "D3D12CreateDevice");
// D4R_DIAG_VRAM_BALLAST_MB: occupy that much VRAM first, reproducing a game near its memory budget.
std::vector<ComPtr<ID3D12Resource>> ballast;
if (const char* mb = std::getenv("D4R_DIAG_VRAM_BALLAST_MB")) {
for (unsigned long long used = 0, total = std::stoull(mb); used + 256 <= total; used += 256)
ballast.push_back(make_buffer(device.Get(), 256ull << 20, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COMMON));
std::printf("D3D12_VRAM_BALLAST buffers=%zu mib=%zu\n", ballast.size(), ballast.size() * 256);
}
if (enhanced) { D3D12_FEATURE_DATA_D3D12_OPTIONS12 support{}; dx(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS12, &support, sizeof(support)), "Enhanced barrier support"); if (!support.EnhancedBarriersSupported) throw std::runtime_error("Enhanced barriers unavailable"); }
auto enhancedBarrier = [&](ID3D12GraphicsCommandList* command, ID3D12Resource* texture, unsigned plane, bool initial) {
ComPtr<ID3D12GraphicsCommandList7> newer; dx(command->QueryInterface(IID_PPV_ARGS(newer.GetAddressOf())), "Harness enhanced list");
+111 -13
View File
@@ -4,6 +4,8 @@
#endif
#include "hip_api.h"
#include <d3d12.h>
#include <dxgi1_4.h>
#include <mutex>
#include <wrl/client.h>
namespace d4r::win {
@@ -103,10 +105,106 @@ inline void texture_copy_barrier(ID3D12GraphicsCommandList* list, ID3D12Resource
class SharedPlane {
ExternalApi& api_;
hipExternalMemory_t memory_ = nullptr;
ComPtr<ID3D12Heap> heap_; // only for the explicit-heap export; outlives buffer (declared first)
void release() noexcept {
if (mapped) { (void)api_.hip.hipFree(mapped); mapped = nullptr; }
if (memory_) { (void)api_.hipDestroyExternalMemory(memory_); memory_ = nullptr; }
}
// Export a committed shared buffer (the tested path). Returns false when the driver refuses
// the export: Cyberpunk 2077 saw CreateSharedHandle E_INVALIDARG on a fresh shared buffer
// after a DLSS feature recreate, although the same sequence works outside the game.
bool export_committed(ID3D12Device* device, Handle& handle, UINT64& importBytes) {
buffer = make_buffer(device, bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_SHARED, D3D12_RESOURCE_STATE_COMMON);
const auto desc = resource_desc(buffer.Get());
D3D12_RESOURCE_ALLOCATION_INFO allocation{};
#if defined(__MINGW32__)
device->GetResourceAllocationInfo(&allocation, 0, 1, &desc);
#else
allocation = device->GetResourceAllocationInfo(0, 1, &desc);
#endif
const HRESULT result = device->CreateSharedHandle(buffer.Get(), nullptr, GENERIC_ALL, nullptr, &handle.value);
if (SUCCEEDED(result)) { importBytes = allocation.SizeInBytes; return true; }
// A wrapping layer can hand back a resource whose owner is not the device it was created on.
ComPtr<ID3D12Device> owner; (void)buffer->GetDevice(IID_PPV_ARGS(owner.GetAddressOf()));
std::fprintf(stderr, "D4R_SHARED_PLANE_EXPORT committed HRESULT=0x%08lx bytes=%llu allocation=%llu device_removed=0x%08lx owner_matches=%d\n",
static_cast<unsigned long>(result), static_cast<unsigned long long>(bytes),
static_cast<unsigned long long>(allocation.SizeInBytes), static_cast<unsigned long>(device->GetDeviceRemovedReason()),
owner.Get() == device);
buffer.Reset(); handle.value = nullptr;
return false;
}
// Last resort when the device refuses every export (Cyberpunk 2077 with the FSR RR backend refused
// both the committed and the heap export): allocate and export on a private device of the same
// adapter, then open that allocation on the caller's device for the copies. HIP imports the
// private device's handle, which names the same memory.
ComPtr<ID3D12Device> privateDevice_;
ComPtr<ID3D12Resource> privateBuffer_;
static ComPtr<ID3D12Device> private_device(ID3D12Device* device) {
static std::mutex lock; static ComPtr<ID3D12Device> cached; static LUID cachedLuid{};
std::lock_guard<std::mutex> guard(lock);
const LUID luid = adapter_luid(device);
if (cached && cachedLuid.LowPart == luid.LowPart && cachedLuid.HighPart == luid.HighPart) return cached;
using CreateFactory = HRESULT(WINAPI*)(REFIID, void**);
HMODULE dxgi = LoadLibraryW(L"dxgi.dll");
auto create = dxgi ? reinterpret_cast<CreateFactory>(reinterpret_cast<void*>(GetProcAddress(dxgi, "CreateDXGIFactory1"))) : nullptr;
if (!create) throw std::runtime_error("CreateDXGIFactory1 unavailable (private shared-plane device)");
ComPtr<IDXGIFactory4> factory; dx(create(IID_PPV_ARGS(factory.GetAddressOf())), "CreateDXGIFactory1(private shared-plane device)");
ComPtr<IDXGIAdapter1> adapter; dx(factory->EnumAdapterByLuid(luid, IID_PPV_ARGS(adapter.GetAddressOf())), "EnumAdapterByLuid(private shared-plane device)");
// D3D12CreateDevice returns the adapter's existing singleton (the game's own device), so an
// independent device comes from the device factory, neither reusing nor replacing the singleton.
using GetInterface = HRESULT(WINAPI*)(REFCLSID, REFIID, void**);
HMODULE d3d12 = GetModuleHandleW(L"d3d12.dll");
auto getInterface = d3d12 ? reinterpret_cast<GetInterface>(reinterpret_cast<void*>(GetProcAddress(d3d12, "D3D12GetInterface"))) : nullptr;
if (!getInterface) throw std::runtime_error("D3D12GetInterface unavailable (private shared-plane device)");
static const GUID deviceFactoryClsid = {0x114863bf, 0xc386, 0x4aee, {0xb3, 0x9d, 0x8f, 0x0b, 0xbb, 0x06, 0x29, 0x55}};
ComPtr<ID3D12DeviceFactory> deviceFactory;
dx(getInterface(deviceFactoryClsid, IID_PPV_ARGS(deviceFactory.GetAddressOf())), "D3D12GetInterface(device factory)");
dx(deviceFactory->SetFlags(D3D12_DEVICE_FACTORY_FLAG_DISALLOW_STORING_NEW_DEVICE_AS_SINGLETON), "Device factory flags");
ComPtr<ID3D12Device> created;
dx(deviceFactory->CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(created.GetAddressOf())), "Create private shared-plane device");
ComPtr<IUnknown> createdIdentity, callerIdentity;
(void)created->QueryInterface(IID_PPV_ARGS(createdIdentity.GetAddressOf()));
(void)device->QueryInterface(IID_PPV_ARGS(callerIdentity.GetAddressOf()));
std::fprintf(stderr, "D4R_SHARED_PLANE_PRIVATE_DEVICE independent=%d\n", createdIdentity.Get() != callerIdentity.Get());
cached = created; cachedLuid = luid;
return cached;
}
void export_private(ID3D12Device* device, Handle& handle, UINT64& importBytes) {
privateDevice_ = private_device(device);
privateBuffer_ = make_buffer(privateDevice_.Get(), bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_SHARED, D3D12_RESOURCE_STATE_COMMON);
const auto desc = resource_desc(privateBuffer_.Get());
D3D12_RESOURCE_ALLOCATION_INFO allocation{};
#if defined(__MINGW32__)
privateDevice_->GetResourceAllocationInfo(&allocation, 0, 1, &desc);
#else
allocation = privateDevice_->GetResourceAllocationInfo(0, 1, &desc);
#endif
dx(privateDevice_->CreateSharedHandle(privateBuffer_.Get(), nullptr, GENERIC_ALL, nullptr, &handle.value), "CreateSharedHandle(private device)");
dx(device->OpenSharedHandle(handle.value, IID_PPV_ARGS(buffer.GetAddressOf())), "OpenSharedHandle(private shared plane)");
importBytes = allocation.SizeInBytes;
}
// Fallback: share an explicit heap that holds the buffer as a placed resource.
bool export_heap(ID3D12Device* device, Handle& handle, UINT64& importBytes) {
constexpr UINT64 alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
D3D12_HEAP_DESC heap{};
heap.SizeInBytes = (bytes + alignment - 1) / alignment * alignment;
heap.Properties.Type = D3D12_HEAP_TYPE_DEFAULT;
heap.Properties.CreationNodeMask = heap.Properties.VisibleNodeMask = 1;
heap.Alignment = alignment;
heap.Flags = D3D12_HEAP_FLAG_SHARED | D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;
dx(device->CreateHeap(&heap, IID_PPV_ARGS(heap_.GetAddressOf())), "CreateHeap(shared plane)");
D3D12_RESOURCE_DESC desc{};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; desc.Width = bytes;
desc.Height = desc.DepthOrArraySize = desc.MipLevels = desc.SampleDesc.Count = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
dx(device->CreatePlacedResource(heap_.Get(), 0, &desc, D3D12_RESOURCE_STATE_COMMON, nullptr,
IID_PPV_ARGS(buffer.GetAddressOf())), "CreatePlacedResource(shared plane)");
const HRESULT result = device->CreateSharedHandle(heap_.Get(), nullptr, GENERIC_ALL, nullptr, &handle.value);
if (SUCCEEDED(result)) { importBytes = heap.SizeInBytes; return true; }
std::fprintf(stderr, "D4R_SHARED_PLANE_EXPORT heap HRESULT=0x%08lx\n", static_cast<unsigned long>(result));
buffer.Reset(); heap_.Reset(); handle.value = nullptr;
return false;
}
public:
ComPtr<ID3D12Resource> buffer;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint{};
@@ -124,28 +222,28 @@ public:
}
if (!bytes || bytes == UINT64_MAX || !footprint.Footprint.RowPitch)
throw std::runtime_error("Invalid shared texture footprint");
buffer = make_buffer(device, bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_SHARED, D3D12_RESOURCE_STATE_COMMON);
const auto desc = resource_desc(buffer.Get());
D3D12_RESOURCE_ALLOCATION_INFO allocation{};
#if defined(__MINGW32__)
device->GetResourceAllocationInfo(&allocation, 0, 1, &desc);
#else
allocation = device->GetResourceAllocationInfo(0, 1, &desc);
#endif
Handle handle;
dx(device->CreateSharedHandle(buffer.Get(), nullptr, GENERIC_ALL, nullptr, &handle.value), "CreateSharedHandle(VRAM)");
UINT64 importBytes = 0;
// D4R_SHARED_PLANE_EXPORT=heap or =private forces that fallback, for testing it.
const char* forcedValue = std::getenv("D4R_SHARED_PLANE_EXPORT");
const std::string forced = forcedValue ? forcedValue : "";
const char* path = "committed";
bool exported = forced.empty() && (export_committed(device, handle, importBytes) || export_committed(device, handle, importBytes));
if (!exported && forced != "private") { path = "heap"; exported = export_heap(device, handle, importBytes); }
if (!exported) { path = "private"; export_private(device, handle, importBytes); }
const bool heapImport = std::string(path) == "heap";
try {
hipExternalMemoryHandleDesc import{};
import.type = hipExternalMemoryHandleTypeD3D12Resource;
import.type = heapImport ? hipExternalMemoryHandleTypeD3D12Heap : hipExternalMemoryHandleTypeD3D12Resource;
import.handle.win32.handle = handle.value;
import.size = allocation.SizeInBytes; import.flags = hipExternalMemoryDedicated;
import.size = importBytes; import.flags = heapImport ? 0 : hipExternalMemoryDedicated;
api.hip.check(api.hipImportExternalMemory(&memory_, &import), "hipImportExternalMemory(D3D12)");
hipExternalMemoryBufferDesc map{}; map.size = bytes;
api.hip.check(api.hipExternalMemoryGetMappedBuffer(&mapped, memory_, &map), "hipExternalMemoryGetMappedBuffer");
} catch (...) { release(); throw; }
// Win32 imports retain their own reference; the exporter closes its HANDLE.
std::printf("D4R_IMPORT width=%u height=%u pitch=%u allocation=%llu cpu_copy=0\n",
footprint.Footprint.Width, footprint.Footprint.Height, footprint.Footprint.RowPitch, allocation.SizeInBytes);
std::printf("D4R_IMPORT width=%u height=%u pitch=%u allocation=%llu export=%s cpu_copy=0\n",
footprint.Footprint.Width, footprint.Footprint.Height, footprint.Footprint.RowPitch, importBytes, path);
}
SharedPlane(const SharedPlane&) = delete;
~SharedPlane() { release(); }
+6 -1
View File
@@ -32,6 +32,9 @@ const Case cases[] = {
{DXGI_FORMAT_R32G32B32A32_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT, 4, "exposure-rgba32f"},
{DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, 4, "exposure-rgba8"},
{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM, 4, "exposure-bgra8"},
{DXGI_FORMAT_R9G9B9E5_SHAREDEXP, DXGI_FORMAT_R9G9B9E5_SHAREDEXP, 0, "rgb9e5"},
{DXGI_FORMAT_R16_FLOAT, DXGI_FORMAT_R16_FLOAT, 1, "depth-r16f"},
{DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, 2, "motion-rgba8"},
};
struct List {
ComPtr<ID3D12CommandAllocator> alloc;
@@ -93,6 +96,7 @@ void fixture(uint8_t* row, unsigned y, PixelSpec spec) {
case d4r::pixel::rg16snorm: case d4r::pixel::rg16unorm: case d4r::pixel::r16unorm:
for (unsigned c = 0; c < spec.bytes / 2; ++c) reinterpret_cast<uint16_t*>(p)[c] = uint16_t(i < 4 ? i * 32767 : i * 193 + c * 37); break;
case d4r::pixel::depth24: *reinterpret_cast<uint32_t*>(p) = (i * 3791) | ((i % 256) << 24); break;
case d4r::pixel::rgb9e5: *reinterpret_cast<uint32_t*>(p) = i * 2654435761u; break; // every exponent and mantissa
default: throw std::runtime_error("Missing diagnostic fixture");
}
}
@@ -231,7 +235,8 @@ int main(int argc, char** argv) {
if (mismatch && differs++ < 5) std::fprintf(stderr, "PIXEL_DIFFERENT format=%s pixel=%u channel=%u expected=%08x actual=%08x\n", item.name, y * width + x, c, expected[i], got);
}
if (differs) throw std::runtime_error(std::string("D3D12 typed decode mismatch: ") + item.name + " count=" + std::to_string(differs));
if (item.plane == 0) {
// D3D12 has no typed UAV store for R9G9B9E5, so its encoder has no hardware reference here.
if (item.plane == 0 && spec.storage != d4r::pixel::rgb9e5) {
// BGRA uses the identical UNORM converter through an RGBA UAV;
// the consumer SRV independently tests the channel permutation.
DXGI_FORMAT encodedFormat = spec.storage == d4r::pixel::bgra8 ? DXGI_FORMAT_R8G8B8A8_UNORM : item.view;
+56 -21
View File
@@ -27,17 +27,26 @@ void initialize(ID3D12Device* device, unsigned long long app, const wchar_t* dat
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;
// A deferred initialization whose SR runtime could not start yields to a later Init.
if (runtime || !deferredSr) 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" || std::string(backend) == "fsr");
#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;
// SR starts eagerly; RuntimeInitialization::create falls back to d4r's project identity when
// NGX rejects the game's application ID, so Streamline's first capability query sees SR and RR.
// Native RR owns its Vulkan execution independently, so with an RR backend a failed SR start
// (no SR files installed) keeps the initialization for RR alone, and a later Init replaces it.
try { runtime = candidate->create(); }
catch (const std::exception& error) {
if (!native) throw;
std::fprintf(stderr, "D4R_SR_UNAVAILABLE %s\n", error.what());
initialization = std::move(candidate); deferredSr = true;
return;
}
initialization = std::move(candidate); deferredSr = false;
}
struct Handle { unsigned Id; };
unsigned nextId = 1;
@@ -161,26 +170,25 @@ bool fsr_ready() {
#if defined(D4R_FSR_RR_BACKEND)
const auto config = d4r::win::rr::FsrConfig::from_environment();
auto present = [](const std::wstring& path) { return !path.empty() && GetFileAttributesW(path.c_str()) != INVALID_FILE_ATTRIBUTES; };
const bool ready = present(config.vkd3dCore) && present(config.denoiserDll) && present(config.amdxcShim);
if (!ready) std::fprintf(stderr, "D4R_RR_UNAVAILABLE FSR RR needs D4R_FSR_VKD3D_CORE, D4R_FSR_RR_DLL and D4R_FSR_AMDXC_SHIM to exist\n");
const bool native = d4r::win::rr::FsrConfig::use_native_device();
const bool ready = present(config.denoiserDll) && (native || (present(config.vkd3dCore) && present(config.amdxcShim)));
if (!ready) std::fprintf(stderr, "D4R_RR_UNAVAILABLE FSR RR needs %s\n", native ? "D4R_FSR_RR_DLL to exist" :
"D4R_FSR_VKD3D_CORE, D4R_FSR_RR_DLL and D4R_FSR_AMDXC_SHIM to exist");
return ready;
#else
return false;
#endif
}
bool rr_ready(ID3D12Device* device) {
// Readiness is a file check, like fsr_ready. Building a Backend here created a Vulkan device inside
// the game during Streamline's startup capability queries (about 30 s in Cyberpunk 2077), and
// OptiScaler's Streamline hooks then crashed when Streamline reloaded its plugins. The model, adapter
// and interop are validated when the game creates the RR feature; a failure there fails that call.
bool rr_ready() {
if (rr_is_fsr()) return fsr_ready();
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);
const auto directory = std::filesystem::path(d4r::diag::wide(rr_directory()));
if (!std::filesystem::is_regular_file(directory / L"d4r-private-rr.json"))
throw std::runtime_error("RR model manifest missing: " + (directory / L"d4r-private-rr.json").string());
return true;
} catch (const std::exception& error) {
if (std::getenv("D4R_RR_BACKEND")) std::fprintf(stderr, "D4R_RR_UNAVAILABLE %s\n", error.what());
@@ -193,6 +201,7 @@ struct RrSnapshot {
std::array<bool, d4r::win::rr::Snapshot::Count> explicitState{};
RrSnapshot(const d4r::win::rr::Creation& creation, void* p, ID3D12GraphicsCommandList* list)
: frame(creation, p) {
if (rr_is_fsr()) use_fsr_hit_distances(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"};
@@ -213,6 +222,33 @@ struct RrSnapshot {
access[4] = access[3]; explicitState[4] = explicitState[3];
}
}
// The Linux FSR backend's guide reading: a specular or diffuse hit distance may come as the w of a
// ray-direction texture, and either one makes that lobe an indirect signal, which AMD's denoiser filters
// far more strongly than a direct one. FSR has no disocclusion or subsurface input, so the diffuse hit
// distance travels in the Subsurface slot. Done before the access states below are captured.
void use_fsr_hit_distances(void* p) {
using d4r::win::rr::Snapshot;
auto origin = [&](const char* prefix) {
return std::array<unsigned, 2>{d4r::win::rr::number(p, (std::string(prefix) + ".Subrect.Base.X").c_str()),
d4r::win::rr::number(p, (std::string(prefix) + ".Subrect.Base.Y").c_str())};
};
if (!frame.resources[Snapshot::HitDistance]) {
if (auto* ray = Snapshot::resource(p, "DLSSD.SpecularRayDirectionHitDistance")) {
frame.resources[Snapshot::HitDistance] = ray; frame.specularHitInW = true;
frame.origins[Snapshot::HitDistance] = origin("DLSSD.SpecularRayDirectionHitDistance");
}
}
frame.resources[Snapshot::Disocclusion].Reset(); frame.resources[Snapshot::Subsurface].Reset();
const char* diffuseName = "DLSSD.DiffuseHitDistance";
ID3D12Resource* diffuse = Snapshot::resource(p, diffuseName);
if (!diffuse && (diffuse = Snapshot::resource(p, "DLSSD.DiffuseRayDirectionHitDistance"))) {
diffuseName = "DLSSD.DiffuseRayDirectionHitDistance"; frame.diffuseHitInW = true;
}
if (diffuse) {
frame.resources[Snapshot::Subsurface] = diffuse; frame.diffuseHit = true;
frame.origins[Snapshot::Subsurface] = origin(diffuseName);
}
}
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]);
@@ -273,7 +309,7 @@ unsigned allocate(void** out, bool caps) {
}
bool rrAvailable = false;
#if defined(D4R_NATIVE_RR_BACKEND)
rrAvailable = rr_ready(initialization->device.Get());
rrAvailable = rr_ready();
#endif
d4r_ngx_set_int(value, "SuperSamplingDenoising.Available", rrAvailable ? 1 : 0);
d4r_ngx_set_int(value, "SuperSamplingDenoising.NeedsUpdatedDriver", 0);
@@ -320,8 +356,7 @@ API unsigned NVSDK_NGX_D3D12_GetFeatureRequirements(IDXGIAdapter* adapter, const
#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())) {
if (rr_ready()) {
requirements->FeatureSupported = 0;
std::strcpy(requirements->MinOSVersion, "10.0.22000.0");
}
+18 -3
View File
@@ -61,6 +61,21 @@ def select_native(data, model):
return (b'\xef\xbb\xbf' if bom else b'') + ''.join(result).encode('utf-8')
def model_arithmetic(identity, runtime):
"""Check the model's arithmetic and target before copying any products."""
architecture = identity.get('architecture')
arithmetic = {'gfx1101': 'gfx11', 'gfx1200': 'gfx12', 'gfx1201': 'gfx12'}.get(architecture)
if identity.get('preset') not in ('D', 'E') or not arithmetic or identity.get('arithmetic') != 'exact-' + arithmetic:
raise RuntimeError('Unsupported local RR cache identity')
target_file = runtime / 'gpu-target.json'
if target_file.is_file():
if json.loads(target_file.read_text(encoding='utf-8-sig')).get('architecture') != architecture:
raise RuntimeError('RR model architecture does not match runtime target')
elif arithmetic == 'gfx12':
raise RuntimeError('RDNA4 RR deployment requires runtime gpu-target.json')
return arithmetic
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--model', required=True, type=lambda p: Path(p).resolve())
@@ -77,8 +92,7 @@ def main():
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')
arithmetic = model_arithmetic(identity, args.runtime)
products = cache['products']
if not {'rr.txt', 'enc0.args', 'hkpn.args'}.issubset(products):
raise RuntimeError('RR cache omits required manifest/templates')
@@ -92,7 +106,8 @@ def main():
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):
len(stages) != 12 or {fields[1] for fields in stages} != expected_stages or
f'arithmetic {arithmetic}' not in manifest.splitlines()):
raise RuntimeError('Incomplete RR model manifest')
for fields in stages:
if fields[2] not in products or fields[4] not in products:
+45
View File
@@ -0,0 +1,45 @@
// Stress the bounded FP32 path against the original sequential FP64 dot.
#define SWIN_BOUNDED_PV
#include "../../kernels/m/swin_common.h"
struct PvParams { uint32_t* output; };
__device__ uint32_t pv_random(uint32_t& state) {
state ^= state << 13; state ^= state >> 17; state ^= state << 5;
return state;
}
extern "C" __global__ void exact_pv_probe(PvParams p) {
const unsigned index = blockIdx.x * 32 + threadIdx.x;
uint32_t state = index + 1;
half_t a[16], b[16];
for (int k = 0; k < 16; ++k) {
uint16_t x = pv_random(state), y = pv_random(state);
x = (x & 0x8000u) | ((x & 0x7fffu) % 0x7c00u);
y = (y & 0x8000u) | ((y & 0x7fffu) % 0x7c00u);
a[k] = __builtin_bit_cast(half_t, x);
b[k] = __builtin_bit_cast(half_t, y);
}
switch (index % 4) {
case 0: break; // Entire finite FP16 exponent range, signed products.
case 1: // Cancellation, including products too small for an FP32 sum.
for (int k = 0; k < 8; ++k) { a[k + 8] = -a[k]; b[k + 8] = b[k]; }
b[15] = (half_t)0.0f;
break;
case 2: // Every finite magnitude with exact midpoint perturbations.
case 3:
for (int k = 0; k < 16; ++k) { a[k] = (half_t)0.0f; b[k] = (half_t)1.0f; }
a[0] = __builtin_bit_cast(half_t, (uint16_t)((index / 4) % 0x7c00u));
{
const uint16_t bits = __builtin_bit_cast(uint16_t, a[0]);
const half_t next = __builtin_bit_cast(half_t, (uint16_t)(bits + (bits < 0x7bffu)));
a[1] = (half_t)((float)next - (float)a[0]);
}
b[1] = (half_t)0.5f;
a[2] = __builtin_bit_cast(half_t, (uint16_t)1);
b[2] = __builtin_bit_cast(half_t, (uint16_t)((index & 1) ? 0x8001u : 1u));
break;
}
double reference = 0.0;
for (int k = 0; k < 16; ++k) reference += (double)a[k] * (double)b[k];
const uint16_t expected = __builtin_bit_cast(uint16_t, (half_t)reference);
const uint16_t actual = __builtin_bit_cast(uint16_t, exact_pv_dot<1>(a, b));
p.output[index] = uint32_t(expected) | (uint32_t(actual) << 16);
}
+56
View File
@@ -0,0 +1,56 @@
// Native D3D12 counterpart of fsr_rr_compose.comp. Keep conversion semantics aligned.
// FSR Ray Regeneration backend, stage 3: the denoised diffuse and specular signals remodulated by
// their albedos, plus the residual the denoiser never saw (particles, what demodulation could not
// carry, sky). The result is render-resolution colour for the Super Sampling stage.
RWTexture2D<float4> denoisedDiffuse : register(u0);
RWTexture2D<float4> denoisedSpecular : register(u1);
RWTexture2D<float4> diffuseAlbedo : register(u2);
RWTexture2D<float4> specularAlbedo : register(u3);
RWTexture2D<float4> residual : register(u4);
RWTexture2D<float4> composed : register(u5);
RWTexture2D<float4> noisyDiffuse : register(u6);
RWTexture2D<float4> noisySpecular : register(u7);
struct FsrRrConstants
{
uint2 renderSize;
float haloLimit; // > 0: each denoised lobe at most this multiple of the brightest noisy input of that lobe around it
uint unused;
};
cbuffer Constants : register(b0) { FsrRrConstants pc; }
[numthreads(8, 8, 1)]
void main(uint3 tid : SV_DispatchThreadID)
{
const int2 px = int2(tid.xy);
if (px.x >= int(pc.renderSize.x) || px.y >= int(pc.renderSize.y))
return;
const float3 da = diffuseAlbedo[px].rgb, sa = specularAlbedo[px].rgb;
float3 diffuse = denoisedDiffuse[px].rgb * da;
float3 specular = denoisedSpecular[px].rgb * sa;
if (pc.haloLimit > 0.0)
{
// The denoiser can carry a thin object's large demodulated signal (dark albedo) across its edge, where this
// texel's albedo turns it into a bright fringe. A denoised value above everything around it in the noisy
// input is such a leak: capped, by luminance (the hue is kept).
const float3 w = float3(0.2126, 0.7152, 0.0722);
float diffuseMax = 0.0, specularMax = 0.0;
for (int dy = -1; dy <= 1; ++dy)
for (int dx = -1; dx <= 1; ++dx)
{
const int2 q = clamp(px + int2(dx, dy), int2(0, 0), int2(pc.renderSize) - 1);
// what the lobe contributes there (the neighbour's own albedo): a leaked demodulated signal
// remodulated by this texel's albedo exceeds all of these
diffuseMax = max(diffuseMax, dot(noisyDiffuse[q].rgb * diffuseAlbedo[q].rgb, w));
specularMax = max(specularMax, dot(noisySpecular[q].rgb * specularAlbedo[q].rgb, w));
}
const float dl = dot(diffuse, w), sl = dot(specular, w);
if (dl > pc.haloLimit * diffuseMax)
diffuse *= pc.haloLimit * diffuseMax / dl;
if (sl > pc.haloLimit * specularMax)
specular *= pc.haloLimit * specularMax / sl;
}
const float3 color = diffuse + specular + residual[px].rgb;
composed[px] = float4(clamp(color, 0.0, 65000.0), 1.0);
}
+195
View File
@@ -0,0 +1,195 @@
// Native D3D12 counterpart of fsr_rr_prepare.comp. Keep conversion semantics aligned.
// FSR Ray Regeneration backend, stage 1: a game's DLSS Ray Reconstruction inputs -> the inputs of
// AMD's FidelityFX denoiser 1.2 (ffx_denoiser.h), at render resolution.
//
// The split of the game's single noisy colour into a diffuse and a specular signal follows the
// DLSS-RR -> FSR-RR adapter of Zach Hembree's OptiScaler branch ffx-denoise-experimental (GPL-3.0,
// 18ac686, FSRDInputConv.hlsl, "Mode 2") and burak113's continuation (5a6adb1): each lobe gets the
// colour in proportion to its albedo and is demodulated by it. That is an approximation - one
// colour cannot be split exactly - and what does not survive demodulation goes to the residual,
// which bypasses the denoiser. Unlike that reference this writes FSR-RR 1.2's contract: signed
// linear depth reconstructed from hardware or linear depth, motion as previous-minus-current UV,
// linear (non-gamma) albedo and the colour before particles as the denoised base.
Texture2D<float4> inColor : register(t0);
Texture2D<float4> inDepth : register(t1);
Texture2D<float4> inMotion : register(t2);
Texture2D<float4> inNormals : register(t3);
Texture2D<float4> inRoughness : register(t4); // the normals again when roughness is packed
Texture2D<float4> inDiffuseAlbedo : register(t5);
Texture2D<float4> inSpecularAlbedo : register(t6);
Texture2D<float4> inSpecularHit : register(t7); // bound to the normals when absent
Texture2D<float4> inDiffuseHit : register(t8); // bound to the normals when absent
Texture2D<float4> inBeforeParticles : register(t9); // bound to the colour when absent
RWTexture2D<float> outLinearDepth : register(u0);
RWTexture2D<float4> outMotion : register(u1);
RWTexture2D<float4> outNormals : register(u2);
RWTexture2D<float4> outDiffuseAlbedo : register(u3);
RWTexture2D<float4> outSpecularAlbedo : register(u4);
RWTexture2D<float4> outDiffuse : register(u5);
RWTexture2D<float4> outSpecular : register(u6);
RWTexture2D<float4> outResidual : register(u7);
// Matrices are uploaded as the row-major, row-vector (v' = vM) float[16] NGX and FFX use. Read as a
// GLSL (column-major) column_major float4x4 that is the transpose, so M * v here is the row-vector product v * M.
struct FsrRrConstants
{
column_major float4x4 clipToView; // inverse(ViewToClipMatrix)
column_major float4x4 viewToPreviousView; // inverse(WorldToView) * previous WorldToView
float4 renderSize; // width, height, 1 / width, 1 / height
float4 motion; // xy: MV texel -> UV delta (MV.Scale / MV resolution); zw: MV texel per render pixel
uint4 colorDepthBase; // colour subrect origin xy, depth subrect origin xy
uint4 motionBaseFlags; // motion subrect origin xy, flags, unused
float4 depthParams; // x: sign of view z in front of the camera, y: sky depth (hardware), z: far marker
float4 detail; // x: share of the colour that bypasses the denoiser (0: none); y: smallest albedo a
// signal is divided by
};
cbuffer Constants : register(b0) { FsrRrConstants pc; }
static const uint kHardwareDepth = 1u;
static const uint kRoughnessPacked = 2u;
static const uint kSpecularHit = 4u;
static const uint kSpecularHitInW = 8u; // DLSSD.SpecularRayDirectionHitDistance: the distance is .w
static const uint kDiffuseHit = 16u;
static const uint kDiffuseHitInW = 32u;
static const uint kNormalsUnorm = 64u; // [0,1]-encoded normals (UNORM formats)
static const uint kBeforeParticles = 128u;
static const uint kInvalidNormalPassthrough = 256u;
static const float kHalfMax = 65000.0;
float3 safe(float3 v)
{
// NaN fails both comparisons and becomes 0
return float3(v.x >= 0.0 ? min(v.x, kHalfMax) : 0.0, v.y >= 0.0 ? min(v.y, kHalfMax) : 0.0,
v.z >= 0.0 ? min(v.z, kHalfMax) : 0.0);
}
// AMD's NormalToOctahedronUv (FidelityFX denoising.md 5.3.1)
float2 octahedron(float3 n)
{
n /= abs(n.x) + abs(n.y) + abs(n.z);
float2 k = float2(n.x >= 0.0 ? 1.0 : -1.0, n.y >= 0.0 ? 1.0 : -1.0);
float2 xy = n.z < 0.0 ? (1.0 - abs(n.yx)) * k : n.xy;
return xy * 0.5 + 0.5;
}
[numthreads(8, 8, 1)]
void main(uint3 tid : SV_DispatchThreadID)
{
const int2 px = int2(tid.xy);
if (px.x >= int(pc.renderSize.x) || px.y >= int(pc.renderSize.y))
return;
const uint flags = pc.motionBaseFlags.z;
const float2 uv = (float2(px) + 0.5) * pc.renderSize.zw;
const float2 ndc = float2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);
// Signed view-space position. Hardware depth goes through the inverse projection, which carries
// the game's handedness and (reversed) depth range; linear depth is the distance along the view
// axis, given its sign by the projection.
const float depth = inDepth.Load(int3(int2(pc.colorDepthBase.zw) + px, 0)).r;
float3 viewPos;
bool sky;
if ((flags & kHardwareDepth) != 0u)
{
const float4 p = mul(pc.clipToView, float4(ndc, depth, 1.0));
viewPos = p.xyz / p.w;
sky = depth == pc.depthParams.y || !(abs(p.w) > 0.0);
}
else
{
const float4 p = mul(pc.clipToView, float4(ndc, 0.5, 1.0));
const float3 ray = p.xyz / p.w;
const float z = depth * pc.depthParams.x;
viewPos = ray * (z / ray.z);
sky = !(depth > 0.0) || depth >= pc.depthParams.z;
}
const float3 color = safe(inColor.Load(int3(int2(pc.colorDepthBase.xy) + px, 0)).rgb);
const float3 base = (flags & kBeforeParticles) != 0u ? safe(inBeforeParticles.Load(int3(px, 0)).rgb) : color;
// Particles (colour after minus before) stay out of the denoiser; signed, so smoke can darken.
const float3 particles = color - base;
float3 diffuseAlbedo = clamp(inDiffuseAlbedo.Load(int3(px, 0)).rgb, 0.0, 1.0);
float3 specularAlbedo = clamp(inSpecularAlbedo.Load(int3(px, 0)).rgb, 0.0, 1.0);
// Energy above 1 is taken from the diffuse lobe first (reference: albedo overshoot clamp).
diffuseAlbedo = max(diffuseAlbedo - max(diffuseAlbedo + specularAlbedo - 1.0, 0.0), 0.0);
const float albedoSum = dot(diffuseAlbedo + specularAlbedo, ((float3)1.0));
sky = sky || !(albedoSum > 3e-3) || any(isnan(viewPos)) || any(isinf(viewPos));
const float4 normalTexel = inNormals.Load(int3(px, 0));
float3 normal = (flags & kNormalsUnorm) != 0u ? normalTexel.xyz * 2.0 - 1.0 : normalTexel.xyz;
const float normalLength = dot(normal, normal);
// A zero/invalid normal is no surface, not an invented world-Z-facing mirror. Alan Wake 2
// supplies zero normals for its sky with finite FP16-max depth, so the far bound alone misses it.
if ((flags & kInvalidNormalPassthrough) != 0u)
sky = sky || !(normalLength > 1e-8) || isnan(normalLength) || isinf(normalLength);
if (sky)
{
// Outside linearDepthBounds: the denoiser passes these texels through, and the residual carries
// the whole colour.
outLinearDepth[px] = float(pc.depthParams.z * 10.0 * pc.depthParams.x);
outMotion[px] = ((float4)0.0);
outNormals[px] = float4(0.5, 0.5, 1.0, 0.0);
outDiffuseAlbedo[px] = ((float4)0.0);
outSpecularAlbedo[px] = ((float4)0.0);
outDiffuse[px] = ((float4)0.0);
outSpecular[px] = ((float4)0.0);
outResidual[px] = float4(color, 0.0);
return;
}
outLinearDepth[px] = float(viewPos.z);
// 2.5D motion: DLSS motion vectors point from this pixel to where it was (previous - current), in
// the MV texture's pixels. The depth delta is the camera's: the previous view's z of this point
// (moving geometry is not reprojected; there is no previous depth for it).
const int2 mvTexel = int2(pc.motionBaseFlags.xy) + int2(float2(px) * pc.motion.zw);
const float2 mv = inMotion.Load(int3(mvTexel, 0)).xy * pc.motion.xy;
const float4 previous = mul(pc.viewToPreviousView, float4(viewPos, 1.0));
outMotion[px] = float4(mv, previous.z / previous.w - viewPos.z, 0.0);
normal = dot(normal, normal) > 1e-8 ? normalize(normal) : float3(0.0, 0.0, 1.0);
const float roughness = clamp((flags & kRoughnessPacked) != 0u ? normalTexel.w : inRoughness.Load(int3(px, 0)).r,
0.0, 1.0);
// material type 0 (the default material) everywhere
outNormals[px] = float4(octahedron(normal), roughness, 0.0);
outDiffuseAlbedo[px] = float4(diffuseAlbedo, 0.0);
outSpecularAlbedo[px] = float4(specularAlbedo, 0.0);
// Split by albedo and demodulate (reference "Mode 2"). Per channel, so a tinted metal keeps its tint.
const float3 total = diffuseAlbedo + specularAlbedo;
const float3 specularShare = specularAlbedo / max(total, ((float3)1e-4));
const float3 specularColor = base * specularShare;
const float3 diffuseColor = base - specularColor;
// A dark albedo would turn light it did not shape (fog, sky light, the background at a thin object's edge) into
// a huge signal that the denoiser clips (fog lost) or spreads (bright fringes): no albedo below the floor
// divides a signal, and what the division cannot carry stays in the residual.
float3 diffuse = safe(diffuseColor / max(diffuseAlbedo, ((float3)pc.detail.y)));
float3 specular = safe(specularColor / max(specularAlbedo, ((float3)pc.detail.y)));
if (pc.detail.x > 0.0)
{
// Fine detail the denoiser treats as noise (hair, water ripples): part of the colour goes around it,
// through the residual.
const float denoised = 1.0 - clamp(pc.detail.x, 0.0, 1.0);
diffuse *= denoised;
specular *= denoised;
}
const float3 remodulated = diffuse * diffuseAlbedo + specular * specularAlbedo;
// A: the ray hit distance for an indirect signal (ignored, but non-negative, for a direct one)
float specularHit = 0.0, diffuseHit = 0.0;
if ((flags & kSpecularHit) != 0u)
{
const float4 hit = inSpecularHit.Load(int3(px, 0));
specularHit = clamp((flags & kSpecularHitInW) != 0u ? hit.w : hit.x, 0.0, kHalfMax);
}
if ((flags & kDiffuseHit) != 0u)
{
const float4 hit = inDiffuseHit.Load(int3(px, 0));
diffuseHit = clamp((flags & kDiffuseHitInW) != 0u ? hit.w : hit.x, 0.0, kHalfMax);
}
outDiffuse[px] = float4(diffuse, diffuseHit);
outSpecular[px] = float4(specular, specularHit);
outResidual[px] = float4(base - remodulated + particles, 0.0);
}
+225 -103
View File
@@ -1,4 +1,6 @@
// FSR Ray Regeneration as the native Windows Ray Reconstruction backend (experimental). See fsr_rr_backend.h.
// RDNA4 records conversion, AMD denoising and composition on the game's native D3D12 device.
// The compatibility route described below remains the RDNA3 default.
//
// Data path of one evaluation (every hand-off is CPU-synchronized; no vkd3d-proton patch is needed):
// game queue : guides -> shared packet (D3D12 copies, as the native DLSS backend), signal, CPU waits
@@ -11,9 +13,12 @@
#include "../d4r_fsr_rr.h"
#include "../d4r_vkd3d_interop.h"
#include "fsr_rr_passes.h"
#include "fsr_rr_native_passes.h"
#include "fsr_rr_route.h"
#include "d3d12_command_hooks.h"
#include <volk.h>
#include "rr_bridge_replay.h"
#include "rr_input_formats.h"
#include <vulkan/vulkan_win32.h>
#include <dxgi1_4.h>
#include <chrono>
@@ -134,35 +139,9 @@ void multiply(const double (&a)[16], const double (&b)[16], double (&out)[16]) {
double sum = 0; for (int k = 0; k < 4; ++k) sum += a[r * 4 + k] * b[k * 4 + c]; out[r * 4 + c] = sum;
}
}
bool unorm(VkFormat format) {
return format == VK_FORMAT_R8G8B8A8_UNORM || format == VK_FORMAT_A2B10G10R10_UNORM_PACK32;
}
size_t resource_index(size_t plane) { return plane < 7 ? plane : plane + 2; }
// Same sampled-input formats as the native backend: the texel values reach Vulkan unchanged.
rr_bridge::Plane input_format(DXGI_FORMAT format, size_t plane) {
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 FSR RR sampled input format");
}
// Same sampled-input formats as the native backend (rr_input_formats.h).
rr_bridge::Plane input_format(DXGI_FORMAT format, size_t plane) { return guide_format(format, plane, "FSR RR"); }
DXGI_FORMAT copy_format(ID3D12Device* device, ID3D12Resource* texture, size_t plane) {
const auto desc = resource_desc(texture);
if (plane != Snapshot::Depth) return desc.Format;
@@ -213,8 +192,14 @@ FsrConfig FsrConfig::from_environment() {
config.cacheDir = env_wide("D4R_FSR_CACHE_DIR");
return config;
}
bool FsrConfig::use_native_device() {
return native_fsr_route(D4R_TARGET_ARCH, std::getenv("D4R_FSR_RR_DEVICE"));
}
struct FsrBackend::Impl {
bool nativeD3d12 = FsrConfig::use_native_device();
NativeFsrPasses nativePasses;
std::array<NativeFsrPasses::Guide, 11> nativeGuides{};
Creation creation;
rr_bridge::Layout layout;
ComPtr<ID3D12Device> d3d; // the game's device
@@ -226,6 +211,7 @@ struct FsrBackend::Impl {
Handle event;
uint64_t sequence = 0;
bool pending = false, failed = false;
std::unique_ptr<Snapshot> retained;
// --- private vkd3d-proton side ---
HMODULE core = nullptr;
ComPtr<ID3D12Device> vdev;
@@ -306,6 +292,26 @@ struct FsrBackend::Impl {
if ((creation.flags & 3u) != 3u) throw std::runtime_error("FSR RR requires HDR and render-resolution motion vectors");
if (creation.flags & 4u) throw std::runtime_error("FSR RR does not convert jittered motion vectors");
if (creation.quality < 0 || creation.quality > 5) throw std::runtime_error("Unsupported RR quality mode");
if (nativeD3d12) {
// AMD sees the game's real D3D12 device and its real driver extension.
// No vkd3d core, amdxc shim, external packet or Vulkan device is loaded.
std::fprintf(stderr, "D4R_FSR_RR_DEVICE native-d3d12 target=%s cross_api_copies=0\n", D4R_TARGET_ARCH);
vdev = d3d;
vevent.value = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if (!vevent.value) throw std::runtime_error("FSR RR native event creation failed");
dx(vdev->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&vfence)), "FSR RR native fence");
dx(vdev->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&vallocator)), "FSR RR native allocator");
dx(vdev->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, vallocator.Get(), nullptr, IID_PPV_ARGS(&vlist)), "FSR RR native list");
dx(vlist->Close(), "FSR RR native initial close");
if (!runtime.load(config.denoiserDll)) throw std::runtime_error(std::string("FSR RR denoiser DLL: ") + fsr_rr_stage_name(runtime.stage));
if (!runtime.query_providers(vdev.Get())) throw std::runtime_error(std::string("FSR RR native D3D12 provider: ") + fsr_rr_stage_name(runtime.stage));
nativePasses.initialize(vdev.Get());
create_intermediates();
create_context();
std::fprintf(stderr, "FSR_RR provider \"%s\" on native D3D12\n", runtime.providerName.c_str());
return;
}
std::fprintf(stderr, "D4R_FSR_RR_DEVICE vkd3d target=%s\n", D4R_TARGET_ARCH);
if (config.vkd3dCore.empty() || config.denoiserDll.empty() || config.amdxcShim.empty())
throw std::runtime_error("FSR RR needs D4R_FSR_VKD3D_CORE, D4R_FSR_RR_DLL and D4R_FSR_AMDXC_SHIM");
// --- the game-device packet, fence and copy lists (as the native backend) ---
@@ -337,7 +343,23 @@ struct FsrBackend::Impl {
void start_vkd3d() {
// vkd3d-proton's own environment: the dxil-spirv RDNA3 WMMA/FP8 emulation, and a cache for its shaders.
SetEnvironmentVariableA("DXIL_SPIRV_CONFIG", "wmma_rdna3_workaround");
// gfx12 uses the native FP8 matrix layout. Applying the gfx11 layout rewrite
// there also forces FP16 RTZ in dxil-spirv. Preserve explicit diagnostic overrides.
// Read the Windows environment: the C runtime's getenv snapshot does not
// reflect SetEnvironmentVariable calls made by other DLLs in this process.
DWORD configLength = GetEnvironmentVariableA("DXIL_SPIRV_CONFIG", nullptr, 0);
if (!configLength && GetLastError() == ERROR_ENVVAR_NOT_FOUND &&
std::string(D4R_TARGET_ARCH).rfind("gfx11", 0) == 0) {
if (!SetEnvironmentVariableA("DXIL_SPIRV_CONFIG", "wmma_rdna3_workaround"))
throw std::runtime_error("FSR RR shader compiler configuration failed");
configLength = GetEnvironmentVariableA("DXIL_SPIRV_CONFIG", nullptr, 0);
}
if (log_enabled()) {
std::vector<char> compilerConfig(configLength ? configLength : 1, '\0');
GetEnvironmentVariableA("DXIL_SPIRV_CONFIG", compilerConfig.data(), DWORD(compilerConfig.size()));
std::fprintf(stderr, "FSR_RR compiler: target=%s DXIL_SPIRV_CONFIG=%s\n",
D4R_TARGET_ARCH, compilerConfig[0] ? compilerConfig.data() : "<default>");
}
if (!config.cacheDir.empty() && !std::getenv("VKD3D_SHADER_CACHE_PATH")) {
CreateDirectoryW(config.cacheDir.c_str(), nullptr);
SetEnvironmentVariableW(L"VKD3D_SHADER_CACHE_PATH", config.cacheDir.c_str());
@@ -435,6 +457,7 @@ struct FsrBackend::Impl {
t.desc.SampleDesc.Count = 1; t.desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT;
dx(vdev->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &t.desc, t.state, nullptr, IID_PPV_ARGS(&t.resource)), "FSR RR intermediate texture");
if (nativeD3d12) return;
UINT64 handle = 0, offset = 0; VkFormat format = VK_FORMAT_UNDEFINED;
dx(interop->GetVulkanResourceInfo1(t.resource.Get(), &handle, &offset, &format), "FSR RR intermediate Vulkan info");
if (!handle) throw std::runtime_error("FSR RR intermediate has no Vulkan image");
@@ -522,10 +545,9 @@ struct FsrBackend::Impl {
if (superSampling && (frame.renderWidth < srRenderLimits[0] || frame.renderHeight < srRenderLimits[1] ||
frame.renderWidth > srRenderLimits[2] || frame.renderHeight > srRenderLimits[3]))
throw std::runtime_error("FSR RR render size is outside the Super Sampling dynamic render range");
if (frame.exposureScale != 1 || frame.depthType || frame.normalsType || frame.roughnessType || frame.diffuseType || frame.specularType)
throw std::runtime_error("FSR RR guide encoding or exposure scale requires conversion");
if (frame.resources[Snapshot::Disocclusion] || frame.resources[Snapshot::Subsurface])
throw std::runtime_error("FSR RR does not use the disocclusion or subsurface guides");
// Like the Linux FSR backend: the exposure scale goes to the Super Sampling stage (it receives
// frame.exposureScale), and the NGX guide encoding hints (DLSS.*.Type) are not read.
if (frame.exposureScale != 1 && !superSampling) throw std::runtime_error("FSR RR exposure scale needs the Super Sampling stage");
if (!superSampling && (frame.renderWidth != creation.outWidth || frame.renderHeight != creation.outHeight))
throw std::runtime_error("FSR RR upscaling needs a Super Sampling stage (none is installed) or output size == render size");
for (size_t i = 0; i < Snapshot::Count; ++i) {
@@ -551,7 +573,7 @@ struct FsrBackend::Impl {
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 FSR RR texture format or subrect (FSR-only output must be RGBA16F or RGBA32F)");
if (i == Snapshot::Output && !superSampling && desc.Format == DXGI_FORMAT_R32G32B32A32_FLOAT) {
if (!nativeD3d12 && i == Snapshot::Output && !superSampling && desc.Format == DXGI_FORMAT_R32G32B32A32_FLOAT) {
VkFormatProperties source{}, destination{};
api.formatProperties(vkPhysical, VK_FORMAT_R16G16B16A16_SFLOAT, &source);
api.formatProperties(vkPhysical, VK_FORMAT_R32G32B32A32_SFLOAT, &destination);
@@ -593,6 +615,14 @@ struct FsrBackend::Impl {
if (WaitForSingleObject(vevent.value, 60000) != WAIT_OBJECT_0) throw std::runtime_error("FSR RR vkd3d completion timeout");
if (vfence->GetCompletedValue() == UINT64_MAX) throw std::runtime_error("FSR RR vkd3d device removed");
}
void prepare_native_guides(const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access) {
for (size_t i : {0, 1, 2, 3, 4, 5, 6, 7, 9, 10}) {
if (!layout.planes[i].enabled) continue;
const size_t index = resource_index(i);
nativePasses.copy_guide(vlist.Get(), frame.resources[index].Get(), access[index], index == Snapshot::Depth,
layout.rw, layout.rh, creation.width, creation.height, frame.origins[index][0], frame.origins[index][1], nativeGuides[i]);
}
}
void wait_game(ID3D12CommandQueue* q, uint64_t value) {
dx(q->Signal(fence.Get(), value), "FSR RR game signal");
dx(fence->SetEventOnCompletion(value, event.value), "FSR RR game completion event");
@@ -618,6 +648,7 @@ struct FsrBackend::Impl {
void evaluate(ID3D12CommandQueue* q, const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access) {
validate(q, frame, access);
retained = std::make_unique<Snapshot>(frame);
failed = true;
const auto begin = Clock::now();
layout.rw = frame.renderWidth; layout.rh = frame.renderHeight;
@@ -634,6 +665,27 @@ struct FsrBackend::Impl {
layout.outputFormat = floatOutput ? VK_FORMAT_R32G32B32A32_SFLOAT : VK_FORMAT_R16G16B16A16_SFLOAT;
const auto outputFormat = floatOutput ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R16G16B16A16_FLOAT;
commands::InternalScope internal;
if (nativeD3d12) {
vqueue = q;
if (log_enabled() && !stamps) {
D3D12_QUERY_HEAP_DESC heap{}; heap.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP; heap.Count = 4;
if (SUCCEEDED(vdev->CreateQueryHeap(&heap, IID_PPV_ARGS(&stamps))) &&
SUCCEEDED(q->GetTimestampFrequency(&stampFrequency)) && stampFrequency)
stampReadback = make_buffer(vdev.Get(), 4 * sizeof(UINT64), D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
else stamps.Reset();
}
pending = true; queue = q;
run_vkd3d(frame, &access);
if (superSampling) {
const auto upscaleStart = Clock::now();
superSampling(q, textures[Composed].resource.Get(), frame, access);
if (log_enabled()) std::fprintf(stderr, "FSR_RR super sampling submitted in %.2f ms\n", since(upscaleStart));
}
pending = false; failed = false; queue.Reset(); retained.reset();
if (log_enabled()) std::fprintf(stderr, "FSR_RR native frame %u: %ux%u total %.2f ms\n", frameIndex, layout.rw, layout.rh, since(begin));
++frameIndex;
return;
}
// --- 1. the game's guides into the shared packet ---
dx(producerAllocator->Reset(), "FSR RR producer allocator reset"); dx(producer->Reset(producerAllocator.Get(), nullptr), "FSR RR producer reset");
transition(producer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST);
@@ -695,19 +747,20 @@ struct FsrBackend::Impl {
superSampling(q, denoised.Get(), frame, access);
if (log_enabled()) std::fprintf(stderr, "FSR_RR super sampling submitted in %.2f ms\n", since(upscaleStart));
}
pending = false; failed = false; queue.Reset();
pending = false; failed = false; queue.Reset(); retained.reset();
if (log_enabled())
std::fprintf(stderr, "FSR_RR frame %u: %ux%u produce %.2f ms, vkd3d (convert+denoise+compose) %.2f ms, total %.2f ms\n",
frameIndex, layout.rw, layout.rh, producedMs, vkd3dMs, since(begin));
++frameIndex;
}
void run_vkd3d(const Snapshot& frame) {
void run_vkd3d(const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>* access = nullptr) {
const auto& s = frame.settings;
const uint32_t rw = layout.rw, rh = layout.rh;
// which game planes exist decides the context's specular signal once
const bool specularHit = layout.planes[7].enabled;
const uint32_t wanted = FFX_DENOISER_SIGNAL_DIRECT_DIFFUSE |
// which hit distances the game passes decides the context's signals once (as on Linux): a lobe with a
// ray length is an indirect signal. Layout plane 9 is the Subsurface slot, carrying the diffuse hit.
const bool specularHit = layout.planes[7].enabled, diffuseHit = frame.diffuseHit && layout.planes[9].enabled;
const uint32_t wanted = (diffuseHit ? FFX_DENOISER_SIGNAL_INDIRECT_DIFFUSE : FFX_DENOISER_SIGNAL_DIRECT_DIFFUSE) |
(specularHit ? FFX_DENOISER_SIGNAL_INDIRECT_SPECULAR : FFX_DENOISER_SIGNAL_DIRECT_SPECULAR);
if (wanted != signals) {
if (frameIndex == 0) {
@@ -715,9 +768,13 @@ struct FsrBackend::Impl {
const ffxReturnCode_t created = fsr_rr_create_context(runtime, vdev.Get(), creation.width, creation.height, signals, 0, context);
if (created != FFX_API_RETURN_OK) throw std::runtime_error(std::string("FSR RR ffxCreateContext failed: ") + fsr_rr_return_name(created));
apply_denoiser_keys();
std::fprintf(stderr, "d4r: FSR RR diffuse %s, specular %s\n",
diffuseHit ? (frame.diffuseHitInW ? "indirect (hit distance in ray direction w)" : "indirect (hit distance)") : "direct (no hit distance)",
specularHit ? (frame.specularHitInW ? "indirect (hit distance in ray direction w)" : "indirect (hit distance)") : "direct (no hit distance)");
} else throw std::runtime_error("FSR RR: the game's hit-distance guide changed after the denoiser was created (recreate the feature)");
}
for (size_t plane : {0, 1, 2, 3, 4, 5, 6, 7, 10}) if (layout.planes[plane].enabled) ensure_staging(plane);
if (!nativeD3d12)
for (size_t plane : {0, 1, 2, 3, 4, 5, 6, 7, 9, 10}) if (layout.planes[plane].enabled) ensure_staging(plane);
double clipToView[16], viewToWorld[16];
if (!invert(s.viewToClip, clipToView) || !invert(s.worldToView, viewToWorld)) throw std::runtime_error("FSR RR camera matrix is not invertible");
const bool reset = s.reset || !havePrevious;
@@ -731,8 +788,9 @@ struct FsrBackend::Impl {
uint32_t flags = 0;
if (s.hardwareDepth) flags |= kFsrRrHardwareDepth;
if (s.roughnessPacked) flags |= kFsrRrRoughnessPacked;
if (specularHit) flags |= kFsrRrSpecularHit;
if (unorm(layout.planes[3].format)) flags |= kFsrRrNormalsUnorm;
if (specularHit) flags |= kFsrRrSpecularHit | (frame.specularHitInW ? kFsrRrSpecularHitInW : 0u);
if (diffuseHit) flags |= kFsrRrDiffuseHit | (frame.diffuseHitInW ? kFsrRrDiffuseHitInW : 0u);
if (unorm_guide(layout.planes[3].format)) flags |= kFsrRrNormalsUnorm;
if (layout.planes[10].enabled) flags |= kFsrRrBeforeParticles;
flags |= kFsrRrInvalidNormalPassthrough;
c.motionBaseFlags[2] = flags;
@@ -748,39 +806,54 @@ struct FsrBackend::Impl {
if (t.resource) { transition(vlist.Get(), t.resource.Get(), t.state, state); t.state = state; }
};
for (int i = 0; i < TexCount; ++i) setState(i, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
VkCommandBuffer command = VK_NULL_HANDLE;
dx(interop->BeginVkCommandBufferInterop(vlist.Get(), &command), "FSR RR Vulkan interop begin");
// the packet comes in from the game's device
VkBufferMemoryBarrier acquire{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; acquire.buffer = packetBuffer; acquire.size = VK_WHOLE_SIZE;
acquire.srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL; acquire.dstQueueFamilyIndex = vkFamily;
acquire.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
api.barrier(command, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &acquire, 0, nullptr);
for (size_t plane : {0, 1, 2, 3, 4, 5, 6, 7, 10}) {
if (!layout.planes[plane].enabled) continue;
const auto& st = staging[plane];
VkImageMemoryBarrier toCopy{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; toCopy.image = st.image;
toCopy.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; toCopy.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
toCopy.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; toCopy.srcQueueFamilyIndex = toCopy.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toCopy.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
api.barrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toCopy);
VkBufferImageCopy region{}; region.bufferOffset = layout.planes[plane].offset;
region.bufferRowLength = uint32_t(layout.planes[plane].pitch / layout.planes[plane].bytes);
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; region.imageExtent = {rw, rh, 1};
api.copyBufferToImage(command, packetBuffer, st.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
VkImageMemoryBarrier toRead = toCopy; toRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; toRead.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
toRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; toRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
api.barrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toRead);
}
auto sampled = [&](size_t plane) { return D4rFsrRrImage{staging[plane].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; };
const bool hasHit = layout.planes[7].enabled, hasParticles = layout.planes[10].enabled;
D4rFsrRrImage images[D4rFsrRrPasses::kPrepareInputs + D4rFsrRrPasses::kPrepareOutputs] = {
sampled(0), sampled(2), sampled(1), sampled(3), sampled(4), sampled(5), sampled(6),
hasHit ? sampled(7) : sampled(3), sampled(3), hasParticles ? sampled(10) : sampled(0)};
static const int targets[D4rFsrRrPasses::kPrepareOutputs] = {LinearDepth, MotionT, NormalsT, DiffuseAlbedo, SpecularAlbedo, DiffuseT, SpecularT, Residual};
for (uint32_t i = 0; i < D4rFsrRrPasses::kPrepareOutputs; ++i)
images[D4rFsrRrPasses::kPrepareInputs + i] = {textures[targets[i]].view, textures[targets[i]].layout};
passes.record_prepare(command, images, c, rw, rh);
dx(interop->EndVkCommandBufferInterop(vlist.Get()), "FSR RR Vulkan interop end");
VkCommandBuffer command = VK_NULL_HANDLE;
VkBufferMemoryBarrier acquire{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER};
if (nativeD3d12) {
prepare_native_guides(frame, *access);
ID3D12Resource* resources[18]{}; DXGI_FORMAT formats[18]{};
const size_t planes[10] = {0, 2, 1, 3, 4, 5, 6, specularHit ? 7u : 3u,
diffuseHit ? 9u : 3u, layout.planes[10].enabled ? 10u : 0u};
for (size_t i = 0; i < 10; ++i) {
resources[i] = nativeGuides[planes[i]].resource.Get(); formats[i] = nativeGuides[planes[i]].format;
}
for (size_t i = 0; i < 8; ++i) {
resources[10 + i] = textures[targets[i]].resource.Get(); formats[10 + i] = textures[targets[i]].desc.Format;
}
nativePasses.record(vlist.Get(), nativePasses.prepare, &c, resources, formats, rw, rh);
} else {
dx(interop->BeginVkCommandBufferInterop(vlist.Get(), &command), "FSR RR Vulkan interop begin");
// the packet comes in from the game's device
acquire.buffer = packetBuffer; acquire.size = VK_WHOLE_SIZE;
acquire.srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL; acquire.dstQueueFamilyIndex = vkFamily;
acquire.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
api.barrier(command, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &acquire, 0, nullptr);
for (size_t plane : {0, 1, 2, 3, 4, 5, 6, 7, 9, 10}) {
if (!layout.planes[plane].enabled) continue;
const auto& st = staging[plane];
VkImageMemoryBarrier toCopy{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; toCopy.image = st.image;
toCopy.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; toCopy.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
toCopy.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; toCopy.srcQueueFamilyIndex = toCopy.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toCopy.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
api.barrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toCopy);
VkBufferImageCopy region{}; region.bufferOffset = layout.planes[plane].offset;
region.bufferRowLength = uint32_t(layout.planes[plane].pitch / layout.planes[plane].bytes);
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; region.imageExtent = {rw, rh, 1};
api.copyBufferToImage(command, packetBuffer, st.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
VkImageMemoryBarrier toRead = toCopy; toRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; toRead.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
toRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; toRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
api.barrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toRead);
}
auto sampled = [&](size_t plane) { return D4rFsrRrImage{staging[plane].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; };
const bool hasHit = layout.planes[7].enabled, hasParticles = layout.planes[10].enabled;
D4rFsrRrImage images[D4rFsrRrPasses::kPrepareInputs + D4rFsrRrPasses::kPrepareOutputs] = {
sampled(0), sampled(2), sampled(1), sampled(3), sampled(4), sampled(5), sampled(6),
hasHit ? sampled(7) : sampled(3), diffuseHit ? sampled(9) : sampled(3), hasParticles ? sampled(10) : sampled(0)};
for (uint32_t i = 0; i < D4rFsrRrPasses::kPrepareOutputs; ++i)
images[D4rFsrRrPasses::kPrepareInputs + i] = {textures[targets[i]].view, textures[targets[i]].layout};
passes.record_prepare(command, images, c, rw, rh);
dx(interop->EndVkCommandBufferInterop(vlist.Get()), "FSR RR Vulkan interop end");
}
stamp(1);
// --- AMD FSR Ray Regeneration (D3D12 on vkd3d-proton) ---
@@ -811,9 +884,13 @@ struct FsrBackend::Impl {
desc.frameIndex = frameIndex;
desc.flags = uint32_t(FFX_DENOISER_DISPATCH_NON_GAMMA_ALBEDO) | (reset ? uint32_t(FFX_DENOISER_DISPATCH_RESET) : 0u);
ffxDispatchDescDenoiserDirectDiffuse directDiffuse = {}; ffxDispatchDescDenoiserDirectSpecular directSpecular = {};
ffxDispatchDescDenoiserIndirectSpecular indirectSpecular = {};
directDiffuse.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_DIFFUSE;
FfxApiDenoiserSignal* diffuseSignal = &directDiffuse.signal;
ffxDispatchDescDenoiserIndirectDiffuse indirectDiffuse = {}; ffxDispatchDescDenoiserIndirectSpecular indirectSpecular = {};
FfxApiDenoiserSignal* diffuseSignal; ffxApiHeader* diffuseHeader;
if (diffuseHit) {
indirectDiffuse.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_DIFFUSE; diffuseSignal = &indirectDiffuse.signal; diffuseHeader = &indirectDiffuse.header;
} else {
directDiffuse.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_DIFFUSE; diffuseSignal = &directDiffuse.signal; diffuseHeader = &directDiffuse.header;
}
FfxApiDenoiserSignal* specularSignal; ffxApiHeader* specularHeader;
if (specularHit) {
indirectSpecular.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_SPECULAR; specularSignal = &indirectSpecular.signal; specularHeader = &indirectSpecular.header;
@@ -822,7 +899,7 @@ struct FsrBackend::Impl {
}
diffuseSignal->input = ffx(DiffuseT, ffxRead); diffuseSignal->output = ffx(DiffuseOut, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS);
specularSignal->input = ffx(SpecularT, ffxRead); specularSignal->output = ffx(SpecularOut, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS);
desc.header.pNext = &directDiffuse.header; directDiffuse.header.pNext = specularHeader;
desc.header.pNext = diffuseHeader; diffuseHeader->pNext = specularHeader;
const ffxReturnCode_t dispatched = runtime.dispatch(&context, &desc.header);
if (dispatched != FFX_API_RETURN_OK) throw std::runtime_error(std::string("FSR RR ffxDispatch failed: ") + fsr_rr_return_name(dispatched));
D3D12_RESOURCE_BARRIER uav{}; uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; vlist->ResourceBarrier(1, &uav);
@@ -830,30 +907,62 @@ struct FsrBackend::Impl {
// --- composition, then the composed image into the packet's output region ---
for (int i = 0; i < TexCount; ++i) setState(i, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
const D4rFsrRrImage compose[D4rFsrRrPasses::kComposeImages] = {
{textures[DiffuseOut].view, textures[DiffuseOut].layout}, {textures[SpecularOut].view, textures[SpecularOut].layout},
{textures[DiffuseAlbedo].view, textures[DiffuseAlbedo].layout}, {textures[SpecularAlbedo].view, textures[SpecularAlbedo].layout},
{textures[Residual].view, textures[Residual].layout}, {textures[Composed].view, textures[Composed].layout},
{textures[DiffuseT].view, textures[DiffuseT].layout}, {textures[SpecularT].view, textures[SpecularT].layout}};
dx(interop->BeginVkCommandBufferInterop(vlist.Get(), &command), "FSR RR Vulkan interop begin (compose)");
passes.record_compose(command, compose, rw, rh, env_float("D4R_FSR_RR_HALO_LIMIT", 1.0f, 0.0f, 1000.0f));
int copiedImage = Composed;
if (layout.outputBytes == 16) {
// Preserve the denoiser/composition's half arithmetic; widen only the completed output on the GPU.
VkImageBlit region{}; region.srcSubresource = region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.srcOffsets[1] = region.dstOffsets[1] = {int32_t(rw), int32_t(rh), 1};
api.blitImage(command, textures[Composed].image, textures[Composed].layout,
textures[Widened].image, textures[Widened].layout, 1, &region, VK_FILTER_NEAREST);
passes.full_barrier(command);
copiedImage = Widened;
if (nativeD3d12) {
ID3D12Resource* resources[8]{}; DXGI_FORMAT formats[8]{};
const int indices[8] = {DiffuseOut, SpecularOut, DiffuseAlbedo, SpecularAlbedo, Residual, Composed, DiffuseT, SpecularT};
for (size_t i = 0; i < 8; ++i) {
resources[i] = textures[indices[i]].resource.Get(); formats[i] = textures[indices[i]].desc.Format;
}
struct { uint32_t width, height; float haloLimit; uint32_t unused; } composeConstants =
{rw, rh, env_float("D4R_FSR_RR_HALO_LIMIT", 1.0f, 0.0f, 1000.0f), 0};
nativePasses.record(vlist.Get(), nativePasses.compose, &composeConstants, resources, formats, rw, rh);
int copiedImage = Composed;
if (layout.outputBytes == 16) {
setState(Composed, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
ID3D12Resource* widening[] = {textures[Composed].resource.Get(), textures[Widened].resource.Get()};
DXGI_FORMAT wideningFormats[] = {textures[Composed].desc.Format, textures[Widened].desc.Format};
const uint32_t extent[2] = {rw, rh};
nativePasses.record(vlist.Get(), nativePasses.widen, extent, widening, wideningFormats, rw, rh);
copiedImage = Widened;
}
if (superSampling) {
setState(Composed, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
} else {
setState(copiedImage, D3D12_RESOURCE_STATE_COPY_SOURCE);
auto* output = frame.resources[Snapshot::Output].Get();
texture_copy_barrier(vlist.Get(), output, (*access)[Snapshot::Output], true, false);
auto source = rr_bridge::D3D12Frames::texture(textures[copiedImage].resource.Get());
auto destination = rr_bridge::D3D12Frames::texture(output);
D3D12_BOX box{0, 0, 0, rw, rh, 1};
vlist->CopyTextureRegion(&destination, frame.origins[Snapshot::Output][0], frame.origins[Snapshot::Output][1], 0, &source, &box);
texture_copy_barrier(vlist.Get(), output, (*access)[Snapshot::Output], true, true);
}
} else {
const D4rFsrRrImage compose[D4rFsrRrPasses::kComposeImages] = {
{textures[DiffuseOut].view, textures[DiffuseOut].layout}, {textures[SpecularOut].view, textures[SpecularOut].layout},
{textures[DiffuseAlbedo].view, textures[DiffuseAlbedo].layout}, {textures[SpecularAlbedo].view, textures[SpecularAlbedo].layout},
{textures[Residual].view, textures[Residual].layout}, {textures[Composed].view, textures[Composed].layout},
{textures[DiffuseT].view, textures[DiffuseT].layout}, {textures[SpecularT].view, textures[SpecularT].layout}};
dx(interop->BeginVkCommandBufferInterop(vlist.Get(), &command), "FSR RR Vulkan interop begin (compose)");
passes.record_compose(command, compose, rw, rh, env_float("D4R_FSR_RR_HALO_LIMIT", 1.0f, 0.0f, 1000.0f));
int copiedImage = Composed;
if (layout.outputBytes == 16) {
// Preserve the denoiser/composition's half arithmetic; widen only the completed output on the GPU.
VkImageBlit region{}; region.srcSubresource = region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.srcOffsets[1] = region.dstOffsets[1] = {int32_t(rw), int32_t(rh), 1};
api.blitImage(command, textures[Composed].image, textures[Composed].layout,
textures[Widened].image, textures[Widened].layout, 1, &region, VK_FILTER_NEAREST);
passes.full_barrier(command);
copiedImage = Widened;
}
VkBufferImageCopy back{}; back.bufferOffset = layout.outputOffset; back.bufferRowLength = uint32_t(layout.outputPitch / layout.outputBytes);
back.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; back.imageExtent = {rw, rh, 1};
api.copyImageToBuffer(command, textures[copiedImage].image, textures[copiedImage].layout, packetBuffer, 1, &back);
VkBufferMemoryBarrier release = acquire; release.srcQueueFamilyIndex = vkFamily; release.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL;
release.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; release.dstAccessMask = 0;
api.barrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 1, &release, 0, nullptr);
dx(interop->EndVkCommandBufferInterop(vlist.Get()), "FSR RR Vulkan interop end (compose)");
}
VkBufferImageCopy back{}; back.bufferOffset = layout.outputOffset; back.bufferRowLength = uint32_t(layout.outputPitch / layout.outputBytes);
back.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; back.imageExtent = {rw, rh, 1};
api.copyImageToBuffer(command, textures[copiedImage].image, textures[copiedImage].layout, packetBuffer, 1, &back);
VkBufferMemoryBarrier release = acquire; release.srcQueueFamilyIndex = vkFamily; release.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL;
release.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; release.dstAccessMask = 0;
api.barrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 1, &release, 0, nullptr);
dx(interop->EndVkCommandBufferInterop(vlist.Get()), "FSR RR Vulkan interop end (compose)");
// Diagnostic (D4R_FSR_RR_DUMP_DIR, first D4R_FSR_RR_DUMP_FRAMES frames, default 3): raw copies of the converted
// linear depth and motion textures, to check the DLSS -> FFX guide conversion numerically.
stamp(3);
@@ -917,13 +1026,26 @@ FsrBackend::~FsrBackend() {
(void)impl.release();
} else impl.reset();
}
namespace {
// AMD's denoiser has no disocclusion or subsurface inputs. Games pass them anyway (Cyberpunk 2077 sends
// both every frame), so they are dropped rather than failing the frame. The shim reuses the Subsurface
// slot for the diffuse hit distance (frame.diffuseHit); a real subsurface guide is dropped here.
Snapshot without_unused_guides(const Snapshot& frame) {
Snapshot used = frame;
used.resources[Snapshot::Disocclusion].Reset();
if (!used.diffuseHit) used.resources[Snapshot::Subsurface].Reset();
return used;
}
}
void FsrBackend::evaluate(ID3D12CommandQueue* queue, const Snapshot& frame, const std::array<ResourceAccess, Snapshot::Count>& access) {
std::lock_guard<std::mutex> lock(hostMutex); impl->evaluate(queue, frame, access);
std::lock_guard<std::mutex> lock(hostMutex); impl->evaluate(queue, without_unused_guides(frame), access);
}
void FsrBackend::set_super_sampling(SuperSampling stage, const std::array<unsigned, 4>& renderLimits) {
std::lock_guard<std::mutex> lock(hostMutex); impl->superSampling = std::move(stage); impl->srRenderLimits = renderLimits;
}
void FsrBackend::validate_frame(const Snapshot& frame) { std::lock_guard<std::mutex> lock(hostMutex); impl->validate_frame(frame); }
void FsrBackend::validate_frame(const Snapshot& frame) {
std::lock_guard<std::mutex> lock(hostMutex); impl->validate_frame(without_unused_guides(frame));
}
void FsrBackend::drain() {
std::lock_guard<std::mutex> lock(hostMutex);
if (impl->pending || impl->failed) throw std::runtime_error("FSR RR backend cannot retire failed GPU work");
+4 -1
View File
@@ -7,18 +7,21 @@
namespace d4r::win::rr {
// Where the experimental FSR Ray Regeneration backend finds its pieces. All paths are explicit: nothing is
// searched for, and AMD's amdxc64.dll is never loaded.
// searched for. The compatibility route uses d4r's amdxc64 shim; native D3D12 uses AMD's real driver.
struct FsrConfig {
std::wstring vkd3dCore; // d4r's vkd3d-proton d3d12core.dll, renamed so it cannot collide with the system's
std::wstring denoiserDll; // AMD's amd_fidelityfx_denoiser_dx12.dll (FidelityFX SDK 2.3), unmodified
std::wstring amdxcShim; // d4r's amdxc64 shim (tools/windows/amdxc64_shim)
std::wstring cacheDir; // vkd3d-proton's shader cache: the denoiser's shaders are compiled once
static FsrConfig from_environment();
static bool use_native_device();
};
// FSR Ray Regeneration as the Ray Reconstruction backend on native Windows. The game's guides cross to a private
// vkd3d-proton device (D3D12 over Vulkan, dxil-spirv FP8 emulation) through the same shared packet the native
// DLSS backend uses; AMD's denoiser runs there. Evaluation is synchronous, as the native backend's is.
// That is the RDNA3 compatibility route. RDNA4 instead records all three passes on the game's
// D3D12 device and queue, with no Vulkan device or cross-API packet.
// The denoiser works at render resolution. When the output is larger (or a Super Sampling stage is wanted at all),
// the owner installs one with set_super_sampling(): the denoised colour is then copied into a texture on the game's
// device and handed to that callback together with the frame, which must record/submit the upscale into the game's
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include <cstdint>
struct D4rFsrRrPrepareConstants
{
float clipToView[16];
float viewToPreviousView[16];
float renderSize[4];
float motion[4];
uint32_t colorDepthBase[4];
uint32_t motionBaseFlags[4];
float depthParams[4];
float detail[4];
};
static_assert(sizeof(D4rFsrRrPrepareConstants) == 224, "fsr_rr_prepare.comp push constants");
enum D4rFsrRrPrepareFlags : uint32_t
{
kFsrRrHardwareDepth = 1u,
kFsrRrRoughnessPacked = 2u,
kFsrRrSpecularHit = 4u,
kFsrRrSpecularHitInW = 8u,
kFsrRrDiffuseHit = 16u,
kFsrRrDiffuseHitInW = 32u,
kFsrRrNormalsUnorm = 64u,
kFsrRrBeforeParticles = 128u,
kFsrRrInvalidNormalPassthrough = 256u,
};
+160
View File
@@ -0,0 +1,160 @@
#pragma once
#include "d3d12_external.h"
#include "fsr_rr_constants.h"
#include "fsr_rr_shaders_hlsl.generated.h"
#include <d3dcompiler.h>
#include <utility>
namespace d4r::win::rr {
// All descriptors belong to one synchronous evaluation. The owner retires the
// submission before rewriting this heap or releasing any referenced resource.
struct NativeFsrPasses {
struct Guide {
ComPtr<ID3D12Resource> resource;
DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN;
};
struct Pipeline {
ComPtr<ID3D12RootSignature> root;
ComPtr<ID3D12PipelineState> state;
UINT constants = 0, srvs = 0, uavs = 0, offset = 0;
} prepare, compose, widen;
ComPtr<ID3D12Device> device;
ComPtr<ID3D12DescriptorHeap> heap;
UINT stride = 0;
D3D12_CPU_DESCRIPTOR_HANDLE cpu{};
D3D12_GPU_DESCRIPTOR_HANDLE gpu{};
void pipeline(Pipeline& p, const char* source, UINT constants, UINT srvs, UINT uavs, UINT offset) {
p.constants = constants; p.srvs = srvs; p.uavs = uavs; p.offset = offset;
// Load the OS compiler by absolute system search, never from the game directory.
static HMODULE compiler = LoadLibraryExW(L"d3dcompiler_47.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
auto compile = compiler ? reinterpret_cast<decltype(&D3DCompile)>(GetProcAddress(compiler, "D3DCompile")) : nullptr;
if (!compile) throw std::runtime_error("Native FSR RR requires Windows d3dcompiler_47.dll");
ComPtr<ID3DBlob> code, errors;
HRESULT result = compile(source, std::strlen(source), "d4r-fsr-rr", nullptr, nullptr, "main", "cs_5_1",
D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_IEEE_STRICTNESS, 0, &code, &errors);
if (FAILED(result)) throw std::runtime_error(std::string("FSR RR native shader: ") +
(errors ? static_cast<const char*>(errors->GetBufferPointer()) : "compilation failed"));
D3D12_DESCRIPTOR_RANGE ranges[2]{};
D3D12_ROOT_PARAMETER params[3]{};
params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
params[0].Constants = {0, 0, constants};
UINT count = 1, range = 0;
for (auto binding : {std::pair<D3D12_DESCRIPTOR_RANGE_TYPE, UINT>{D3D12_DESCRIPTOR_RANGE_TYPE_SRV, srvs},
{D3D12_DESCRIPTOR_RANGE_TYPE_UAV, uavs}}) {
if (!binding.second) continue;
ranges[range] = {binding.first, binding.second, 0, 0, D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND};
params[count].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
params[count].DescriptorTable = {1, &ranges[range]};
++count; ++range;
}
D3D12_ROOT_SIGNATURE_DESC desc{}; desc.NumParameters = count; desc.pParameters = params;
ComPtr<ID3DBlob> serialized;
dx(D3D12SerializeRootSignature(&desc, D3D_ROOT_SIGNATURE_VERSION_1, &serialized, &errors), "FSR RR root serialization");
dx(device->CreateRootSignature(0, serialized->GetBufferPointer(), serialized->GetBufferSize(), IID_PPV_ARGS(&p.root)), "FSR RR root signature");
D3D12_COMPUTE_PIPELINE_STATE_DESC state{}; state.pRootSignature = p.root.Get();
state.CS = {code->GetBufferPointer(), code->GetBufferSize()};
dx(device->CreateComputePipelineState(&state, IID_PPV_ARGS(&p.state)), "FSR RR native pipeline");
}
void initialize(ID3D12Device* owner) {
device = owner;
for (auto format : {DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT}) {
D3D12_FEATURE_DATA_FORMAT_SUPPORT support{}; support.Format = format;
dx(device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT, &support, sizeof(support)), "FSR RR native format support");
const UINT wanted = D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD | D3D12_FORMAT_SUPPORT2_UAV_TYPED_STORE;
if ((support.Support2 & wanted) != wanted) throw std::runtime_error("FSR RR native typed UAV formats are unsupported");
}
D3D12_DESCRIPTOR_HEAP_DESC desc{}; desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
desc.NumDescriptors = 28; desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
dx(device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&heap)), "FSR RR native descriptor heap");
stride = device->GetDescriptorHandleIncrementSize(desc.Type);
#if defined(__MINGW32__)
heap->GetCPUDescriptorHandleForHeapStart(&cpu); heap->GetGPUDescriptorHandleForHeapStart(&gpu);
#else
cpu = heap->GetCPUDescriptorHandleForHeapStart(); gpu = heap->GetGPUDescriptorHandleForHeapStart();
#endif
pipeline(prepare, d4r_hlsl_fsr_rr_prepare, sizeof(D4rFsrRrPrepareConstants) / 4, 10, 8, 0);
pipeline(compose, d4r_hlsl_fsr_rr_compose, 4, 0, 8, 18);
const char* widening = R"HLSL(
Texture2D<float4> source : register(t0);
RWTexture2D<float4> destination : register(u0);
cbuffer Constants : register(b0) { uint2 extent; }
[numthreads(8,8,1)] void main(uint3 p : SV_DispatchThreadID) {
if (all(p.xy < extent)) destination[p.xy] = source.Load(int3(p.xy, 0));
})HLSL";
pipeline(widen, widening, 2, 1, 1, 26);
}
static DXGI_FORMAT guide_format(DXGI_FORMAT format, bool depth) {
if (depth) switch (format) {
case DXGI_FORMAT_R32G8X24_TYPELESS: case DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS: return DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;
case DXGI_FORMAT_R32_TYPELESS: case DXGI_FORMAT_D32_FLOAT: return DXGI_FORMAT_R32_FLOAT;
case DXGI_FORMAT_R16_TYPELESS: case DXGI_FORMAT_D16_UNORM: return DXGI_FORMAT_R16_UNORM;
default: return format;
}
switch (format) {
case DXGI_FORMAT_R8_TYPELESS: return DXGI_FORMAT_R8_UNORM;
case DXGI_FORMAT_R8G8_TYPELESS: return DXGI_FORMAT_R8G8_UNORM;
case DXGI_FORMAT_R8G8B8A8_TYPELESS: case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: return DXGI_FORMAT_R8G8B8A8_UNORM;
case DXGI_FORMAT_B8G8R8A8_TYPELESS: case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: return DXGI_FORMAT_B8G8R8A8_UNORM;
case DXGI_FORMAT_R16_TYPELESS: return DXGI_FORMAT_R16_FLOAT;
case DXGI_FORMAT_R16G16_TYPELESS: return DXGI_FORMAT_R16G16_FLOAT;
case DXGI_FORMAT_R16G16B16A16_TYPELESS: return DXGI_FORMAT_R16G16B16A16_FLOAT;
case DXGI_FORMAT_R32_TYPELESS: return DXGI_FORMAT_R32_FLOAT;
case DXGI_FORMAT_R32G32_TYPELESS: return DXGI_FORMAT_R32G32_FLOAT;
case DXGI_FORMAT_R32G32B32A32_TYPELESS: return DXGI_FORMAT_R32G32B32A32_FLOAT;
default: return format;
}
}
void copy_guide(ID3D12GraphicsCommandList* list, ID3D12Resource* source, const ResourceAccess& access,
bool depth, UINT width, UINT height, UINT maxWidth, UINT maxHeight, UINT x, UINT y, Guide& guide) {
// Copy into typed, shader-readable textures on the same device. This also
// supports game depth resources created with DENY_SHADER_RESOURCE.
const auto desc = resource_desc(source);
const auto format = guide_format(desc.Format, depth);
if (!guide.resource || guide.format != format) {
guide.resource.Reset(); guide.format = format;
D3D12_RESOURCE_DESC target{}; target.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
target.Width = maxWidth; target.Height = maxHeight; target.DepthOrArraySize = target.MipLevels = 1;
target.Format = format == DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS ? DXGI_FORMAT_R32G8X24_TYPELESS : format;
target.SampleDesc.Count = 1;
D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT;
dx(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &target,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, nullptr, IID_PPV_ARGS(&guide.resource)), "FSR RR native guide texture");
}
transition(list, guide.resource.Get(), D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COPY_DEST);
texture_copy_barrier(list, source, access, false, false);
D3D12_TEXTURE_COPY_LOCATION src{}, dst{};
src.pResource = source; src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
dst.pResource = guide.resource.Get(); dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
D3D12_BOX box{x, y, 0, x + width, y + height, 1};
list->CopyTextureRegion(&dst, 0, 0, 0, &src, (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) ? nullptr : &box);
texture_copy_barrier(list, source, access, false, true);
transition(list, guide.resource.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
}
void record(ID3D12GraphicsCommandList* list, const Pipeline& p, const void* constants,
ID3D12Resource* const* resources, const DXGI_FORMAT* formats, UINT width, UINT height) {
for (UINT i = 0; i < p.srvs + p.uavs; ++i) {
auto handle = cpu; handle.ptr += SIZE_T(p.offset + i) * stride;
if (i < p.srvs) {
D3D12_SHADER_RESOURCE_VIEW_DESC view{}; view.Format = formats[i];
view.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
view.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; view.Texture2D.MipLevels = 1;
device->CreateShaderResourceView(resources[i], &view, handle);
} else {
D3D12_UNORDERED_ACCESS_VIEW_DESC view{}; view.Format = formats[i]; view.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
device->CreateUnorderedAccessView(resources[i], nullptr, &view, handle);
}
}
ID3D12DescriptorHeap* heaps[] = {heap.Get()}; list->SetDescriptorHeaps(1, heaps);
list->SetComputeRootSignature(p.root.Get()); list->SetPipelineState(p.state.Get());
list->SetComputeRoot32BitConstants(0, p.constants, constants, 0);
UINT table = 1; auto handle = gpu; handle.ptr += UINT64(p.offset) * stride;
if (p.srvs) { list->SetComputeRootDescriptorTable(table++, handle); handle.ptr += UINT64(p.srvs) * stride; }
list->SetComputeRootDescriptorTable(table, handle);
list->Dispatch((width + 7) / 8, (height + 7) / 8, 1);
D3D12_RESOURCE_BARRIER barrier{}; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
list->ResourceBarrier(1, &barrier);
}
};
}
+195
View File
@@ -0,0 +1,195 @@
// Exercises native D3D12 guide conversion, composition and exact half widening.
// AMD's denoiser is deliberately absent: this tests our adapter on RDNA3 or WARP.
#include "fsr_rr_native_passes.h"
#include "fsr_rr_route.h"
#include "binary16.h"
#include <array>
#include <vector>
#include <cmath>
#include <cstdio>
using namespace d4r::win;
using namespace d4r::win::rr;
namespace diag = d4r::diag;
int main(int argc, char** argv) try {
if (native_fsr_route("gfx1101") || !native_fsr_route("gfx1200") || !native_fsr_route("gfx1201") ||
native_fsr_route("gfx1201", "vkd3d") || !native_fsr_route("gfx1101", "native"))
throw std::runtime_error("FSR route selection failed");
bool rejected = false;
try { native_fsr_route("gfx1201", "invalid"); } catch (...) { rejected = true; }
if (!rejected) throw std::runtime_error("Invalid FSR route accepted");
ComPtr<IDXGIFactory4> factory; dx(CreateDXGIFactory1(IID_PPV_ARGS(&factory)), "Factory");
ComPtr<IDXGIAdapter1> adapter;
auto option = [&](const char* name) { for (int i = 1; i < argc; ++i) if (std::string(argv[i]) == name) return true; return false; };
const bool warp = option("--warp"), sky = option("--sky"), depthStencil = option("--depth-stencil");
const bool hardwareDepth = option("--hardware-depth") || depthStencil, subrect = option("--subrect");
if (depthStencil && subrect) throw std::runtime_error("Use a full depth-stencil rectangle");
if (warp) dx(factory->EnumWarpAdapter(IID_PPV_ARGS(&adapter)), "WARP");
else {
for (UINT i = 0;; ++i) {
ComPtr<IDXGIAdapter1> candidate;
if (factory->EnumAdapters1(i, &candidate) == DXGI_ERROR_NOT_FOUND) break;
DXGI_ADAPTER_DESC1 desc{}; candidate->GetDesc1(&desc);
if (!(desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)) { adapter = candidate; break; }
}
}
if (!adapter) throw std::runtime_error("No test adapter");
ComPtr<ID3D12Device> device; dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device)), "Device");
NativeFsrPasses passes; passes.initialize(device.Get());
ComPtr<ID3D12CommandQueue> queue; D3D12_COMMAND_QUEUE_DESC qd{};
dx(device->CreateCommandQueue(&qd, IID_PPV_ARGS(&queue)), "Queue");
ComPtr<ID3D12CommandAllocator> allocator;
dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&allocator)), "Allocator");
ComPtr<ID3D12GraphicsCommandList> list;
dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator.Get(), nullptr, IID_PPV_ARGS(&list)), "List");
constexpr UINT width = 65, height = 67;
auto texture = [&](DXGI_FORMAT format, D3D12_RESOURCE_STATES state, bool uav, UINT tw = 65, UINT th = 67) {
D3D12_RESOURCE_DESC desc{}; desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
desc.Width = tw; desc.Height = th; desc.DepthOrArraySize = desc.MipLevels = 1;
desc.Format = format; desc.SampleDesc.Count = 1;
if (uav) desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
if (format == DXGI_FORMAT_D32_FLOAT_S8X24_UINT)
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL | D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE;
D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT;
ComPtr<ID3D12Resource> result;
dx(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, state, nullptr, IID_PPV_ARGS(&result)), "Texture");
return result;
};
auto desc_of = [](ID3D12Resource* resource) {
D3D12_RESOURCE_DESC desc{};
#if defined(__MINGW32__)
resource->GetDesc(&desc);
#else
desc = resource->GetDesc();
#endif
return desc;
};
auto location = [](ID3D12Resource* resource) {
D3D12_TEXTURE_COPY_LOCATION result{}; result.pResource = resource;
result.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; return result;
};
std::array<ComPtr<ID3D12Resource>, 10> inputs;
std::vector<ComPtr<ID3D12Resource>> uploads;
float values[10][4] = {{2,1,.5f,1}, {2,0,0,0}, {.25f,-.5f,0,0}, {0,0,1,.75f}, {.5f,0,0,0},
{.5f,.5f,.5f,0}, {.25f,.25f,.25f,0}, {3,0,0,0}, {5,0,0,0}, {1,.5f,.25f,1}};
if (sky) values[3][2] = 0;
if (hardwareDepth) values[1][0] = .25f;
for (size_t i = 0; i < inputs.size(); ++i) {
const bool depth = depthStencil && i == 1;
inputs[i] = texture(depth ? DXGI_FORMAT_D32_FLOAT_S8X24_UINT : DXGI_FORMAT_R32G32B32A32_FLOAT,
D3D12_RESOURCE_STATE_COPY_DEST, false, width + (subrect ? 6 : 0), height + (subrect ? 8 : 0));
auto desc = desc_of(inputs[i].Get());
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint{}; UINT64 bytes = 0;
device->GetCopyableFootprints(&desc, 0, 1, 0, &footprint, nullptr, nullptr, &bytes);
auto upload = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ);
void* mapped = nullptr; D3D12_RANGE empty{}; dx(upload->Map(0, &empty, &mapped), "Upload map");
const float padding[4] = {-10,-10,-10,-10};
const UINT bytesPerTexel = depth ? 4 : 16;
for (UINT y = 0; y < desc.Height; ++y) for (UINT x = 0; x < desc.Width; ++x) {
const bool inside = !subrect || (x >= 3 && x < width + 3 && y >= 4 && y < height + 4);
std::memcpy(static_cast<char*>(mapped) + y * footprint.Footprint.RowPitch + x * bytesPerTexel,
inside ? values[i] : padding, bytesPerTexel);
}
upload->Unmap(0, nullptr);
auto dst = location(inputs[i].Get()); D3D12_TEXTURE_COPY_LOCATION src{};
src.pResource = upload.Get(); src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; src.PlacedFootprint = footprint;
list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
transition(list.Get(), inputs[i].Get(), D3D12_RESOURCE_STATE_COPY_DEST,
depth ? D3D12_RESOURCE_STATE_DEPTH_READ : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
uploads.push_back(upload);
}
std::array<ComPtr<ID3D12Resource>, 8> prepared;
std::array<NativeFsrPasses::Guide, 10> guides;
ID3D12Resource* resources[18]{}; DXGI_FORMAT formats[18]{};
for (size_t i = 0; i < 10; ++i) {
const ResourceAccess access(depthStencil && i == 1 ? D3D12_RESOURCE_STATE_DEPTH_READ : D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
passes.copy_guide(list.Get(), inputs[i].Get(), access, i == 1, width, height, width, height,
subrect ? 3 : 0, subrect ? 4 : 0, guides[i]);
resources[i] = guides[i].resource.Get(); formats[i] = guides[i].format;
}
for (size_t i = 0; i < 8; ++i) {
formats[10+i] = i == 0 ? DXGI_FORMAT_R32_FLOAT : DXGI_FORMAT_R16G16B16A16_FLOAT;
prepared[i] = texture(formats[10+i], D3D12_RESOURCE_STATE_UNORDERED_ACCESS, true); resources[10+i] = prepared[i].Get();
}
D4rFsrRrPrepareConstants constants{};
for (int i : {0,5,10,15}) constants.clipToView[i] = constants.viewToPreviousView[i] = 1;
constants.viewToPreviousView[14] = .5f;
constants.renderSize[0] = width; constants.renderSize[1] = height;
constants.renderSize[2] = 1.0f/width; constants.renderSize[3] = 1.0f/height;
constants.motion[0] = 1.0f/width; constants.motion[1] = 1.0f/height;
constants.motion[2] = constants.motion[3] = 1;
constants.motionBaseFlags[2] = kFsrRrSpecularHit | kFsrRrDiffuseHit | kFsrRrBeforeParticles | kFsrRrInvalidNormalPassthrough;
if (hardwareDepth) constants.motionBaseFlags[2] |= kFsrRrHardwareDepth;
constants.depthParams[0] = constants.depthParams[1] = 1; constants.depthParams[2] = 1e6f;
constants.detail[1] = .1f;
passes.record(list.Get(), passes.prepare, &constants, resources, formats, width, height);
auto composed = texture(DXGI_FORMAT_R16G16B16A16_FLOAT, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, true);
ID3D12Resource* composition[] = {prepared[5].Get(), prepared[6].Get(), prepared[3].Get(), prepared[4].Get(),
prepared[7].Get(), composed.Get(), prepared[5].Get(), prepared[6].Get()};
DXGI_FORMAT halfFormats[8]; for (auto& format : halfFormats) format = DXGI_FORMAT_R16G16B16A16_FLOAT;
struct { UINT width, height; float halo; UINT unused; } composeConstants{width, height, 0, 0};
passes.record(list.Get(), passes.compose, &composeConstants, composition, halfFormats, width, height);
transition(list.Get(), composed.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
auto widened = texture(DXGI_FORMAT_R32G32B32A32_FLOAT, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, true);
ID3D12Resource* widening[] = {composed.Get(), widened.Get()};
DXGI_FORMAT wideFormats[] = {DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT};
const UINT extent[] = {width, height};
passes.record(list.Get(), passes.widen, extent, widening, wideFormats, width, height);
struct Readback { ComPtr<ID3D12Resource> resource; D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint; };
std::vector<Readback> reads;
for (size_t i = 0; i < 10; ++i) {
auto* source = i < 8 ? prepared[i].Get() : i == 8 ? composed.Get() : widened.Get();
transition(list.Get(), source, i == 8 ? D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE : D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE);
auto desc = desc_of(source); Readback r{}; UINT64 bytes = 0;
device->GetCopyableFootprints(&desc, 0, 1, 0, &r.footprint, nullptr, nullptr, &bytes);
r.resource = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
auto src = location(source); D3D12_TEXTURE_COPY_LOCATION dst{};
dst.pResource = r.resource.Get(); dst.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; dst.PlacedFootprint = r.footprint;
list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr); reads.push_back(r);
}
dx(list->Close(), "Close"); ID3D12CommandList* lists[] = {list.Get()}; queue->ExecuteCommandLists(1, lists);
ComPtr<ID3D12Fence> fence; dx(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)), "Fence");
Handle event; event.value = CreateEventW(nullptr, FALSE, FALSE, nullptr);
dx(queue->Signal(fence.Get(), 1), "Signal"); dx(fence->SetEventOnCompletion(1, event.value), "Completion");
if (WaitForSingleObject(event.value, 30000) != WAIT_OBJECT_0 || fence->GetCompletedValue() == UINT64_MAX)
throw std::runtime_error("GPU test failed to retire");
double expected[10][4] = {{2}, {.25/width,-.5/height,.5,0}, {.5,.5,.5,0}, {.5,.5,.5,0},
{.25,.25,.25,0}, {4.0/3,2.0/3,1.0/3,5}, {4.0/3,2.0/3,1.0/3,3}, {1,.5,.25,0}, {2,1,.5,1}, {2,1,.5,1}};
if (hardwareDepth) expected[0][0] = .25;
if (sky) {
for (int i = 0; i < 8; ++i) for (double& value : expected[i]) value = 0;
expected[0][0] = 1e7;
expected[2][0] = expected[2][1] = .5; expected[2][2] = 1;
expected[7][0] = 2; expected[7][1] = 1; expected[7][2] = .5;
}
// D3D's typed float32-to-float16 store truncates toward zero. Compute the
// independent expected composition from the specified half signal values.
for (int c = 0; c < 3; ++c) {
const double signal = diag::binary16::half_value(diag::binary16::half_bits(expected[5][c]));
const double color = signal * (sky ? 0 : .75) + expected[7][c];
uint16_t bits = diag::binary16::half_bits(color);
if (diag::binary16::half_value(bits) > color) --bits;
expected[8][c] = expected[9][c] = diag::binary16::half_value(bits);
}
std::vector<std::vector<uint16_t>> halfData(10);
for (size_t i = 0; i < reads.size(); ++i) {
void* mapped = nullptr; dx(reads[i].resource->Map(0, nullptr, &mapped), "Readback map");
const bool wide = i == 0 || i == 9; const UINT channels = i == 0 ? 1 : 4;
for (UINT y = 0; y < height; ++y) for (UINT x = 0; x < width; ++x) for (UINT c = 0; c < channels; ++c) {
const char* p = static_cast<const char*>(mapped) + y * reads[i].footprint.Footprint.RowPitch + (x * channels + c) * (wide ? 4 : 2);
float value; uint16_t half = 0;
if (wide) std::memcpy(&value, p, 4);
else { std::memcpy(&half, p, 2); value = float(diag::binary16::half_value(half)); }
const double wanted = wide ? expected[i][c] : diag::binary16::half_value(diag::binary16::half_bits(expected[i][c]));
if (!std::isfinite(value) || std::abs(double(value) - wanted) > 1e-6)
throw std::runtime_error("Native conversion mismatch plane=" + std::to_string(i) + " channel=" + std::to_string(c) + " value=" + std::to_string(value));
if (i == 8) halfData[8].push_back(half);
if (i == 9 && value != diag::binary16::half_value(halfData[8][(y * width + x) * 4 + c]))
throw std::runtime_error("Native output widening is not exact");
}
reads[i].resource->Unmap(0, nullptr);
}
std::printf("PASS FSR_NATIVE_CONVERSIONS adapter=%s sky=%d hardware_depth=%d depth_stencil=%d subrect=%d pixels=%u prepared_planes=8 composed=1 exact_widening=1 route_contract=1 denoiser_executed=0\n",
warp ? "WARP" : "hardware", sky, hardwareDepth, depthStencil, subrect, width*height);
return 0;
} catch (const std::exception& error) { std::fprintf(stderr, "FAIL FSR_NATIVE_CONVERSIONS %s\n", error.what()); return 1; }
+1 -25
View File
@@ -11,31 +11,7 @@
#include "fsr_rr/fsr_rr_shaders_spv.h"
struct D4rFsrRrPrepareConstants
{
float clipToView[16];
float viewToPreviousView[16];
float renderSize[4];
float motion[4];
uint32_t colorDepthBase[4];
uint32_t motionBaseFlags[4];
float depthParams[4];
float detail[4];
};
static_assert(sizeof(D4rFsrRrPrepareConstants) == 224, "fsr_rr_prepare.comp push constants");
enum D4rFsrRrPrepareFlags : uint32_t
{
kFsrRrHardwareDepth = 1u,
kFsrRrRoughnessPacked = 2u,
kFsrRrSpecularHit = 4u,
kFsrRrSpecularHitInW = 8u,
kFsrRrDiffuseHit = 16u,
kFsrRrDiffuseHitInW = 32u,
kFsrRrNormalsUnorm = 64u,
kFsrRrBeforeParticles = 128u,
kFsrRrInvalidNormalPassthrough = 256u,
};
#include "fsr_rr_constants.h"
// One image a pass reads or writes: the view and the layout the image is in while the pass runs.
struct D4rFsrRrImage
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <stdexcept>
#include <string_view>
namespace d4r::win::rr {
inline bool native_fsr_route(std::string_view architecture, const char* overrideMode = nullptr) {
const std::string_view mode = overrideMode ? overrideMode : "auto";
if (mode == "native") return true;
if (mode == "vkd3d") return false;
if (mode.empty() || mode == "auto") return architecture.rfind("gfx12", 0) == 0;
throw std::runtime_error("D4R_FSR_RR_DEVICE must be auto, native or vkd3d");
}
}
+4
View File
@@ -0,0 +1,4 @@
#pragma once
// Embedded at CMake configure time; native conversion never reads source files at runtime.
static const char d4r_hlsl_fsr_rr_prepare[] = R"D4RHLSL(@d4r_hlsl_fsr_rr_prepare@)D4RHLSL";
static const char d4r_hlsl_fsr_rr_compose[] = R"D4RHLSL(@d4r_hlsl_fsr_rr_compose@)D4RHLSL";
+105 -5
View File
@@ -12,6 +12,7 @@ import re
import shutil
import subprocess
import tempfile
import time
import urllib.request
import uuid
@@ -47,6 +48,41 @@ def external(path, game):
raise RuntimeError('Cache and backups must stay outside game directory: ' + str(path))
def format_eta(seconds):
seconds = int(seconds)
hours, rest = divmod(seconds, 3600)
minutes, seconds = divmod(rest, 60)
if hours:
return '{}h {:02d}m {:02d}s'.format(hours, minutes, seconds)
if minutes:
return '{}m {:02d}s'.format(minutes, seconds)
return str(seconds) + 's'
def copy_with_progress(response, out, name, chunk=1 << 20):
"""Copy a download to `out`, rewriting one status line with percent, speed and ETA."""
total = int(response.headers.get('Content-Length') or 0)
done, start, last = 0, time.monotonic(), 0.0
while True:
block = response.read(chunk)
if not block:
break
out.write(block)
done += len(block)
now = time.monotonic()
if now - last >= 0.25 or (total and done >= total):
last = now
speed = done / max(now - start, 1e-6)
line = ' {} {:.1f} MiB'.format(name, done / 1048576)
if total:
line += ' / {:.1f} MiB ({:.0f}%)'.format(total / 1048576, 100 * done / total)
line += ' at {:.1f} MiB/s'.format(speed / 1048576)
if total and done < total:
line += ', ETA ' + format_eta((total - done) / speed)
print('\r' + line.ljust(79), end='', flush=True)
print(flush=True)
def verified_download(path, url, expected, offline=False):
if path.exists():
if path.is_file() and sha(path) == expected:
@@ -61,7 +97,7 @@ def verified_download(path, url, expected, offline=False):
try:
print('DOWNLOAD ' + path.name, flush=True)
with urllib.request.urlopen(url, timeout=60) as response, temporary.open('xb') as out:
shutil.copyfileobj(response, out)
copy_with_progress(response, out, path.name)
if sha(temporary) != expected:
raise RuntimeError('Download SHA256 mismatch: ' + path.name)
os.replace(temporary, path)
@@ -70,6 +106,17 @@ def verified_download(path, url, expected, offline=False):
return path
def native_fsr_package(runtime):
"""Only explicit RDNA4-only packages can omit the compatibility payload."""
target = runtime / 'gpu-target.json'
architectures = set()
if target.is_file():
architectures.add(json.loads(target.read_text(encoding='utf-8-sig')).get('architecture'))
if (runtime / 'package.json').is_file():
architectures.update(json.loads((runtime / 'package.json').read_text(encoding='utf-8-sig')).get('targets', {}))
return bool(architectures) and architectures <= {'gfx1200', 'gfx1201'}
def ini_selects_fsr(runtime):
"""True when d4r.ini sets [RayReconstruction] Backend = fsr (comments and case ignored)."""
section = ''
@@ -139,6 +186,48 @@ def enable_fsr(runtime, backup_root):
return backup
def absolutize_library_paths(game, backup_root):
"""Make OptiScaler.ini's [Libraries] folders absolute for this game folder.
OptiScaler builds without patch 0006 reject a relative NvngxPath ("AllowExternalBackend requires an
absolute NvngxPath"), which leaves the AMD GPU DLSS-incapable and hides DLSS in its menu. Idempotent;
the previous file is kept outside the game.
"""
config = game / 'OptiScaler.ini'
if not config.is_file():
return None
original = config.read_bytes()
text = original.decode('utf-8-sig')
section, changed, lines = '', False, []
for line in text.splitlines(keepends=True):
stripped = line.strip()
if stripped.startswith('[') and stripped.endswith(']'):
section = stripped[1:-1].strip().lower()
elif section == 'libraries' and '=' in line and not stripped.startswith((';', '#')):
key, value = line.split('=', 1)
ending = line[len(line.rstrip('\r\n')):]
value = value.strip()
if (key.strip().lower() in ('nvngxpath', 'nvngxdlsspath', 'nvngxfeaturepath')
and value and not Path(value).is_absolute()):
line = key.rstrip() + '=' + str((game / value).resolve()) + ending
changed = True
lines.append(line)
if not changed:
return None
backup_root.mkdir(parents=True, exist_ok=True)
backup = backup_root / ('optiscaler-config-' + uuid.uuid4().hex + '.ini')
with backup.open('xb') as stream:
stream.write(original)
temporary = config.with_name(config.name + '.' + uuid.uuid4().hex + '.tmp')
with temporary.open('xb') as stream:
stream.write((b'\xef\xbb\xbf' if original.startswith(b'\xef\xbb\xbf') else b'') + ''.join(lines).encode('utf-8'))
if config.read_bytes() != original:
temporary.unlink()
raise RuntimeError('OptiScaler.ini changed during installation; preserved current configuration')
os.replace(temporary, config)
return backup
def collect(cache, offline, fsr=False):
assets = []
for kind, (name, relative, url, expected) in PINS.items():
@@ -293,6 +382,12 @@ def install_model(runtime, cache, backup, preset, enable):
python = tools / 'python/python.exe'
recipe = tools / 'recipe'
output = recipe / 'build' / ('rr-' + preset.lower())
# An interrupted build (window closed, Ctrl+C) leaves products without the builder's cache
# stamp, which --reuse rejects on every later run. Keep it aside and build afresh.
if output.exists() and not (output / 'windows-native-python-cache.json').is_file():
aside = output.with_name(output.name + '.incomplete-' + uuid.uuid4().hex)
output.rename(aside)
print('Previous RR model build was incomplete; kept at ' + str(aside) + ' and rebuilding.', flush=True)
# Each builder child has its own 600s deadline and kills a timed-out tree.
subprocess.run([str(python), str(recipe / 'tools/windows/build_rr_model.py'),
'--dll', str(runtime / 'rr/nvngx_dlssd.dll'), '--preset', preset,
@@ -316,7 +411,7 @@ def main():
parser.add_argument('--enable-rr', action='store_true', help='select generated native RR model in d4r.ini')
parser.add_argument('--enable-fsr', action='store_true', help='install the FSR denoiser and select Backend = fsr in d4r.ini')
parser.add_argument('--install-fsr', action='store_true',
help="also install AMD's pinned FSR Ray Regeneration denoiser (automatic when d4r.ini selects Backend = fsr)")
help="require AMD's pinned FSR Ray Regeneration denoiser (installed automatically whenever the release has its d4r/fsr payload)")
args = parser.parse_args()
runtime = args.package_root / 'd4r' if (args.package_root / 'd4r/d4r.ini').is_file() else args.package_root
if not (runtime / 'd4r.ini').is_file():
@@ -332,13 +427,18 @@ def main():
raise RuntimeError('Release does not recognize pinned ' + kind)
if args.enable_fsr and args.rr_preset:
parser.error('--enable-fsr and --rr-preset select different RR backends')
fsr = args.install_fsr or args.enable_fsr or ini_selects_fsr(runtime)
if fsr and not all((runtime / 'fsr' / name).is_file() for name in ('d4r_vkd3d_core.dll', 'amdxc64.dll')):
# Install every backend's files up front so switching [RayReconstruction] Backend later needs no rerun.
# AMD's denoiser comes along whenever the release carries the FSR payload; requesting FSR without it fails.
fsr_payload = all((runtime / 'fsr' / name).is_file() for name in ('d4r_vkd3d_core.dll', 'amdxc64.dll'))
native_fsr = native_fsr_package(runtime)
if (args.install_fsr or args.enable_fsr or ini_selects_fsr(runtime)) and not (fsr_payload or native_fsr):
parser.error("FSR Ray Regeneration needs this release's d4r/fsr payload (d4r_vkd3d_core.dll, amdxc64.dll); extract the complete ZIP")
if not args.yes and input('Download pinned DLSS/RR 310.9.1 and NGX 617.14 (~945 MiB archive)? [Y/n] ').lower().startswith('n'):
fsr = fsr_payload or native_fsr
if not args.yes and input('Download pinned DLSS/RR 310.9.1 and NGX 617.14 (~945 MiB archive)? [Y/n] ').strip().lower().startswith('n'):
return
result = transaction(runtime, collect(args.cache_root, args.offline, fsr), args.backup_root)
print(json.dumps(result, indent=2))
absolutize_library_paths(game, args.backup_root)
if args.enable_fsr:
backup = enable_fsr(runtime, args.backup_root)
print(json.dumps({'fsrBackendSelected': True, 'configBackup': str(backup) if backup else None}, indent=2))
+15 -2
View File
@@ -35,8 +35,21 @@ struct RuntimeInitialization {
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);
try {
return std::make_shared<Runtime>(device.Get(), application, data.c_str(), sdk,
hasCommon ? &common : nullptr, hasProject ? &project : nullptr);
} catch (const std::exception& error) {
if (hasProject) throw;
// NGX 617.14 rejects application-ID initialization on this path (Cyberpunk 2077:
// 0xBAD0000C, or SR unavailable with 0xBAD00004). Streamline's RR plugin reads
// capabilities right after that first Init, so a failure there hides RR for the
// session even though the game's later project-ID Init works. Retry with d4r's
// own project identity (the diagnostics' default) so the first Init succeeds.
std::fprintf(stderr, "D4R_NGX_IDENTITY_FALLBACK application=%llu: %s\n", application, error.what());
const ngx::ProjectIdentity fallback{"24480451-f00d-face-1304-0308dabad187", 0, "1.0"};
return std::make_shared<Runtime>(device.Get(), application, data.c_str(), sdk,
hasCommon ? &common : nullptr, &fallback);
}
}
};
}
+6 -3
View File
@@ -691,10 +691,10 @@ private:
mark("ngx_all_stream_completion");
auto& output = planes_[3];
output.image->download_device(reinterpret_cast<uintptr_t>(output.canonical ? output.canonical->data : output.shared->mapped),
output.canonical ? output.canonical->pitch : output.shared->footprint.Footprint.RowPitch);
rt_->cuda.check(rt_->cuda.cuCtxSynchronize(), "VRAM output completion");
output.canonical ? output.canonical->pitch : output.shared->footprint.Footprint.RowPitch, true);
mark("output_array_download");
if (std::getenv("D4R_VALIDATE_OUTPUT")) {
rt_->cuda.check(rt_->cuda.cuCtxSynchronize(), "VRAM diagnostic output completion");
if (!rt_->pixels) rt_->pixels = std::make_unique<PixelProgram>(rt_->hip, pixel_module_path());
const auto counts = rt_->pixels->validate(output.canonical ? output.canonical->data : output.shared->mapped,
output.canonical ? output.canonical->pitch : output.shared->footprint.Footprint.RowPitch, unsigned(output.desc.Width), output.desc.Height);
@@ -702,8 +702,11 @@ private:
if (counts[0] || counts[1]) throw std::runtime_error("DLSS output contains NaN/Inf");
}
mark("output_diagnostic");
if (output.canonical) rt_->pixels->convert(true, output.canonical->data, output.canonical->pitch, output.shared->mapped,
if (output.canonical) rt_->pixels->convert_deferred(true, output.canonical->data, output.canonical->pitch, output.shared->mapped,
output.shared->footprint.Footprint.RowPitch, unsigned(output.desc.Width), output.desc.Height, output.spec.storage, 3);
// One completion covers the array download and optional format encode.
// NGX's nonblocking streams were completed before this ordered batch.
rt_->cuda.check(rt_->cuda.cuCtxSynchronize(), "VRAM output batch completion");
if (ticket) rt_->timeline->signal_ready(ticket->output); else rt_->timeline->signal_output(queue);
mark("output_conversion_fence_signal");
const double frameMs = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - begin).count();
+10 -5
View File
@@ -19,8 +19,12 @@ inline PixelSpec pixel_spec(unsigned plane, DXGI_FORMAT format) {
case DXGI_FORMAT_R32G32B32A32_FLOAT: return {rgba32f, 16, false};
case DXGI_FORMAT_R11G11B10_FLOAT: return {r11g11b10f, 4, false};
case DXGI_FORMAT_R10G10B10A2_UNORM: case DXGI_FORMAT_R10G10B10A2_TYPELESS: return {rgb10a2, 4, false};
case DXGI_FORMAT_R8G8B8A8_UNORM: case DXGI_FORMAT_R8G8B8A8_TYPELESS: return {rgba8, 4, false};
case DXGI_FORMAT_B8G8R8A8_UNORM: case DXGI_FORMAT_B8G8R8A8_TYPELESS: return {bgra8, 4, false};
// sRGB views are read as their stored UNORM values, as the Linux shim does.
case DXGI_FORMAT_R8G8B8A8_UNORM: case DXGI_FORMAT_R8G8B8A8_TYPELESS: case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
return {rgba8, 4, false};
case DXGI_FORMAT_B8G8R8A8_UNORM: case DXGI_FORMAT_B8G8R8A8_TYPELESS: case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
return {bgra8, 4, false};
case DXGI_FORMAT_R9G9B9E5_SHAREDEXP: return {rgb9e5, 4, false};
case DXGI_FORMAT_R16G16_FLOAT: case DXGI_FORMAT_R16G16_TYPELESS: return {rg16f, 4, plane == motion};
case DXGI_FORMAT_R32G32_FLOAT: case DXGI_FORMAT_R32G32_TYPELESS: return {rg32f, 8, false};
case DXGI_FORMAT_R16G16_SNORM: return {rg16snorm, 4, false};
@@ -42,9 +46,10 @@ inline PixelSpec pixel_spec(unsigned plane, DXGI_FORMAT format) {
inline bool pixel_supported(unsigned plane, pixel::Storage storage) {
using namespace pixel;
switch (plane) {
case color: case output: return storage <= rgb10a2;
case motion: return storage == rgba16f || storage == rgba32f || (storage >= rg16f && storage <= rg16unorm);
case depth: return storage == r32f || storage == depth24 || storage == r16unorm;
// The Linux shim's supported_input/supported_output sets, plus the RG16 SNORM/UNORM motion formats.
case color: case output: return storage <= rgb10a2 || storage == rgb9e5;
case motion: return storage == rgba16f || storage == rgba32f || storage == rgba8 || (storage >= rg16f && storage <= rg16unorm);
case depth: return storage == r32f || storage == depth24 || storage == r16unorm || storage == r16f;
case exposure: return storage == r32f || storage == r16f || storage == rgba16f || storage == rgba32f ||
storage == rgba8 || storage == bgra8;
default: return false;
+22
View File
@@ -85,6 +85,14 @@ __device__ inline void decode(const u8* p, Storage fmt, float* value) {
case r32f: value[0] = singles[0]; break;
case depth24: value[0] = float(*reinterpret_cast<const u32*>(p) & 0xffffffu) / 16777215.f; break;
case r16unorm: value[0] = float(halves[0]) / 65535.f; break;
case rgb9e5: {
// Microsoft spec: c = (p >> offset) & 0x1FF, E = p >> 27, v = c * 2^(E - 24);
// no implicit leading bit. 2^(E - 24) is a normal float for every E.
u32 packed = *reinterpret_cast<const u32*>(p);
const float scale = as_float(((packed >> 27) + 103u) << 23);
for (unsigned i = 0; i < 3; ++i) value[i] = float((packed >> (9 * i)) & 511u) * scale;
break;
}
default: break; // Host rejects formats which have no tested decoder.
}
}
@@ -103,6 +111,20 @@ __device__ inline void encode(u8* p, Storage fmt, const float* value) {
case rgb10a2:
*reinterpret_cast<u32*>(p) = quantize(value[0], 1023) | (quantize(value[1], 1023) << 10) |
(quantize(value[2], 1023) << 20) | (quantize(value[3], 3) << 30); break;
case rgb9e5: {
// Shared exponent from the largest channel (D3D11.3 spec 3.2.2): negative and
// NaN clamp to 0, the maximum is (511/512) * 2^16.
float c[3];
for (unsigned i = 0; i < 3; ++i) c[i] = value[i] > 0 ? (value[i] < 65408.f ? value[i] : 65408.f) : 0.f;
float largest = c[0] > c[1] ? c[0] : c[1]; largest = largest > c[2] ? largest : c[2];
int floorLog2 = int((as_uint(largest) >> 23) & 255u) - 127;
int shared = (floorLog2 < -16 ? -16 : floorLog2) + 16; // biased exponent, 0..31
float step = as_float(u32(shared - 24 + 127) << 23); // 2^(shared - 24)
if (u32(largest / step + .5f) == 512u) { ++shared; step *= 2.f; }
u32 packed = u32(shared) << 27;
for (unsigned i = 0; i < 3; ++i) packed |= (u32(c[i] / step + .5f) & 511u) << (9 * i);
*reinterpret_cast<u32*>(p) = packed; break;
}
default: break; // Only color/output formats are encoded in the runtime.
}
}
+2 -1
View File
@@ -6,7 +6,8 @@ namespace d4r::pixel {
enum Storage : unsigned {
rgba16f, rgba32f, rgba8, bgra8, r11g11b10f, rgb10a2,
rg16f, rg32f, rg16snorm, rg16unorm, r16f, r32f,
depth24, r16unorm, rgba8srgb, bgra8srgb
depth24, r16unorm, rgba8srgb, bgra8srgb,
rgb9e5 // DXGI_FORMAT_R9G9B9E5_SHAREDEXP
};
enum Plane : unsigned { color, depth, motion, output, exposure };
}
+4 -2
View File
@@ -273,8 +273,10 @@ struct Replay {
: 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");
const bool gfx12 = std::string(D4R_TARGET_ARCH).rfind("gfx12", 0) == 0;
const char* arithmetic = gfx12 ? "\narithmetic gfx12\n" : "\narithmetic gfx11\n";
if (manifest.rfind("D4RR0001", 0) != 0 || manifest.find(arithmetic) == std::string::npos)
throw std::runtime_error("RR bridge model arithmetic does not match the package GPU family");
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);
+63
View File
@@ -0,0 +1,63 @@
#pragma once
// RR guide formats shared by the native and FSR RR backends. Include after volk.h and rr_bridge_replay.h.
#include <string>
namespace d4r::win::rr {
// The Linux shim's RR guide formats (supported_input and aux_array_layout): every UNORM/SNORM/float
// layout samples verbatim, as a D3D12 shader reads it; no CPU conversion or reinterpretation is involved.
// Typeless resources use the float/UNORM view and sRGB is read as stored UNORM, like Linux.
inline rr_bridge::Plane guide_format(DXGI_FORMAT format, size_t plane, const char* backend) {
if (plane == Snapshot::Depth) {
switch (format) {
case DXGI_FORMAT_R32_FLOAT: case DXGI_FORMAT_R32_TYPELESS: case DXGI_FORMAT_D32_FLOAT:
case DXGI_FORMAT_R32G8X24_TYPELESS: case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS:
return {nullptr, VK_FORMAT_R32_SFLOAT, 4};
case DXGI_FORMAT_R16_FLOAT: return {nullptr, VK_FORMAT_R16_SFLOAT, 2};
case DXGI_FORMAT_D16_UNORM: case DXGI_FORMAT_R16_UNORM: case DXGI_FORMAT_R16_TYPELESS: return {nullptr, VK_FORMAT_R16_UNORM, 2};
default: break;
}
} else {
switch (format) {
case DXGI_FORMAT_R8_UNORM: case DXGI_FORMAT_R8_TYPELESS: return {nullptr, VK_FORMAT_R8_UNORM, 1};
case DXGI_FORMAT_R8_SNORM: return {nullptr, VK_FORMAT_R8_SNORM, 1};
case DXGI_FORMAT_R8G8_UNORM: case DXGI_FORMAT_R8G8_TYPELESS: return {nullptr, VK_FORMAT_R8G8_UNORM, 2};
case DXGI_FORMAT_R8G8_SNORM: return {nullptr, VK_FORMAT_R8G8_SNORM, 2};
case DXGI_FORMAT_R8G8B8A8_UNORM: case DXGI_FORMAT_R8G8B8A8_TYPELESS: case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
return {nullptr, VK_FORMAT_R8G8B8A8_UNORM, 4};
case DXGI_FORMAT_R8G8B8A8_SNORM: return {nullptr, VK_FORMAT_R8G8B8A8_SNORM, 4};
case DXGI_FORMAT_B8G8R8A8_UNORM: case DXGI_FORMAT_B8G8R8A8_TYPELESS: case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
return {nullptr, VK_FORMAT_B8G8R8A8_UNORM, 4};
case DXGI_FORMAT_R16_UNORM: return {nullptr, VK_FORMAT_R16_UNORM, 2};
case DXGI_FORMAT_R16_SNORM: return {nullptr, VK_FORMAT_R16_SNORM, 2};
case DXGI_FORMAT_R16_FLOAT: case DXGI_FORMAT_R16_TYPELESS: return {nullptr, VK_FORMAT_R16_SFLOAT, 2};
case DXGI_FORMAT_R16G16_UNORM: return {nullptr, VK_FORMAT_R16G16_UNORM, 4};
case DXGI_FORMAT_R16G16_SNORM: return {nullptr, VK_FORMAT_R16G16_SNORM, 4};
case DXGI_FORMAT_R16G16_FLOAT: case DXGI_FORMAT_R16G16_TYPELESS: return {nullptr, VK_FORMAT_R16G16_SFLOAT, 4};
case DXGI_FORMAT_R16G16B16A16_UNORM: return {nullptr, VK_FORMAT_R16G16B16A16_UNORM, 8};
case DXGI_FORMAT_R16G16B16A16_SNORM: return {nullptr, VK_FORMAT_R16G16B16A16_SNORM, 8};
case DXGI_FORMAT_R16G16B16A16_FLOAT: case DXGI_FORMAT_R16G16B16A16_TYPELESS: return {nullptr, VK_FORMAT_R16G16B16A16_SFLOAT, 8};
case DXGI_FORMAT_R32_FLOAT: case DXGI_FORMAT_R32_TYPELESS: return {nullptr, VK_FORMAT_R32_SFLOAT, 4};
case DXGI_FORMAT_R32G32_FLOAT: case DXGI_FORMAT_R32G32_TYPELESS: return {nullptr, VK_FORMAT_R32G32_SFLOAT, 8};
case DXGI_FORMAT_R32G32B32A32_FLOAT: case DXGI_FORMAT_R32G32B32A32_TYPELESS: return {nullptr, VK_FORMAT_R32G32B32A32_SFLOAT, 16};
case DXGI_FORMAT_R10G10B10A2_UNORM: case DXGI_FORMAT_R10G10B10A2_TYPELESS: return {nullptr, VK_FORMAT_A2B10G10R10_UNORM_PACK32, 4};
// Same bit layouts: R11G11B10 red in bits 0-10; R9G9B9E5 red in bits 0-8, exponent 27-31.
case DXGI_FORMAT_R11G11B10_FLOAT: return {nullptr, VK_FORMAT_B10G11R11_UFLOAT_PACK32, 4};
case DXGI_FORMAT_R9G9B9E5_SHAREDEXP: return {nullptr, VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, 4};
default: break;
}
}
// Linux also accepts UINT/SINT guides by converting them to float on the CPU; this
// sampled-image route has no conversion pass, so those still fail here, by name.
throw std::runtime_error(std::string("Unsupported ") + backend + " sampled input format: plane " + std::to_string(plane) +
" DXGI format " + std::to_string(unsigned(format)));
}
// UNORM guides store [0, 1]; normals in them are remapped to [-1, 1] before denoising.
inline bool unorm_guide(VkFormat format) {
switch (format) {
case VK_FORMAT_R8G8B8A8_UNORM: case VK_FORMAT_B8G8R8A8_UNORM: case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
case VK_FORMAT_R16G16B16A16_UNORM: case VK_FORMAT_R8G8_UNORM: case VK_FORMAT_R16G16_UNORM:
return true;
default: return false;
}
}
}
+7 -27
View File
@@ -2,6 +2,7 @@
#include "rr_native_backend.h"
#include "rr_native_packet.h"
#include "rr_bridge_replay.h"
#include "rr_input_formats.h"
#include "d3d12_command_hooks.h"
#include <mutex>
@@ -13,32 +14,7 @@ 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");
}
rr_bridge::Plane input_format(DXGI_FORMAT format, size_t plane) { return guide_format(format, plane, "native RR"); }
DXGI_FORMAT copy_format(ID3D12Device* device, ID3D12Resource* texture, size_t plane) {
const auto desc = resource_desc(texture);
if (plane != Snapshot::Depth) return desc.Format;
@@ -111,7 +87,11 @@ struct Backend::Impl {
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");
// The preset is a hint (Cyberpunk 2077 asks for one per quality mode). Serve the locally
// built model rather than failing RR; the log names both so the user can build that preset.
if (requested && requested != replay->model->preset())
std::fprintf(stderr, "d4r: RR preset %u requested; serving the configured preset %u model (build the requested one with install.bat to match)\n",
unsigned(requested), unsigned(replay->model->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;
+17 -1
View File
@@ -47,13 +47,28 @@ struct NativeDevice {
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};
const bool fp8 = inference && std::string(D4R_TARGET_ARCH).rfind("gfx12", 0) == 0;
if (fp8) {
uint32_t extensionCount = 0;
check(vkEnumerateDeviceExtensionProperties(physical, nullptr, &extensionCount, nullptr), "RR extension count");
std::vector<VkExtensionProperties> available(extensionCount);
check(vkEnumerateDeviceExtensionProperties(physical, nullptr, &extensionCount, available.data()), "RR extensions");
bool supported = false;
for (const auto& extension : available)
supported |= std::strcmp(extension.extensionName, VK_EXT_SHADER_FLOAT8_EXTENSION_NAME) == 0;
if (!supported) throw std::runtime_error("RDNA4 native RR requires VK_EXT_shader_float8");
}
VkPhysicalDeviceShaderFloat8FeaturesEXT f8{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT8_FEATURES_EXT};
VkPhysicalDeviceCooperativeMatrixFeaturesKHR cm{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_KHR,
fp8 ? &f8 : nullptr};
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 (fp8 && (!f8.shaderFloat8 || !f8.shaderFloat8CooperativeMatrix))
throw std::runtime_error("RDNA4 native RR requires FP8 cooperative matrices");
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;
@@ -62,6 +77,7 @@ struct NativeDevice {
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);
if (fp8) extensions.push_back(VK_EXT_SHADER_FLOAT8_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();
+3
View File
@@ -54,6 +54,9 @@ struct Snapshot {
unsigned renderWidth = 0, renderHeight = 0;
unsigned depthType = 0, normalsType = 0, roughnessType = 0, diffuseType = 0, specularType = 0;
float exposureScale = 1;
// FSR RR only (set by the shim, as the Linux FSR backend reads them): the hit distance is the w of a
// ray-direction texture, and the Subsurface slot carries the diffuse hit distance instead.
bool specularHitInW = false, diffuseHit = false, diffuseHitInW = false;
static ID3D12Resource* resource(void* parameters, const char* name, const char* alias = nullptr) {
ID3D12Resource* value = nullptr;
(void)d4r_ngx_get_d3d12_resource(parameters, name, &value);
+40
View File
@@ -0,0 +1,40 @@
"""Compare bounded FP32 attention dots with sequential FP64 on the GPU."""
import argparse
import json
import os
from pathlib import Path
import struct
import subprocess
def main(args):
root = args.output_dir.resolve()
root.mkdir(parents=True, exist_ok=False)
count = 262144
(root / 'alloc-0.bin').write_bytes(bytes(count * 4))
(root / 'args.bin').write_bytes(struct.pack('<Q', 0x100000000))
(root / 'manifest.txt').write_text(
f'kernel exact_pv_probe\nlaunch {count // 32} 1 1 32 1 1 0\nargs 8\n'
f'alloc 0 0x100000000 {count * 4}\npointer 0 0 0\n')
with (root / 'stdout.log').open('wb') as out, (root / 'stderr.log').open('wb') as err:
completed = subprocess.run([
str(args.probe.resolve()), '--hip-root', str(args.hip_root.resolve()),
'--module', str(args.module.resolve()), '--fixture-dir', str(root),
'--output-dir', str(root / 'output'), '--iterations', '1'],
stdout=out, stderr=err, timeout=120, env=dict(os.environ, D4R_DIAG_DIR=str(root)))
if completed.returncode:
raise RuntimeError(f'GPU probe failed: {root}')
data = (root / 'output/alloc-0.bin').read_bytes()
mismatches = sum((word & 65535) != (word >> 16) for (word,) in struct.iter_unpack('<I', data))
summary = dict(passed=len(data) == count * 4 and mismatches == 0,
dots=count, differingHalfBits=mismatches)
(root / 'summary.json').write_text(json.dumps(summary, indent=2) + '\n')
print(summary)
return 0 if summary['passed'] else 1
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
for name in ('probe', 'hip-root', 'module', 'output-dir'):
parser.add_argument('--' + name, type=Path, required=True)
raise SystemExit(main(parser.parse_args()))
+34 -2
View File
@@ -28,9 +28,13 @@ def main():
for name in ('probe', 'shim', 'runtime', 'core', 'dlss', 'fsr-dir', 'denoiser', 'fixture', 'output'):
parser.add_argument('--' + name, required=True, type=lambda value: Path(value).resolve())
parser.add_argument('--timeout', type=int, default=1500)
parser.add_argument('--device', choices=('auto', 'native', 'vkd3d'), default='auto', help='select the native D3D12 or compatibility route for this diagnostic')
parser.add_argument('--without-sr', action='store_true', help='omit SR/core dependencies and require the same-size FSR-only fallback')
parser.add_argument('--cache-dir', type=lambda value: Path(value).resolve(), help='isolated cache for shader/compiler experiments')
parser.add_argument('--vkd3d-config', help='diagnostic per-process vkd3d configuration')
parser.add_argument('--vkd3d-debug', choices=('none', 'err', 'warn', 'fixme', 'info', 'trace'), help='diagnostic vkd3d logging level')
parser.add_argument('--shader-dump', type=lambda value: Path(value).resolve(), help='diagnostic original shader dump into a new directory')
parser.add_argument('--disable-builtin-shaders', action='store_true', help='diagnostic control: use the original translations in a locally optimized payload')
parser.add_argument('--fp16-rte', action='store_true', help='experimental dxil-spirv payload: retain layout workaround but omit global FP16 RTZ')
parser.add_argument('--float-guides', action='store_true')
parser.add_argument('--switch-guides', action='store_true')
@@ -69,7 +73,12 @@ def main():
fsr = staged / 'fsr'
fsr.mkdir(exist_ok=True)
for name in ('d4r_vkd3d_core.dll', 'amdxc64.dll'):
shutil.copy2(args.fsr_dir / name, fsr / name)
source = args.fsr_dir / name
if args.device == 'native':
(fsr / name).unlink(missing_ok=True)
continue # prove that the direct route has no compatibility payload dependency
if source.is_file():
shutil.copy2(source, fsr / name)
shutil.copy2(args.denoiser, staged / 'amd_fidelityfx_denoiser_dx12.dll')
config = (repo / 'packaging/windows/d4r.ini').read_text()
if 'Backend = off' not in config:
@@ -87,8 +96,17 @@ def main():
env = {key: value for key, value in os.environ.items()
if not key.startswith(('D4R_', 'VKD3D_', 'DXIL_')) and key not in ('HIP_PATH', 'ZLUDA_CUDA_LIB', 'ZLUDA_LOG_DIR', 'ZLUDA_CACHE_DIR')}
env['D4R_FSR_RR_LOG'] = '1' # per-frame stage timings in stderr; selection itself comes only from d4r.ini
if args.device != 'auto':
env['D4R_FSR_RR_DEVICE'] = args.device
if args.vkd3d_config:
env['VKD3D_CONFIG'] = args.vkd3d_config
if args.vkd3d_debug:
env['VKD3D_DEBUG'] = args.vkd3d_debug
if args.shader_dump:
args.shader_dump.mkdir(parents=True, exist_ok=False)
env['VKD3D_SHADER_DUMP_PATH'] = str(args.shader_dump)
if args.disable_builtin_shaders:
env['D4R_FSR_DISABLE_BUILTIN_SHADERS'] = '1'
if args.fp16_rte:
env['D4R_DXIL_F16_RTE'] = '1'
if args.verify_output_widening:
@@ -126,8 +144,9 @@ def main():
command.append('-'.join(modes))
report = {'passed': False, 'gameAccessed': False, 'qualityReference': False, 'synthetic': True,
'selectionMode': 'diagnostic environment without SR' if args.without_sr else 'player INI',
'requestedDevice': args.device,
'artifacts': {str(path): sha(path) for path in (args.probe, args.shim, args.core, args.dlss, args.denoiser,
args.fsr_dir / 'd4r_vkd3d_core.dll', args.fsr_dir / 'amdxc64.dll')}}
args.fsr_dir / 'd4r_vkd3d_core.dll', args.fsr_dir / 'amdxc64.dll') if path.is_file()}}
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=args.timeout)
@@ -184,11 +203,22 @@ def main():
raise RuntimeError('FSR output differs from the same-frame composed half image')
# player mode redirects the shim's stderr into its own log beside d4r_nvngx.dll
err_text = (args.output / 'stderr.log').read_text(errors='replace')
device_modes = re.findall(r'D4R_FSR_RR_DEVICE (native-d3d12|vkd3d) ', err_text)
if not device_modes:
raise RuntimeError('FSR RR did not identify its device route')
report['selectedDevice'] = device_modes[-1]
requested = {'native': 'native-d3d12', 'vkd3d': 'vkd3d'}.get(args.device)
if requested and report['selectedDevice'] != requested:
raise RuntimeError('FSR RR used a different device route than requested')
playerLog = staged / 'd4r_nvngx.log'
if playerLog.exists():
err_text += playerLog.read_text(errors='replace')
shutil.copy2(playerLog, args.output / 'd4r_nvngx.log')
report['superSamplingUsed'] = 'FSR_RR super sampling submitted' in err_text
report['builtinShaderOptimizations'] = (sorted(set(re.findall(
r'using validated local optimization for shader ([0-9a-f]{16})', err_text)))
if args.vkd3d_debug in ('info', 'trace') else None)
report['builtinShadersDisabled'] = args.disable_builtin_shaders
if args.without_sr and report['superSamplingUsed']:
raise RuntimeError('SR ran in the FSR-only test')
report['superSamplingFrames'] = err_text.count('FSR_RR super sampling submitted')
@@ -202,6 +232,8 @@ def main():
for i, name in enumerate(('prepareMedianMs', 'denoiseMedianMs', 'composeCopyMedianMs', 'totalMedianMs'))}
report['gpuTiming']['warmupFrames'] = 4
report['gpuTiming']['measuredFrames'] = len(timings) - 4
report['gpuTiming']['tailDenoiseMedianMs'] = statistics.median(float(row[1]) for row in timings[-7:])
report['gpuTiming']['tailFrames'] = min(7, len(timings))
report.update(passed=True)
print('PASS FSR_RR_NGX super_sampling=%d tail_rmse=%.4f input_rmse=%.4f ideal_nearest_rmse=%.4f' % (
report['superSamplingUsed'], report['quality']['tailOutputRmse'], report['quality']['tailInputRmse'], report['quality'].get('idealNearestRmse', float('nan'))))
+4 -3
View File
@@ -11,6 +11,7 @@ import struct
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--gpu-arch', choices=('gfx1101', 'gfx1200', 'gfx1201'), default='gfx1101')
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')
@@ -55,13 +56,13 @@ def main():
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'
if (native / args.gpu_arch).is_dir():
native = native / args.gpu_arch
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_FORMAT_MODULE=str(runtime / f'pixel_convert_{args.gpu_arch}.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))