rr: add FSR Ray Regeneration, exact RDNA3 inference and motion experiments
Add an experimental FSR Ray Regeneration backend for D3D12 RR requests: - Convert depth, motion, normals and albedos into AMD denoiser inputs. - Split and recompose diffuse/specular signals, then run Super Sampling. - Add stability/detail controls, demodulation guards and halo limiting. - Handle AMD provider discovery under NVIDIA GPU spoofing. - Add runtime probes, synthetic reference scenes and per-dispatch profiling. Add exact gfx11 DLSS-RR inference using f16 cooperative matrices: - Reproduce FP8 conversions, accumulation and camera reprojection arithmetic. - Widen model weights and avoid requiring VK_EXT_shader_float8. - Extend model building, replay tools and descriptor metadata. Expand motion preprocessing and experimental AW2 native-motion support: - Add tiled LDS dilation, padded allocation handling and thin-only sky fill. - Order dilation with input waits and NGX on the selected stream. - Add jitter-aligned native composition, hybrid routing and wire history repair. - Add prepared masks, sparse repair and independent motion/repair controls. Add optional fused R11G11B10 input/output conversion, sampled stage timings and profile summarization. Extend configuration, packaging, documentation, FidelityFX headers and CPU/GPU regression coverage.
This commit is contained in:
1 parent
17c1828f97
commit
34e6b0fe71
83 files changed
+11300
-195
No files matched your search
@@ -0,0 +1,279 @@
|
||||
// AMD FSR Ray Regeneration (FidelityFX denoiser 1.2) runtime access, shared by the NGX shim's
|
||||
// FSR Ray Reconstruction backend and tools/fsr_rr_probe.cpp.
|
||||
//
|
||||
// The denoiser is AMD's signed amd_fidelityfx_denoiser_dx12.dll, loaded from an explicit path
|
||||
// (never from a search path, never patched) and driven through its five FFX API exports. It
|
||||
// records its compute work into the caller's D3D12 command list; nothing here involves CUDA,
|
||||
// ZLUDA or NVIDIA's denoiser. Struct layouts come from AMD's MIT-licensed headers in
|
||||
// third_party/fidelityfx (SDK 2.3.0 / FSR Ray Regeneration 1.2.0).
|
||||
//
|
||||
// Each stage reports separately (see FsrRrStage): a missing DLL, a missing export, a provider
|
||||
// query that returns no denoiser for this device, a failed context creation (where shader
|
||||
// pipelines are built) and a failed dispatch are different failures and are never folded together.
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <d3d12.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../third_party/fidelityfx/api/include/ffx_api.h"
|
||||
#include "../third_party/fidelityfx/api/include/ffx_api_types.h"
|
||||
#include "../third_party/fidelityfx/denoisers/include/ffx_denoiser.h"
|
||||
|
||||
// ffx_api_dx12.h's backend descriptor, declared here because that header's inline helpers call
|
||||
// ID3D12Resource::GetDesc() by value, which does not compile with WIDL_EXPLICIT_AGGREGATE_RETURNS.
|
||||
#ifndef FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12
|
||||
#define FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12 FFX_API_MAKE_BACKEND_SUB_ID(FFX_API_BACKEND_ID_DX12, 0x02)
|
||||
struct ffxCreateBackendDX12Desc
|
||||
{
|
||||
ffxCreateContextDescHeader header;
|
||||
ID3D12Device* device;
|
||||
};
|
||||
#endif
|
||||
static_assert(sizeof(ffxCreateBackendDX12Desc) == 24, "ffx_api_dx12.h layout");
|
||||
// The 1.2 descriptors as the signed runtime reads them (x64, natural alignment, same as MSVC).
|
||||
static_assert(sizeof(FfxApiResource) == 48 && sizeof(FfxApiDenoiserSignal) == 104, "ffx_api_types.h layout");
|
||||
static_assert(sizeof(ffxCreateContextDescDenoiser) == 40, "ffx_denoiser.h 1.2 create layout");
|
||||
static_assert(sizeof(ffxDispatchDescDenoiser) == 448 && offsetof(ffxDispatchDescDenoiser, view) == 296 &&
|
||||
offsetof(ffxDispatchDescDenoiser, renderSize) == 432,
|
||||
"ffx_denoiser.h 1.2 dispatch layout");
|
||||
|
||||
enum class FsrRrStage
|
||||
{
|
||||
NotLoaded, // nothing tried yet
|
||||
DllMissing, // no file at the configured path
|
||||
DllLoadFailed, // LoadLibrary failed (dependencies, architecture)
|
||||
ExportsMissing, // not an FFX API DLL
|
||||
NoProvider, // the runtime returned no denoiser version for this device
|
||||
Ready, // a provider was enumerated for this device; contexts may still fail
|
||||
};
|
||||
|
||||
inline const char* fsr_rr_stage_name(FsrRrStage stage)
|
||||
{
|
||||
switch (stage)
|
||||
{
|
||||
case FsrRrStage::NotLoaded: return "not loaded";
|
||||
case FsrRrStage::DllMissing: return "denoiser DLL missing";
|
||||
case FsrRrStage::DllLoadFailed: return "denoiser DLL failed to load";
|
||||
case FsrRrStage::ExportsMissing: return "denoiser DLL lacks the FFX API exports";
|
||||
case FsrRrStage::NoProvider: return "no FSR Ray Regeneration provider for this device";
|
||||
case FsrRrStage::Ready: return "provider available";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
inline const char* fsr_rr_return_name(ffxReturnCode_t code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case FFX_API_RETURN_OK: return "OK";
|
||||
case FFX_API_RETURN_ERROR: return "ERROR";
|
||||
case FFX_API_RETURN_ERROR_UNKNOWN_DESCTYPE: return "ERROR_UNKNOWN_DESCTYPE";
|
||||
case FFX_API_RETURN_ERROR_RUNTIME_ERROR: return "ERROR_RUNTIME_ERROR";
|
||||
case FFX_API_RETURN_NO_PROVIDER: return "NO_PROVIDER";
|
||||
case FFX_API_RETURN_ERROR_MEMORY: return "ERROR_MEMORY";
|
||||
case FFX_API_RETURN_ERROR_PARAMETER: return "ERROR_PARAMETER";
|
||||
case FFX_API_RETURN_PROVIDER_NO_SUPPORT_NEW_DESCTYPE: return "PROVIDER_NO_SUPPORT_NEW_DESCTYPE";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
// The loaded runtime and the provider the device was offered. One per process: the DLL keeps
|
||||
// global state (its debug callback), so it is loaded once and never unloaded while contexts exist.
|
||||
struct FsrRrRuntime
|
||||
{
|
||||
HMODULE module = nullptr;
|
||||
std::wstring path; // as configured
|
||||
std::wstring mappedPath; // as the loader mapped it
|
||||
PfnFfxCreateContext createContext = nullptr;
|
||||
PfnFfxDestroyContext destroyContext = nullptr;
|
||||
PfnFfxConfigure configure = nullptr;
|
||||
PfnFfxQuery query = nullptr;
|
||||
PfnFfxDispatch dispatch = nullptr;
|
||||
FsrRrStage stage = FsrRrStage::NotLoaded;
|
||||
ffxReturnCode_t queryResult = FFX_API_RETURN_OK;
|
||||
std::vector<uint64_t> versionIds;
|
||||
std::vector<std::string> versionNames;
|
||||
uint64_t providerId = 0; // the provider contexts are created with (the first enumerated)
|
||||
std::string providerName;
|
||||
|
||||
// Loads `dll` with its own directory searched for dependencies. Does not query providers.
|
||||
bool load(const std::wstring& dll)
|
||||
{
|
||||
path = dll;
|
||||
if (GetFileAttributesW(dll.c_str()) == INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
stage = FsrRrStage::DllMissing;
|
||||
return false;
|
||||
}
|
||||
module = LoadLibraryExW(dll.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
|
||||
if (module == nullptr)
|
||||
{
|
||||
stage = FsrRrStage::DllLoadFailed;
|
||||
return false;
|
||||
}
|
||||
wchar_t mapped[MAX_PATH] = {};
|
||||
GetModuleFileNameW(module, mapped, MAX_PATH);
|
||||
mappedPath = mapped;
|
||||
createContext = reinterpret_cast<PfnFfxCreateContext>(reinterpret_cast<void*>(GetProcAddress(module, "ffxCreateContext")));
|
||||
destroyContext = reinterpret_cast<PfnFfxDestroyContext>(reinterpret_cast<void*>(GetProcAddress(module, "ffxDestroyContext")));
|
||||
configure = reinterpret_cast<PfnFfxConfigure>(reinterpret_cast<void*>(GetProcAddress(module, "ffxConfigure")));
|
||||
query = reinterpret_cast<PfnFfxQuery>(reinterpret_cast<void*>(GetProcAddress(module, "ffxQuery")));
|
||||
dispatch = reinterpret_cast<PfnFfxDispatch>(reinterpret_cast<void*>(GetProcAddress(module, "ffxDispatch")));
|
||||
if (!createContext || !destroyContext || !configure || !query || !dispatch)
|
||||
{
|
||||
stage = FsrRrStage::ExportsMissing;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Routes the runtime's own error/warning messages to `callback` (may be called on any thread).
|
||||
ffxReturnCode_t set_message_callback(ffxApiMessage callback, uint32_t level)
|
||||
{
|
||||
ffxConfigureDescGlobalDebug desc = {};
|
||||
desc.header.type = FFX_API_CONFIGURE_DESC_TYPE_GLOBALDEBUG;
|
||||
desc.effectId = FFX_API_EFFECT_ID_DENOISER;
|
||||
desc.fpMessage = callback;
|
||||
desc.debugLevel = level;
|
||||
return configure(nullptr, &desc.header);
|
||||
}
|
||||
|
||||
// The two-call version enumeration AMD's sample uses, against the real device. Zero
|
||||
// versions is the "no provider for this device" outcome, distinct from a query error.
|
||||
bool query_providers(ID3D12Device* device)
|
||||
{
|
||||
versionIds.clear();
|
||||
versionNames.clear();
|
||||
uint64_t count = 0;
|
||||
ffxQueryDescGetVersions desc = {};
|
||||
desc.header.type = FFX_API_QUERY_DESC_TYPE_GET_VERSIONS;
|
||||
desc.createDescType = FFX_API_EFFECT_ID_DENOISER;
|
||||
desc.device = device;
|
||||
desc.outputCount = &count;
|
||||
queryResult = query(nullptr, &desc.header);
|
||||
if (queryResult != FFX_API_RETURN_OK || count == 0)
|
||||
{
|
||||
stage = FsrRrStage::NoProvider;
|
||||
return false;
|
||||
}
|
||||
versionIds.resize(count);
|
||||
std::vector<const char*> names(count, nullptr);
|
||||
desc.versionIds = versionIds.data();
|
||||
desc.versionNames = names.data();
|
||||
queryResult = query(nullptr, &desc.header);
|
||||
if (queryResult != FFX_API_RETURN_OK || count == 0)
|
||||
{
|
||||
stage = FsrRrStage::NoProvider;
|
||||
return false;
|
||||
}
|
||||
versionIds.resize(count);
|
||||
for (uint64_t index = 0; index < count; ++index)
|
||||
versionNames.emplace_back(names[index] != nullptr ? names[index] : "");
|
||||
providerId = versionIds[0];
|
||||
providerName = versionNames[0];
|
||||
stage = FsrRrStage::Ready;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// GPU memory the denoiser would allocate for this configuration (total, aliasable).
|
||||
inline ffxReturnCode_t fsr_rr_query_memory(FsrRrRuntime& runtime, ID3D12Device* device, uint32_t width,
|
||||
uint32_t height, uint32_t signals, uint32_t flags,
|
||||
FfxApiEffectMemoryUsage& usage)
|
||||
{
|
||||
ffxOverrideVersion version = {};
|
||||
version.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION;
|
||||
version.versionId = runtime.providerId;
|
||||
ffxQueryDescDenoiserGetGPUMemoryUsage desc = {};
|
||||
desc.header.type = FFX_API_QUERY_DESC_TYPE_DENOISER_GPU_MEMORY_USAGE;
|
||||
desc.header.pNext = &version.header;
|
||||
desc.device = device;
|
||||
desc.maxRenderSize = {width, height};
|
||||
desc.signalFlags = signals;
|
||||
desc.flags = flags;
|
||||
desc.gpuMemoryUsage = &usage;
|
||||
return runtime.query(nullptr, &desc.header);
|
||||
}
|
||||
|
||||
// Creates one denoiser context on the enumerated provider. The descriptor chain only has to live
|
||||
// for the call: ffx_api.h requires pointers *inside* descriptors to outlive the context, and
|
||||
// none of these carry any.
|
||||
inline ffxReturnCode_t fsr_rr_create_context(FsrRrRuntime& runtime, ID3D12Device* device, uint32_t maxWidth,
|
||||
uint32_t maxHeight, uint32_t signals, uint32_t flags,
|
||||
ffxContext& context)
|
||||
{
|
||||
ffxCreateContextDescDenoiser desc = {};
|
||||
desc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_DENOISER;
|
||||
desc.version = FFX_DENOISER_VERSION;
|
||||
desc.maxRenderSize = {maxWidth, maxHeight};
|
||||
desc.signalFlags = signals;
|
||||
desc.flags = flags;
|
||||
ffxCreateBackendDX12Desc backend = {};
|
||||
backend.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12;
|
||||
backend.device = device;
|
||||
ffxOverrideVersion version = {};
|
||||
version.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION;
|
||||
version.versionId = runtime.providerId;
|
||||
desc.header.pNext = &backend.header;
|
||||
backend.header.pNext = &version.header;
|
||||
context = nullptr;
|
||||
return runtime.createContext(&context, &desc.header, nullptr);
|
||||
}
|
||||
|
||||
// Asks a live context which provider actually serves it (the runtime's own account, logged as
|
||||
// the "actual backend").
|
||||
inline std::string fsr_rr_context_provider(FsrRrRuntime& runtime, ffxContext& context, uint64_t* id = nullptr)
|
||||
{
|
||||
ffxQueryGetProviderVersion desc = {};
|
||||
desc.header.type = FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION;
|
||||
if (runtime.query(&context, &desc.header) != FFX_API_RETURN_OK || desc.versionName == nullptr)
|
||||
return std::string();
|
||||
if (id != nullptr)
|
||||
*id = desc.versionId;
|
||||
return desc.versionName;
|
||||
}
|
||||
|
||||
// An FfxApiResource for a 2D texture whose description the caller already knows (the shim never
|
||||
// calls GetDesc() by value; see above).
|
||||
inline FfxApiResource fsr_rr_texture(ID3D12Resource* resource, const D3D12_RESOURCE_DESC& desc, uint32_t format,
|
||||
uint32_t state)
|
||||
{
|
||||
FfxApiResource result = {};
|
||||
result.resource = resource;
|
||||
result.state = state;
|
||||
if (resource == nullptr)
|
||||
return result;
|
||||
result.description.type = FFX_API_RESOURCE_TYPE_TEXTURE2D;
|
||||
result.description.format = format;
|
||||
result.description.width = static_cast<uint32_t>(desc.Width);
|
||||
result.description.height = desc.Height;
|
||||
result.description.depth = desc.DepthOrArraySize;
|
||||
result.description.mipCount = desc.MipLevels;
|
||||
result.description.usage = FFX_API_RESOURCE_USAGE_READ_ONLY;
|
||||
if (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS)
|
||||
result.description.usage |= FFX_API_RESOURCE_USAGE_UAV;
|
||||
return result;
|
||||
}
|
||||
|
||||
// The FFX surface formats the adapter's own intermediate textures use.
|
||||
inline uint32_t fsr_rr_surface_format(DXGI_FORMAT format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case DXGI_FORMAT_R32_FLOAT: return FFX_API_SURFACE_FORMAT_R32_FLOAT;
|
||||
case DXGI_FORMAT_R16G16B16A16_FLOAT: return FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT;
|
||||
case DXGI_FORMAT_R32G32B32A32_FLOAT: return FFX_API_SURFACE_FORMAT_R32G32B32A32_FLOAT;
|
||||
case DXGI_FORMAT_R10G10B10A2_UNORM: return FFX_API_SURFACE_FORMAT_R10G10B10A2_UNORM;
|
||||
case DXGI_FORMAT_R8G8B8A8_UNORM: return FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM;
|
||||
case DXGI_FORMAT_R16_FLOAT: return FFX_API_SURFACE_FORMAT_R16_FLOAT;
|
||||
case DXGI_FORMAT_R8_UNORM: return FFX_API_SURFACE_FORMAT_R8_UNORM;
|
||||
default: return FFX_API_SURFACE_FORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
@@ -0,0 +1,172 @@
|
||||
// AMD's FSR Ray Regeneration runtime in a process that reports an NVIDIA GPU to the game.
|
||||
//
|
||||
// d4r's DLSS games run with WINE_HIDE_AMD_GPU (PROTON_FORCE_NVAPI): Vulkan, and so DXGI and
|
||||
// vkd3d-proton, report vendor 0x10de, which is what lets the game offer DLSS at all. vkd3d-proton
|
||||
// loads Proton's amdxc64.dll - the shim standing in for AMD's D3D12 driver-extension DLL, which
|
||||
// forwards to vkd3d-proton's own extensions - only when Vulkan reports vendor 0x1002
|
||||
// (d3d12_device_init_vendor_hacks), so under the spoof it never does. AMD's runtimes look that module
|
||||
// up by name, and without it the unmodified denoiser 1.2.0 DLL enumerates no provider for the same
|
||||
// RX 7700 XT it serves without the spoof (tools/fsr_rr_probe.cpp, 2026-10-13).
|
||||
//
|
||||
// For a GPU that sysfs says is AMD's, the module is loaded here exactly as vkd3d-proton itself would
|
||||
// have. The game's view of the GPU is not changed, and nothing of AMD's is patched. The real PCI ids
|
||||
// are read from sysfs (Z:\sys\bus\pci\devices\...) at the address Vulkan reports for vkd3d-proton's
|
||||
// physical device, which the vendor-id spoof does not touch: nothing is assumed without that evidence.
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <d3d12.h>
|
||||
#include <dxgi1_4.h>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
#include "d4r_vkd3d_interop.h"
|
||||
|
||||
struct D4rPciIdentity
|
||||
{
|
||||
uint32_t vendor = 0, device = 0;
|
||||
bool known = false;
|
||||
};
|
||||
|
||||
// The identity the D3D12 device's own DXGI adapter reports (spoofed or not).
|
||||
inline D4rPciIdentity d4r_dxgi_identity(ID3D12Device* device)
|
||||
{
|
||||
D4rPciIdentity identity;
|
||||
LUID luid;
|
||||
device->GetAdapterLuid(&luid);
|
||||
IDXGIFactory4* factory = nullptr;
|
||||
if (FAILED(CreateDXGIFactory2(0, __uuidof(IDXGIFactory4), reinterpret_cast<void**>(&factory))))
|
||||
return identity;
|
||||
IDXGIAdapter1* adapter = nullptr;
|
||||
if (SUCCEEDED(factory->EnumAdapterByLuid(luid, __uuidof(IDXGIAdapter1), reinterpret_cast<void**>(&adapter))))
|
||||
{
|
||||
DXGI_ADAPTER_DESC1 desc = {};
|
||||
if (SUCCEEDED(adapter->GetDesc1(&desc)))
|
||||
identity = {desc.VendorId, desc.DeviceId, true};
|
||||
adapter->Release();
|
||||
}
|
||||
factory->Release();
|
||||
return identity;
|
||||
}
|
||||
|
||||
inline bool d4r_read_hex_file(const char* path, uint32_t& value)
|
||||
{
|
||||
FILE* file = std::fopen(path, "rb");
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
char text[32] = {};
|
||||
const size_t length = std::fread(text, 1, sizeof(text) - 1, file);
|
||||
std::fclose(file);
|
||||
if (length == 0)
|
||||
return false;
|
||||
char* end = nullptr;
|
||||
const unsigned long parsed = std::strtoul(text, &end, 16);
|
||||
if (end == text)
|
||||
return false;
|
||||
value = static_cast<uint32_t>(parsed);
|
||||
return true;
|
||||
}
|
||||
|
||||
// The real PCI identity of the GPU behind a vkd3d-proton device: Vulkan's PCI bus address, then that
|
||||
// device's sysfs vendor/device files.
|
||||
inline D4rPciIdentity d4r_true_identity(ID3D12Device* device, std::string& detail)
|
||||
{
|
||||
D4rPciIdentity identity;
|
||||
ID3D12DXVKInteropDevice1* interop = nullptr;
|
||||
if (FAILED(device->QueryInterface(__uuidof(ID3D12DXVKInteropDevice1), reinterpret_cast<void**>(&interop))))
|
||||
{
|
||||
detail = "not a vkd3d-proton device";
|
||||
return identity;
|
||||
}
|
||||
VkInstance instance = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice physical = VK_NULL_HANDLE;
|
||||
VkDevice vkDevice = VK_NULL_HANDLE;
|
||||
const HRESULT handles = interop->GetVulkanHandles(&instance, &physical, &vkDevice);
|
||||
interop->Release();
|
||||
HMODULE vulkan = GetModuleHandleA("vulkan-1.dll");
|
||||
if (vulkan == nullptr)
|
||||
vulkan = LoadLibraryA("vulkan-1.dll");
|
||||
auto instanceProc = vulkan != nullptr ? reinterpret_cast<PFN_vkGetInstanceProcAddr>(
|
||||
reinterpret_cast<void*>(GetProcAddress(vulkan, "vkGetInstanceProcAddr")))
|
||||
: nullptr;
|
||||
auto properties2 = instanceProc != nullptr && SUCCEEDED(handles)
|
||||
? reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
|
||||
instanceProc(instance, "vkGetPhysicalDeviceProperties2"))
|
||||
: nullptr;
|
||||
if (properties2 == nullptr)
|
||||
{
|
||||
detail = "no Vulkan physical-device query";
|
||||
return identity;
|
||||
}
|
||||
VkPhysicalDevicePCIBusInfoPropertiesEXT bus = {};
|
||||
bus.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PCI_BUS_INFO_PROPERTIES_EXT;
|
||||
VkPhysicalDeviceDriverProperties driver = {};
|
||||
driver.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;
|
||||
driver.pNext = &bus;
|
||||
VkPhysicalDeviceProperties2 props = {};
|
||||
props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
|
||||
props.pNext = &driver;
|
||||
properties2(physical, &props);
|
||||
if (bus.pciBus == 0 && bus.pciDevice == 0 && bus.pciDomain == 0 && bus.pciFunction == 0)
|
||||
{
|
||||
detail = "Vulkan reports no PCI bus address";
|
||||
return identity;
|
||||
}
|
||||
char base[96];
|
||||
std::snprintf(base, sizeof(base), "Z:\\sys\\bus\\pci\\devices\\%04x:%02x:%02x.%x\\", bus.pciDomain, bus.pciBus,
|
||||
bus.pciDevice, bus.pciFunction);
|
||||
const std::string vendorPath = std::string(base) + "vendor", devicePath = std::string(base) + "device";
|
||||
if (!d4r_read_hex_file(vendorPath.c_str(), identity.vendor) || !d4r_read_hex_file(devicePath.c_str(), identity.device))
|
||||
{
|
||||
detail = "cannot read " + vendorPath;
|
||||
return identity;
|
||||
}
|
||||
identity.known = true;
|
||||
char text[256];
|
||||
std::snprintf(text, sizeof(text), "PCI %04x:%02x:%02x.%x is %04x:%04x (Vulkan reports %04x:%04x, driver %s)",
|
||||
bus.pciDomain, bus.pciBus, bus.pciDevice, bus.pciFunction, identity.vendor, identity.device,
|
||||
props.properties.vendorID, props.properties.deviceID, driver.driverName);
|
||||
detail = text;
|
||||
return identity;
|
||||
}
|
||||
|
||||
// Makes Proton's amdxc64.dll available to AMD's runtime when the GPU really is AMD's. Returns false
|
||||
// only when it should be there and could not be loaded; `detail` says what was found either way.
|
||||
inline bool d4r_fsr_rr_driver_extension(ID3D12Device* device, std::string& detail)
|
||||
{
|
||||
const D4rPciIdentity reported = d4r_dxgi_identity(device);
|
||||
std::string pci;
|
||||
const D4rPciIdentity real = d4r_true_identity(device, pci);
|
||||
char prefix[512];
|
||||
std::snprintf(prefix, sizeof(prefix), "DXGI reports %04x:%04x; %s", reported.vendor, reported.device,
|
||||
real.known ? pci.c_str() : ("real PCI identity unknown (" + pci + ")").c_str());
|
||||
detail = prefix;
|
||||
if (!real.known || real.vendor != 0x1002)
|
||||
{
|
||||
detail += "; not an AMD GPU by sysfs: amdxc64.dll left alone";
|
||||
return true;
|
||||
}
|
||||
if (GetModuleHandleA("winevulkan.dll") == nullptr)
|
||||
{
|
||||
// A native Windows driver's amdxc64.dll is AMD's own and is not loaded from here.
|
||||
detail += "; not running on winevulkan: amdxc64.dll left alone";
|
||||
return true;
|
||||
}
|
||||
HMODULE extension = GetModuleHandleA("amdxc64.dll");
|
||||
const bool already = extension != nullptr;
|
||||
if (extension == nullptr)
|
||||
extension = LoadLibraryA("amdxc64.dll");
|
||||
if (extension == nullptr)
|
||||
{
|
||||
detail += "; amdxc64.dll (Proton's AMD driver-extension shim) is not available";
|
||||
return false;
|
||||
}
|
||||
char path[MAX_PATH] = {};
|
||||
GetModuleFileNameA(extension, path, MAX_PATH);
|
||||
detail += std::string(already ? "; amdxc64.dll already loaded (" : "; loaded amdxc64.dll (") + path + ")";
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
// The FSR Ray Regeneration backend's own two compute passes (kernels/support/fsr_rr_prepare.comp and
|
||||
// fsr_rr_compose.comp), recorded into the game's D3D12 command list through vkd3d-proton's Vulkan
|
||||
// interop. Descriptors are pushed (VK_KHR_push_descriptor, which vkd3d-proton requires), so nothing
|
||||
// here outlives the command buffer that recorded it except the pipelines.
|
||||
#pragma once
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "d4r_vulkan_staging_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,
|
||||
};
|
||||
|
||||
// One image a pass reads or writes: the view and the layout the image is in while the pass runs.
|
||||
struct D4rFsrRrImage
|
||||
{
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
};
|
||||
|
||||
struct D4rFsrRrPasses
|
||||
{
|
||||
static constexpr uint32_t kPrepareInputs = 10, kPrepareOutputs = 8, kComposeImages = 8;
|
||||
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
VkSampler sampler = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout prepareSet = VK_NULL_HANDLE, composeSet = VK_NULL_HANDLE;
|
||||
VkPipelineLayout prepareLayout = VK_NULL_HANDLE, composeLayout = VK_NULL_HANDLE;
|
||||
VkPipeline preparePipeline = VK_NULL_HANDLE, composePipeline = VK_NULL_HANDLE;
|
||||
PFN_vkCreateSampler createSampler = nullptr;
|
||||
PFN_vkDestroySampler destroySampler = nullptr;
|
||||
PFN_vkCreateDescriptorSetLayout createSetLayout = nullptr;
|
||||
PFN_vkDestroyDescriptorSetLayout destroySetLayout = nullptr;
|
||||
PFN_vkCreatePipelineLayout createLayout = nullptr;
|
||||
PFN_vkDestroyPipelineLayout destroyLayout = nullptr;
|
||||
PFN_vkCreateShaderModule createShader = nullptr;
|
||||
PFN_vkDestroyShaderModule destroyShader = nullptr;
|
||||
PFN_vkCreateComputePipelines createPipelines = nullptr;
|
||||
PFN_vkDestroyPipeline destroyPipeline = nullptr;
|
||||
PFN_vkCmdBindPipeline bindPipeline = nullptr;
|
||||
PFN_vkCmdPushConstants push = nullptr;
|
||||
PFN_vkCmdPushDescriptorSetKHR pushDescriptors = nullptr;
|
||||
PFN_vkCmdDispatch dispatch = nullptr;
|
||||
PFN_vkCmdPipelineBarrier barrier = nullptr;
|
||||
PFN_vkCreateImageView createView = nullptr;
|
||||
PFN_vkDestroyImageView destroyView = nullptr;
|
||||
|
||||
bool pipeline(const uint32_t* code, size_t bytes, VkPipelineLayout layout, VkPipeline& result)
|
||||
{
|
||||
VkShaderModuleCreateInfo shader = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
shader.codeSize = bytes;
|
||||
shader.pCode = code;
|
||||
VkShaderModule module = VK_NULL_HANDLE;
|
||||
if (createShader(device, &shader, nullptr, &module) != VK_SUCCESS)
|
||||
return false;
|
||||
VkComputePipelineCreateInfo info = {VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
|
||||
info.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
|
||||
info.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
info.stage.module = module;
|
||||
info.stage.pName = "main";
|
||||
info.layout = layout;
|
||||
const VkResult status = createPipelines(device, VK_NULL_HANDLE, 1, &info, nullptr, &result);
|
||||
destroyShader(device, module, nullptr);
|
||||
return status == VK_SUCCESS;
|
||||
}
|
||||
|
||||
bool set_layout(const VkDescriptorType* types, uint32_t count, uint32_t pushBytes, VkDescriptorSetLayout& set,
|
||||
VkPipelineLayout& layout)
|
||||
{
|
||||
VkDescriptorSetLayoutBinding bindings[kPrepareInputs + kPrepareOutputs] = {};
|
||||
for (uint32_t i = 0; i < count; ++i)
|
||||
{
|
||||
bindings[i].binding = i;
|
||||
bindings[i].descriptorType = types[i];
|
||||
bindings[i].descriptorCount = 1;
|
||||
bindings[i].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
bindings[i].pImmutableSamplers = types[i] == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ? &sampler : nullptr;
|
||||
}
|
||||
VkDescriptorSetLayoutCreateInfo info = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
|
||||
info.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR;
|
||||
info.bindingCount = count;
|
||||
info.pBindings = bindings;
|
||||
if (createSetLayout(device, &info, nullptr, &set) != VK_SUCCESS)
|
||||
return false;
|
||||
VkPushConstantRange range = {VK_SHADER_STAGE_COMPUTE_BIT, 0, pushBytes};
|
||||
VkPipelineLayoutCreateInfo pipelineLayout = {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
|
||||
pipelineLayout.setLayoutCount = 1;
|
||||
pipelineLayout.pSetLayouts = &set;
|
||||
pipelineLayout.pushConstantRangeCount = 1;
|
||||
pipelineLayout.pPushConstantRanges = ⦥
|
||||
return createLayout(device, &pipelineLayout, nullptr, &layout) == VK_SUCCESS;
|
||||
}
|
||||
|
||||
// `maxPushConstants`: VkPhysicalDeviceLimits::maxPushConstantsSize (the prepare pass needs 224).
|
||||
bool init(VkDevice d, PFN_vkGetDeviceProcAddr proc, uint32_t maxPushConstants)
|
||||
{
|
||||
device = d;
|
||||
if (maxPushConstants < sizeof(D4rFsrRrPrepareConstants))
|
||||
return false;
|
||||
#define D4R_LOAD(member, name) \
|
||||
member = reinterpret_cast<decltype(member)>(proc(d, "vk" #name)); \
|
||||
if (!member) \
|
||||
return false
|
||||
D4R_LOAD(createSampler, CreateSampler);
|
||||
D4R_LOAD(destroySampler, DestroySampler);
|
||||
D4R_LOAD(createSetLayout, CreateDescriptorSetLayout);
|
||||
D4R_LOAD(destroySetLayout, DestroyDescriptorSetLayout);
|
||||
D4R_LOAD(createLayout, CreatePipelineLayout);
|
||||
D4R_LOAD(destroyLayout, DestroyPipelineLayout);
|
||||
D4R_LOAD(createShader, CreateShaderModule);
|
||||
D4R_LOAD(destroyShader, DestroyShaderModule);
|
||||
D4R_LOAD(createPipelines, CreateComputePipelines);
|
||||
D4R_LOAD(destroyPipeline, DestroyPipeline);
|
||||
D4R_LOAD(bindPipeline, CmdBindPipeline);
|
||||
D4R_LOAD(push, CmdPushConstants);
|
||||
D4R_LOAD(pushDescriptors, CmdPushDescriptorSetKHR);
|
||||
D4R_LOAD(dispatch, CmdDispatch);
|
||||
D4R_LOAD(barrier, CmdPipelineBarrier);
|
||||
D4R_LOAD(createView, CreateImageView);
|
||||
D4R_LOAD(destroyView, DestroyImageView);
|
||||
#undef D4R_LOAD
|
||||
VkSamplerCreateInfo nearest = {VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
|
||||
nearest.magFilter = nearest.minFilter = VK_FILTER_NEAREST;
|
||||
nearest.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||
nearest.addressModeU = nearest.addressModeV = nearest.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
if (createSampler(d, &nearest, nullptr, &sampler) != VK_SUCCESS)
|
||||
return false;
|
||||
VkDescriptorType prepare[kPrepareInputs + kPrepareOutputs];
|
||||
for (uint32_t i = 0; i < kPrepareInputs + kPrepareOutputs; ++i)
|
||||
prepare[i] = i < kPrepareInputs ? VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER : VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
||||
VkDescriptorType compose[kComposeImages];
|
||||
for (VkDescriptorType& type : compose)
|
||||
type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
||||
return set_layout(prepare, kPrepareInputs + kPrepareOutputs, sizeof(D4rFsrRrPrepareConstants), prepareSet,
|
||||
prepareLayout) &&
|
||||
set_layout(compose, kComposeImages, 16, composeSet, composeLayout) &&
|
||||
pipeline(d4r_vk_fsr_rr_prepare, sizeof(d4r_vk_fsr_rr_prepare), prepareLayout, preparePipeline) &&
|
||||
pipeline(d4r_vk_fsr_rr_compose, sizeof(d4r_vk_fsr_rr_compose), composeLayout, composePipeline);
|
||||
}
|
||||
|
||||
void destroy()
|
||||
{
|
||||
if (device == VK_NULL_HANDLE || destroyPipeline == nullptr)
|
||||
return;
|
||||
destroyPipeline(device, preparePipeline, nullptr);
|
||||
destroyPipeline(device, composePipeline, nullptr);
|
||||
destroyLayout(device, prepareLayout, nullptr);
|
||||
destroyLayout(device, composeLayout, nullptr);
|
||||
destroySetLayout(device, prepareSet, nullptr);
|
||||
destroySetLayout(device, composeSet, nullptr);
|
||||
destroySampler(device, sampler, nullptr);
|
||||
*this = D4rFsrRrPasses{};
|
||||
}
|
||||
|
||||
// Everything the D3D12 side wrote before is visible to the pass, and the pass's writes to what
|
||||
// the D3D12 side records after.
|
||||
void full_barrier(VkCommandBuffer cmd)
|
||||
{
|
||||
VkMemoryBarrier memory = {VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
memory.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
memory.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
barrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &memory, 0, nullptr,
|
||||
0, nullptr);
|
||||
}
|
||||
|
||||
void record(VkCommandBuffer cmd, VkPipeline pipeline, VkPipelineLayout layout, const D4rFsrRrImage* images,
|
||||
uint32_t count, uint32_t sampledCount, const void* constants, uint32_t constantBytes, uint32_t width,
|
||||
uint32_t height)
|
||||
{
|
||||
VkDescriptorImageInfo infos[kPrepareInputs + kPrepareOutputs] = {};
|
||||
VkWriteDescriptorSet writes[kPrepareInputs + kPrepareOutputs] = {};
|
||||
for (uint32_t i = 0; i < count; ++i)
|
||||
{
|
||||
infos[i].sampler = i < sampledCount ? sampler : VK_NULL_HANDLE;
|
||||
infos[i].imageView = images[i].view;
|
||||
infos[i].imageLayout = images[i].layout;
|
||||
writes[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writes[i].dstBinding = i;
|
||||
writes[i].descriptorCount = 1;
|
||||
writes[i].descriptorType =
|
||||
i < sampledCount ? VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER : VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
||||
writes[i].pImageInfo = &infos[i];
|
||||
}
|
||||
bindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
|
||||
pushDescriptors(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, count, writes);
|
||||
push(cmd, layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, constantBytes, constants);
|
||||
dispatch(cmd, (width + 7) / 8, (height + 7) / 8, 1);
|
||||
}
|
||||
|
||||
// images: the 10 sampled inputs, then the 8 storage outputs (fsr_rr_prepare.comp's bindings)
|
||||
void record_prepare(VkCommandBuffer cmd, const D4rFsrRrImage* images, const D4rFsrRrPrepareConstants& constants,
|
||||
uint32_t width, uint32_t height)
|
||||
{
|
||||
full_barrier(cmd);
|
||||
record(cmd, preparePipeline, prepareLayout, images, kPrepareInputs + kPrepareOutputs, kPrepareInputs,
|
||||
&constants, sizeof(constants), width, height);
|
||||
full_barrier(cmd);
|
||||
}
|
||||
|
||||
// images: denoised diffuse, denoised specular, diffuse albedo, specular albedo, residual, composed, noisy diffuse,
|
||||
// noisy specular. haloLimit: fsr_rr_compose.comp's limit (0: off)
|
||||
void record_compose(VkCommandBuffer cmd, const D4rFsrRrImage* images, uint32_t width, uint32_t height,
|
||||
float haloLimit = 0.0f)
|
||||
{
|
||||
uint32_t limitBits;
|
||||
std::memcpy(&limitBits, &haloLimit, sizeof(limitBits));
|
||||
const uint32_t constants[4] = {width, height, limitBits, 0};
|
||||
full_barrier(cmd);
|
||||
record(cmd, composePipeline, composeLayout, images, kComposeImages, 0, constants, sizeof(constants), width,
|
||||
height);
|
||||
full_barrier(cmd);
|
||||
}
|
||||
};
|
||||
@@ -11,6 +11,13 @@ typedef struct D4rMotionDilationParams
|
||||
uint32_t radius, reversed_depth;
|
||||
} D4rMotionDilationParams;
|
||||
|
||||
/* Exact LDS specialization; allocation padding is outside this active grid. */
|
||||
static inline int d4r_motion_dilation_lds_eligible(const D4rMotionDilationParams* p)
|
||||
{
|
||||
return p->motion_width == p->render_width && p->motion_height == p->render_height &&
|
||||
p->jitter_x == 0 && p->jitter_y == 0 && p->radius >= 1 && p->radius <= 2;
|
||||
}
|
||||
|
||||
/* Low-resolution velocity and depth share the render raster grid. Allocation
|
||||
padding is not part of that grid; retain the original NGX flags and MV scale.
|
||||
Display-resolution velocity still needs the jittered depth-grid mapping. */
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
typedef struct D4rNativeMotionParams
|
||||
{
|
||||
uint64_t depth,geometry,scene,matrix,output;
|
||||
uint64_t depth_pitch,geometry_pitch,output_pitch;
|
||||
uint32_t width,height,render_width,render_height;
|
||||
} D4rNativeMotionParams;
|
||||
#ifdef __cplusplus
|
||||
static_assert(sizeof(D4rNativeMotionParams)==80);
|
||||
#endif
|
||||
|
||||
/* Native AW2 hardware-depth contract: immutable input planes; disjoint output. */
|
||||
static inline int d4r_native_motion_parameters_valid(const D4rNativeMotionParams* p)
|
||||
{
|
||||
uint64_t pointers[5],sizes[5],pitches[3],row;
|
||||
unsigned int i;
|
||||
if(!p || !p->width || !p->height || p->width>4096 || p->height>4096 ||
|
||||
!p->render_width || !p->render_height || p->render_width>p->width || p->render_height>p->height) return 0;
|
||||
row=(uint64_t)p->width*4;
|
||||
pitches[0]=p->depth_pitch;pitches[1]=p->geometry_pitch;pitches[2]=p->output_pitch;
|
||||
for(i=0;i<3;i++)
|
||||
{
|
||||
if(pitches[i]<row || pitches[i]%4 || (p->height>1 && pitches[i]>(UINT64_MAX-row)/(p->height-1))) return 0;
|
||||
}
|
||||
pointers[0]=p->depth;pointers[1]=p->geometry;pointers[2]=p->scene;pointers[3]=p->matrix;pointers[4]=p->output;
|
||||
sizes[0]=(p->height-1)*p->depth_pitch+row;sizes[1]=(p->height-1)*p->geometry_pitch+row;
|
||||
sizes[2]=71*16;sizes[3]=4*16;sizes[4]=(p->height-1)*p->output_pitch+row;
|
||||
for(i=0;i<5;i++) if(!pointers[i] || pointers[i]%4 || pointers[i]>UINT64_MAX-sizes[i]) return 0;
|
||||
for(i=0;i<4;i++)
|
||||
if(pointers[4]<pointers[i]+sizes[i] && pointers[i]<pointers[4]+sizes[4]) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Additive thin-field ABI: depth coverage is jittered, output velocity grid is not.
|
||||
typedef struct D4rNativeMotionThinParams
|
||||
{
|
||||
D4rNativeMotionParams base;
|
||||
float jitter_x, jitter_y;
|
||||
} D4rNativeMotionThinParams;
|
||||
#ifdef __cplusplus
|
||||
static_assert(sizeof(D4rNativeMotionThinParams)==88);
|
||||
#endif
|
||||
|
||||
// Preserve the game's vectors outside thin native-silhouette footprints.
|
||||
typedef struct D4rNativeMotionHybridParams
|
||||
{
|
||||
D4rNativeMotionThinParams native;
|
||||
uint64_t game_motion,game_motion_pitch;
|
||||
} D4rNativeMotionHybridParams;
|
||||
#ifdef __cplusplus
|
||||
static_assert(sizeof(D4rNativeMotionHybridParams)==104);
|
||||
#endif
|
||||
static inline int d4r_native_motion_hybrid_parameters_valid(const D4rNativeMotionHybridParams* p)
|
||||
{
|
||||
if(!p || !d4r_native_motion_parameters_valid(&p->native.base) || !p->game_motion || p->game_motion%4 ||
|
||||
!(p->native.jitter_x>=-1 && p->native.jitter_x<=1 && p->native.jitter_y>=-1 && p->native.jitter_y<=1)) return 0;
|
||||
const D4rNativeMotionParams* b=&p->native.base;
|
||||
const uint64_t row=(uint64_t)b->render_width*4;
|
||||
if(p->game_motion_pitch<row || p->game_motion_pitch%4 ||
|
||||
(b->render_height>1 && p->game_motion_pitch>(UINT64_MAX-row)/(b->render_height-1))) return 0;
|
||||
const uint64_t size=(b->render_height-1)*p->game_motion_pitch+row;
|
||||
const uint64_t output_size=(b->height-1)*b->output_pitch+(uint64_t)b->width*4;
|
||||
if(p->game_motion>UINT64_MAX-size || (p->game_motion<b->output+output_size && b->output<p->game_motion+size)) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Packed silhouette mask and per-pixel wire axes, prepared once for this frame.
|
||||
typedef struct D4rNativeMotionPreparedParams
|
||||
{
|
||||
D4rNativeMotionThinParams native;
|
||||
uint64_t thin_mask,wire_axes;
|
||||
uint32_t wire_flags,reserved;
|
||||
} D4rNativeMotionPreparedParams;
|
||||
#ifdef __cplusplus
|
||||
static_assert(sizeof(D4rNativeMotionPreparedParams)==112);
|
||||
#endif
|
||||
static inline int d4r_native_motion_prepared_parameters_valid(const D4rNativeMotionPreparedParams* p)
|
||||
{
|
||||
if(!p || !d4r_native_motion_parameters_valid(&p->native.base) || !p->thin_mask || p->thin_mask%4 ||
|
||||
!p->wire_axes || p->wire_axes%4 || (p->wire_flags & ~3u) || p->reserved ||
|
||||
!(p->native.jitter_x>=-1 && p->native.jitter_x<=1 && p->native.jitter_y>=-1 && p->native.jitter_y<=1)) return 0;
|
||||
const D4rNativeMotionParams* b=&p->native.base;
|
||||
const uint64_t mask_size=(uint64_t)((b->width+31)/32)*b->height*4,axes_size=(uint64_t)b->width*b->height;
|
||||
if(p->thin_mask>UINT64_MAX-mask_size || p->wire_axes>UINT64_MAX-axes_size ||
|
||||
(p->thin_mask<p->wire_axes+axes_size && p->wire_axes<p->thin_mask+mask_size)) return 0;
|
||||
const uint64_t pointers[5]={b->depth,b->geometry,b->scene,b->matrix,b->output};
|
||||
const uint64_t sizes[5]={(b->height-1)*b->depth_pitch+(uint64_t)b->width*4,
|
||||
(b->height-1)*b->geometry_pitch+(uint64_t)b->width*4,1136,64,
|
||||
(b->height-1)*b->output_pitch+(uint64_t)b->width*4};
|
||||
for(unsigned i=0;i<5;i++) if((p->thin_mask<pointers[i]+sizes[i] && pointers[i]<p->thin_mask+mask_size) ||
|
||||
(p->wire_axes<pointers[i]+sizes[i] && pointers[i]<p->wire_axes+axes_size)) return 0;
|
||||
return 1;
|
||||
}
|
||||
+1488
-112
File diff suppressed because it is too large.
Load diff
@@ -514,7 +514,7 @@ static void native_vk_run(Feature* feature, NativeVkFrame* frame)
|
||||
const auto downloaded = ProfileClock::now();
|
||||
if (!destination.cuda) std::memcpy(destination.mapped, feature->outputHost[0].bytes, feature->outputHost[0].size());
|
||||
feature->completedFrames++;
|
||||
if (frame->number <= 3 || frame->number % 120 == 0 || profile_enabled())
|
||||
if (profile_enabled() ? profile_log_frame(frame->number) : (frame->number <= 3 || frame->number % 120 == 0))
|
||||
logf("D4R_VULKAN_PROFILE frame=%u stage=%.3f upload=%.3f evaluate=%.3f download=%.3f encode=%.3f ms",
|
||||
frame->number, profile_ms(stageStart, staged), profile_ms(staged, uploaded),
|
||||
profile_ms(uploaded, evaluatedAt), profile_ms(evaluatedAt, downloaded),
|
||||
@@ -539,7 +539,7 @@ static void native_vk_run(Feature* feature, NativeVkFrame* frame)
|
||||
if (g_nativeVk.setEvent(g_nativeVk.device, frame->outputReady) != VK_SUCCESS)
|
||||
frame->result = NGX_FAIL_PLATFORM_ERROR;
|
||||
frame->done = true;
|
||||
if (frame->number <= 3 || frame->number % 120 == 0 || profile_enabled())
|
||||
if (profile_enabled() ? profile_log_frame(frame->number) : (frame->number <= 3 || frame->number % 120 == 0))
|
||||
logf("D4R_VULKAN_WAIT frame=%u input_ready=%.3f signal=%.3f total=%.3f ms",
|
||||
frame->number, profile_ms(waitStarted, inputsReadyAt),
|
||||
profile_ms(signalStarted, ProfileClock::now()), profile_ms(waitStarted, ProfileClock::now()));
|
||||
@@ -857,7 +857,7 @@ D4R_EXPORT NgxResult NVSDK_NGX_VULKAN_EvaluateFeature(VkCommandBuffer cmd, const
|
||||
// write and all earlier commands which reference the staging objects.
|
||||
g_nativeVk.cmdSetEvent(cmd, frame->consumed, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
|
||||
owner.frames.push_back(frame);
|
||||
if (frame->number <= 3 || frame->number % 120 == 0 || profile_enabled())
|
||||
if (profile_enabled() ? profile_log_frame(frame->number) : (frame->number <= 3 || frame->number % 120 == 0))
|
||||
logf("D4R_VULKAN_API frame=%u reclaim=%.3f allocate=%.3f record=%.3f total=%.3f ms",
|
||||
frame->number, profile_ms(apiStarted, reapedAt), profile_ms(reapedAt, allocatedAt),
|
||||
profile_ms(allocatedAt, ProfileClock::now()), profile_ms(apiStarted, ProfileClock::now()));
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#define D4R_WIRE_HISTORY_CONSERVATIVE 1u
|
||||
#define D4R_WIRE_HISTORY_SKY_ONLY 2u
|
||||
typedef struct D4rWireHistoryParams
|
||||
{
|
||||
uint64_t current, color, native_depth, motion;
|
||||
uint64_t previous, previous_age, previous_depth;
|
||||
uint64_t output, output_age, output_depth, input_depth, output_history;
|
||||
uint64_t color_pitch, depth_pitch, motion_pitch, output_pitch, input_depth_pitch;
|
||||
uint32_t width, height, render_width, render_height, history_valid;
|
||||
float jitter_x, jitter_y;
|
||||
uint32_t reserved;
|
||||
} D4rWireHistoryParams;
|
||||
#ifdef __cplusplus
|
||||
static_assert(sizeof(D4rWireHistoryParams)==168);
|
||||
#endif
|
||||
static inline int d4r_wire_history_parameters_valid(const D4rWireHistoryParams* p)
|
||||
{
|
||||
if(!p || !p->width || !p->height || p->width>4096 || p->height>4096 ||
|
||||
p->render_width<12 || p->render_height<12 || p->render_width>p->width || p->render_height>p->height ||
|
||||
p->history_valid>1 || !(p->jitter_x>=-1 && p->jitter_x<=1 && p->jitter_y>=-1 && p->jitter_y<=1) ||
|
||||
!p->current || !p->color || !p->native_depth || !p->motion || !p->previous || !p->previous_age ||
|
||||
!p->previous_depth || !p->output || !p->output_age || !p->output_depth || !p->input_depth || !p->output_history ||
|
||||
p->color_pitch<(uint64_t)p->render_width*8 || p->input_depth_pitch<(uint64_t)p->render_width*4 || p->depth_pitch<(uint64_t)p->width*4 ||
|
||||
p->motion_pitch<(uint64_t)p->width*4 || p->output_pitch<(uint64_t)p->width*8 ||
|
||||
p->current==p->output || p->previous==p->output || p->previous_age==p->output_age ||
|
||||
p->previous_depth==p->output_depth || p->output_history==p->output ||
|
||||
p->output_history==p->current || p->output_history==p->previous) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Sparse publication keeps current immutable until the separate apply dispatch.
|
||||
// Scratch RGB uses the same pitch; patch_mask has one byte per output pixel.
|
||||
typedef struct D4rWireHistorySparseParams
|
||||
{
|
||||
D4rWireHistoryParams base;
|
||||
uint64_t patch_mask;
|
||||
} D4rWireHistorySparseParams;
|
||||
#ifdef __cplusplus
|
||||
static_assert(sizeof(D4rWireHistorySparseParams)==176);
|
||||
#endif
|
||||
static inline int d4r_wire_history_sparse_parameters_valid(const D4rWireHistorySparseParams* p)
|
||||
{
|
||||
uint64_t pointers[12],rows[12],pitches[12],heights[12],bytes,end;
|
||||
unsigned i;
|
||||
if(!p || !d4r_wire_history_parameters_valid(&p->base) || !p->patch_mask) return 0;
|
||||
const D4rWireHistoryParams* q=&p->base;
|
||||
bytes=(uint64_t)q->width*q->height;
|
||||
if(p->patch_mask>UINT64_MAX-bytes) return 0;
|
||||
end=p->patch_mask+bytes;
|
||||
pointers[0]=q->current;pointers[1]=q->color;pointers[2]=q->native_depth;pointers[3]=q->motion;
|
||||
pointers[4]=q->previous;pointers[5]=q->previous_age;pointers[6]=q->previous_depth;
|
||||
pointers[7]=q->output;pointers[8]=q->output_age;pointers[9]=q->output_depth;
|
||||
pointers[10]=q->input_depth;pointers[11]=q->output_history;
|
||||
for(i=0;i<12;i++) { rows[i]=(uint64_t)q->width*8;pitches[i]=q->output_pitch;heights[i]=q->height; }
|
||||
rows[1]=(uint64_t)q->render_width*8;pitches[1]=q->color_pitch;heights[1]=q->render_height;
|
||||
rows[2]=(uint64_t)q->width*4;pitches[2]=q->depth_pitch;
|
||||
rows[3]=(uint64_t)q->width*4;pitches[3]=q->motion_pitch;
|
||||
rows[5]=rows[8]=q->width;pitches[5]=pitches[8]=q->width;
|
||||
rows[6]=rows[9]=(uint64_t)q->width*4;pitches[6]=pitches[9]=(uint64_t)q->width*4;
|
||||
rows[10]=(uint64_t)q->render_width*4;pitches[10]=q->input_depth_pitch;heights[10]=q->render_height;
|
||||
for(i=0;i<12;i++)
|
||||
{
|
||||
if(heights[i]>1 && pitches[i]>(UINT64_MAX-rows[i])/(heights[i]-1)) return 0;
|
||||
bytes=(heights[i]-1)*pitches[i]+rows[i];
|
||||
if(pointers[i]>UINT64_MAX-bytes || (p->patch_mask<pointers[i]+bytes && pointers[i]<end)) return 0;
|
||||
}
|
||||
// current becomes the apply destination: every input and history output must
|
||||
// be disjoint from it, as must the correction scratch allocation.
|
||||
const uint64_t current_bytes=(q->height-1)*q->output_pitch+(uint64_t)q->width*8;
|
||||
const uint64_t scratch_bytes=current_bytes;
|
||||
for(i=1;i<12;i++)
|
||||
{
|
||||
bytes=(heights[i]-1)*pitches[i]+rows[i];
|
||||
if(q->current<pointers[i]+bytes && pointers[i]<q->current+current_bytes) return 0;
|
||||
if(i!=7 && q->output<pointers[i]+bytes && pointers[i]<q->output+scratch_bytes) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Prepared repair preserves rendered output and live history exactly. RGB/depth
|
||||
// history entries with age 255 are unspecified and must never be read.
|
||||
typedef struct D4rWireHistoryPreparedParams
|
||||
{
|
||||
D4rWireHistorySparseParams sparse;
|
||||
uint64_t axes;
|
||||
} D4rWireHistoryPreparedParams;
|
||||
#ifdef __cplusplus
|
||||
static_assert(sizeof(D4rWireHistoryPreparedParams)==184);
|
||||
#endif
|
||||
static inline int d4r_wire_history_prepared_parameters_valid(const D4rWireHistoryPreparedParams* p)
|
||||
{
|
||||
if(!p || !d4r_wire_history_sparse_parameters_valid(&p->sparse) || !p->axes || (p->sparse.base.reserved & ~3u)) return 0;
|
||||
const D4rWireHistoryParams* b=&p->sparse.base;
|
||||
const uint64_t size=(uint64_t)b->width*b->height;
|
||||
if(p->axes>UINT64_MAX-size || (p->axes<p->sparse.patch_mask+size && p->sparse.patch_mask<p->axes+size)) return 0;
|
||||
const uint64_t pointers[12]={b->current,b->color,b->native_depth,b->motion,b->previous,b->previous_age,
|
||||
b->previous_depth,b->output,b->output_age,b->output_depth,b->input_depth,b->output_history};
|
||||
const uint64_t sizes[12]={(b->height-1)*b->output_pitch+(uint64_t)b->width*8,
|
||||
(b->render_height-1)*b->color_pitch+(uint64_t)b->render_width*8,
|
||||
(b->height-1)*b->depth_pitch+(uint64_t)b->width*4,(b->height-1)*b->motion_pitch+(uint64_t)b->width*4,
|
||||
(b->height-1)*b->output_pitch+(uint64_t)b->width*8,size,size*4,
|
||||
(b->height-1)*b->output_pitch+(uint64_t)b->width*8,size,size*4,
|
||||
(b->render_height-1)*b->input_depth_pitch+(uint64_t)b->render_width*4,
|
||||
(b->height-1)*b->output_pitch+(uint64_t)b->width*8};
|
||||
for(unsigned i=0;i<12;i++) if(p->axes<pointers[i]+sizes[i] && pointers[i]<p->axes+size) return 0;
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,695 @@
|
||||
// Standalone check that AMD's FSR Ray Regeneration runtime runs on this D3D12 device, separate from
|
||||
// any game and from the NGX shim. It:
|
||||
// 1. creates a D3D12 device and reports the adapter identity the runtime will see through DXGI,
|
||||
// 2. loads the given amd_fidelityfx_denoiser_dx12.dll (unmodified, by path) and enumerates its
|
||||
// denoiser providers against that device (FFX_API_QUERY_DESC_TYPE_GET_VERSIONS),
|
||||
// 3. creates a context (this is where the runtime builds its shader pipelines) and asks it which
|
||||
// provider it got,
|
||||
// 4. dispatches a synthetic ray-traced scene with per-frame white noise: a static camera, then a
|
||||
// moving one (with exact 2.5D motion vectors), then a camera cut with FFX_DENOISER_DISPATCH_RESET,
|
||||
// 5. reads every output back and compares input and output against the noise-free signals, so a
|
||||
// passthrough or a stale history cannot pass for denoising.
|
||||
// Every stage prints one "stage=... result=..." line, and OUT_DIR/probe.json collects the lot.
|
||||
//
|
||||
// usage: fsr_rr_probe.exe DENOISER_DLL OUT_DIR [--size WxH] [--frames N] [--signals HEX] [--query-only]
|
||||
// [--no-driver-extension]
|
||||
// --no-driver-extension: do not load Proton's amdxc64.dll for an AMD GPU hidden as NVIDIA
|
||||
// (see d4r_fsr_rr_identity.h); reproduces the "no provider" outcome
|
||||
// --signals: FfxApiDenoiserSignalFlags of the radiance signals to run (default 0x36: direct and
|
||||
// indirect diffuse and specular, AMD's four-signal configuration; 0x30 = indirect only)
|
||||
#define WIDL_EXPLICIT_AGGREGATE_RETURNS
|
||||
#include <windows.h>
|
||||
#include <d3d12.h>
|
||||
#include <dxgi1_4.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "d4r_fsr_rr.h"
|
||||
#include "d4r_fsr_rr_identity.h"
|
||||
#include "fsr_rr_scene.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace fsr_scene;
|
||||
|
||||
FILE* g_json = nullptr;
|
||||
bool g_jsonFirst = true;
|
||||
|
||||
void json_raw(const char* format, ...)
|
||||
{
|
||||
if (g_json == nullptr)
|
||||
return;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
std::vfprintf(g_json, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
// One top-level "key": value pair; value is preformatted JSON.
|
||||
void json_field(const char* key, const std::string& value)
|
||||
{
|
||||
json_raw("%s\n \"%s\": %s", g_jsonFirst ? "" : ",", key, value.c_str());
|
||||
g_jsonFirst = false;
|
||||
}
|
||||
|
||||
std::string quoted(const std::string& text)
|
||||
{
|
||||
std::string out = "\"";
|
||||
for (char c : text)
|
||||
{
|
||||
if (c == '"' || c == '\\')
|
||||
out += '\\';
|
||||
if (static_cast<unsigned char>(c) < 0x20)
|
||||
continue;
|
||||
out += c;
|
||||
}
|
||||
return out + "\"";
|
||||
}
|
||||
|
||||
std::string narrow(const wchar_t* text)
|
||||
{
|
||||
char buffer[1024] = {};
|
||||
WideCharToMultiByte(CP_UTF8, 0, text, -1, buffer, sizeof(buffer), nullptr, nullptr);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string format(const char* pattern, ...)
|
||||
{
|
||||
char buffer[2048];
|
||||
va_list args;
|
||||
va_start(args, pattern);
|
||||
std::vsnprintf(buffer, sizeof(buffer), pattern, args);
|
||||
va_end(args);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void stage(const char* name, bool ok, const std::string& detail)
|
||||
{
|
||||
std::printf("stage=%s result=%s %s\n", name, ok ? "ok" : "FAIL", detail.c_str());
|
||||
std::fflush(stdout);
|
||||
json_field((std::string("stage_") + name).c_str(),
|
||||
format("{\"ok\": %s, \"detail\": %s}", ok ? "true" : "false", quoted(detail).c_str()));
|
||||
}
|
||||
|
||||
std::vector<std::string> g_messages;
|
||||
|
||||
void ffx_message(uint32_t type, const wchar_t* message)
|
||||
{
|
||||
const std::string text = narrow(message);
|
||||
std::printf("ffx %s: %s\n", type == FFX_API_MESSAGE_TYPE_WARNING ? "warning" : "error", text.c_str());
|
||||
std::fflush(stdout);
|
||||
if (g_messages.size() < 64)
|
||||
g_messages.push_back(text);
|
||||
}
|
||||
|
||||
// --- D3D12 plumbing -------------------------------------------------------------------
|
||||
|
||||
#define CHECK_HR(expr) \
|
||||
do \
|
||||
{ \
|
||||
const HRESULT hr_ = (expr); \
|
||||
if (FAILED(hr_)) \
|
||||
{ \
|
||||
std::printf("%s failed: 0x%08lx (line %d)\n", #expr, static_cast<unsigned long>(hr_), __LINE__); \
|
||||
return false; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
struct Texture
|
||||
{
|
||||
ID3D12Resource* resource = nullptr;
|
||||
D3D12_RESOURCE_DESC desc = {};
|
||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {};
|
||||
UINT rows = 0;
|
||||
UINT64 rowBytes = 0, total = 0;
|
||||
D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COMMON;
|
||||
};
|
||||
|
||||
ID3D12Device* g_device = nullptr;
|
||||
|
||||
bool make_texture(Texture& texture, uint32_t width, uint32_t height, DXGI_FORMAT format, bool uav,
|
||||
D3D12_RESOURCE_STATES state)
|
||||
{
|
||||
texture.desc = {};
|
||||
texture.desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||
texture.desc.Width = width;
|
||||
texture.desc.Height = height;
|
||||
texture.desc.DepthOrArraySize = 1;
|
||||
texture.desc.MipLevels = 1;
|
||||
texture.desc.Format = format;
|
||||
texture.desc.SampleDesc.Count = 1;
|
||||
texture.desc.Flags = uav ? D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS : D3D12_RESOURCE_FLAG_NONE;
|
||||
D3D12_HEAP_PROPERTIES heap = {};
|
||||
heap.Type = D3D12_HEAP_TYPE_DEFAULT;
|
||||
CHECK_HR(g_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &texture.desc, state, nullptr,
|
||||
IID_PPV_ARGS(&texture.resource)));
|
||||
texture.state = state;
|
||||
g_device->GetCopyableFootprints(&texture.desc, 0, 1, 0, &texture.footprint, &texture.rows, &texture.rowBytes,
|
||||
&texture.total);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool make_buffer(ID3D12Resource*& buffer, UINT64 bytes, D3D12_HEAP_TYPE type, uint8_t*& mapped)
|
||||
{
|
||||
D3D12_RESOURCE_DESC desc = {};
|
||||
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
|
||||
desc.Width = bytes;
|
||||
desc.Height = 1;
|
||||
desc.DepthOrArraySize = 1;
|
||||
desc.MipLevels = 1;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
||||
D3D12_HEAP_PROPERTIES heap = {};
|
||||
heap.Type = type;
|
||||
const D3D12_RESOURCE_STATES state =
|
||||
type == D3D12_HEAP_TYPE_UPLOAD ? D3D12_RESOURCE_STATE_GENERIC_READ : D3D12_RESOURCE_STATE_COPY_DEST;
|
||||
CHECK_HR(g_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, state, nullptr,
|
||||
IID_PPV_ARGS(&buffer)));
|
||||
CHECK_HR(buffer->Map(0, nullptr, reinterpret_cast<void**>(&mapped)));
|
||||
return true;
|
||||
}
|
||||
|
||||
void barrier(ID3D12GraphicsCommandList* list, Texture& texture, D3D12_RESOURCE_STATES after)
|
||||
{
|
||||
if (texture.state == after)
|
||||
return;
|
||||
D3D12_RESOURCE_BARRIER b = {};
|
||||
b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
||||
b.Transition.pResource = texture.resource;
|
||||
b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
||||
b.Transition.StateBefore = texture.state;
|
||||
b.Transition.StateAfter = after;
|
||||
list->ResourceBarrier(1, &b);
|
||||
texture.state = after;
|
||||
}
|
||||
|
||||
// Copies `texelBytes`-sized texels from `source` (tightly packed rows) into the upload buffer at
|
||||
// `offset` with the texture's footprint, and records the copy.
|
||||
void upload(ID3D12GraphicsCommandList* list, Texture& texture, ID3D12Resource* staging, uint8_t* mapped,
|
||||
UINT64& offset, const void* source, size_t texelBytes)
|
||||
{
|
||||
offset = (offset + D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT - 1) & ~UINT64(D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT - 1);
|
||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = texture.footprint;
|
||||
footprint.Offset = offset;
|
||||
const size_t rowBytes = texelBytes * texture.desc.Width;
|
||||
for (UINT row = 0; row < texture.rows; ++row)
|
||||
std::memcpy(mapped + offset + row * footprint.Footprint.RowPitch,
|
||||
static_cast<const uint8_t*>(source) + row * rowBytes, rowBytes);
|
||||
offset += texture.total;
|
||||
barrier(list, texture, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
D3D12_TEXTURE_COPY_LOCATION dst = {};
|
||||
dst.pResource = texture.resource;
|
||||
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
||||
D3D12_TEXTURE_COPY_LOCATION src = {};
|
||||
src.pResource = staging;
|
||||
src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
||||
src.PlacedFootprint = footprint;
|
||||
list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
|
||||
}
|
||||
|
||||
void readback(ID3D12GraphicsCommandList* list, Texture& texture, ID3D12Resource* buffer, UINT64 offset)
|
||||
{
|
||||
barrier(list, texture, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = texture.footprint;
|
||||
footprint.Offset = offset;
|
||||
D3D12_TEXTURE_COPY_LOCATION dst = {};
|
||||
dst.pResource = buffer;
|
||||
dst.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
||||
dst.PlacedFootprint = footprint;
|
||||
D3D12_TEXTURE_COPY_LOCATION src = {};
|
||||
src.pResource = texture.resource;
|
||||
src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
||||
list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
|
||||
}
|
||||
|
||||
// --- metrics and captures -------------------------------------------------------------
|
||||
|
||||
struct Error
|
||||
{
|
||||
double input = 0, output = 0, outputVsPrevious = 0, change = 0;
|
||||
size_t nonFinite = 0;
|
||||
};
|
||||
|
||||
Error measure(const Frame& frame, const Frame* previous, const uint8_t* rows, UINT pitch, uint32_t width,
|
||||
uint32_t height, int signal)
|
||||
{
|
||||
Error e;
|
||||
size_t n = 0;
|
||||
for (uint32_t y = 2; y + 2 < height; ++y)
|
||||
for (uint32_t x = 2; x + 2 < width; ++x)
|
||||
{
|
||||
const size_t i = static_cast<size_t>(y) * width + x;
|
||||
const uint16_t* out = reinterpret_cast<const uint16_t*>(rows + static_cast<size_t>(y) * pitch) + x * 4;
|
||||
for (int c = 0; c < 3; ++c)
|
||||
{
|
||||
const float o = from_half(out[c]);
|
||||
if (!std::isfinite(o))
|
||||
{
|
||||
++e.nonFinite;
|
||||
continue;
|
||||
}
|
||||
if (!frame.valid[i])
|
||||
continue;
|
||||
const float clean = frame.clean[signal][i * 3 + c];
|
||||
const float in = from_half(frame.noisy[signal][i * 4 + c]);
|
||||
e.input += (in - clean) * (in - clean);
|
||||
e.output += (o - clean) * (o - clean);
|
||||
e.change += std::fabs(o - in);
|
||||
if (previous != nullptr && previous->valid[i])
|
||||
{
|
||||
const float old = previous->clean[signal][i * 3 + c];
|
||||
e.outputVsPrevious += (o - old) * (o - old);
|
||||
}
|
||||
++n;
|
||||
}
|
||||
}
|
||||
if (n > 0)
|
||||
{
|
||||
e.input = std::sqrt(e.input / n);
|
||||
e.output = std::sqrt(e.output / n);
|
||||
e.outputVsPrevious = std::sqrt(e.outputVsPrevious / n);
|
||||
e.change /= n;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
// Tonemapped composite (diffuse signals x diffuse albedo + specular signals x specular albedo).
|
||||
void write_ppm(const std::string& path, uint32_t width, uint32_t height, const std::vector<V3>& radiance)
|
||||
{
|
||||
FILE* file = std::fopen(path.c_str(), "wb");
|
||||
if (file == nullptr)
|
||||
return;
|
||||
std::fprintf(file, "P6\n%u %u\n255\n", width, height);
|
||||
std::vector<uint8_t> row(width * 3);
|
||||
for (uint32_t y = 0; y < height; ++y)
|
||||
{
|
||||
for (uint32_t x = 0; x < width; ++x)
|
||||
{
|
||||
const V3 c = radiance[static_cast<size_t>(y) * width + x];
|
||||
const float rgb[3] = {c.x, c.y, c.z};
|
||||
for (int k = 0; k < 3; ++k)
|
||||
{
|
||||
const float mapped = std::pow(std::max(0.0f, rgb[k]) / (1.0f + std::max(0.0f, rgb[k])), 1.0f / 2.2f);
|
||||
row[x * 3 + k] = static_cast<uint8_t>(std::lround(saturate(mapped) * 255.0f));
|
||||
}
|
||||
}
|
||||
std::fwrite(row.data(), 1, row.size(), file);
|
||||
}
|
||||
std::fclose(file);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int wmain(int argc, wchar_t** argv)
|
||||
{
|
||||
if (argc < 3)
|
||||
{
|
||||
std::printf("usage: fsr_rr_probe.exe DENOISER_DLL OUT_DIR [--size WxH] [--frames N] [--signals HEX] [--query-only]\n");
|
||||
return 2;
|
||||
}
|
||||
const std::wstring dllPath = argv[1];
|
||||
const std::string outDir = narrow(argv[2]);
|
||||
uint32_t width = 960, height = 540, frames = 48, signals = 0x36;
|
||||
bool queryOnly = false, driverExtension = true;
|
||||
for (int i = 3; i < argc; ++i)
|
||||
{
|
||||
const std::wstring arg = argv[i];
|
||||
if (arg == L"--size" && i + 1 < argc)
|
||||
std::swscanf(argv[++i], L"%ux%u", &width, &height);
|
||||
else if (arg == L"--frames" && i + 1 < argc)
|
||||
frames = static_cast<uint32_t>(std::wcstoul(argv[++i], nullptr, 0));
|
||||
else if (arg == L"--signals" && i + 1 < argc)
|
||||
signals = static_cast<uint32_t>(std::wcstoul(argv[++i], nullptr, 16));
|
||||
else if (arg == L"--query-only")
|
||||
queryOnly = true;
|
||||
else if (arg == L"--no-driver-extension")
|
||||
driverExtension = false;
|
||||
}
|
||||
signals &= 0x36;
|
||||
if (signals == 0 || frames < 12)
|
||||
{
|
||||
std::printf("need at least one radiance signal and 12 frames\n");
|
||||
return 2;
|
||||
}
|
||||
CreateDirectoryA(outDir.c_str(), nullptr);
|
||||
g_json = std::fopen((outDir + "\\probe.json").c_str(), "w");
|
||||
json_raw("{");
|
||||
json_field("denoiser_dll", quoted(narrow(dllPath.c_str())));
|
||||
json_field("render_size", format("[%u, %u]", width, height));
|
||||
json_field("signal_flags", format("%u", signals));
|
||||
json_field("frames", format("%u", frames));
|
||||
int exitCode = 1;
|
||||
auto finish = [&](int code) {
|
||||
json_raw("\n}\n");
|
||||
if (g_json)
|
||||
std::fclose(g_json);
|
||||
g_json = nullptr;
|
||||
return code;
|
||||
};
|
||||
|
||||
// 1. Device, and the adapter identity DXGI reports (what the runtime can see).
|
||||
IDXGIFactory4* factory = nullptr;
|
||||
IDXGIAdapter1* adapter = nullptr;
|
||||
if (FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&factory))) || FAILED(factory->EnumAdapters1(0, &adapter)))
|
||||
{
|
||||
stage("device", false, "no DXGI adapter");
|
||||
return finish(1);
|
||||
}
|
||||
DXGI_ADAPTER_DESC1 adapterDesc = {};
|
||||
adapter->GetDesc1(&adapterDesc);
|
||||
if (FAILED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&g_device))))
|
||||
{
|
||||
stage("device", false, "D3D12CreateDevice failed");
|
||||
return finish(1);
|
||||
}
|
||||
D3D12_FEATURE_DATA_SHADER_MODEL shaderModel = {D3D_SHADER_MODEL_6_6};
|
||||
g_device->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel));
|
||||
stage("device", true,
|
||||
format("adapter=\"%s\" vendor=0x%04x device=0x%04x luid=%08lx:%08lx shader_model=0x%x",
|
||||
narrow(adapterDesc.Description).c_str(), adapterDesc.VendorId, adapterDesc.DeviceId,
|
||||
static_cast<unsigned long>(adapterDesc.AdapterLuid.HighPart),
|
||||
static_cast<unsigned long>(adapterDesc.AdapterLuid.LowPart),
|
||||
static_cast<unsigned>(shaderModel.HighestShaderModel)));
|
||||
json_field("adapter", format("{\"name\": %s, \"vendor\": %u, \"device\": %u}",
|
||||
quoted(narrow(adapterDesc.Description)).c_str(), adapterDesc.VendorId,
|
||||
adapterDesc.DeviceId));
|
||||
|
||||
// 2. Runtime and provider.
|
||||
FsrRrRuntime runtime;
|
||||
if (!runtime.load(dllPath))
|
||||
{
|
||||
stage("load", false, format("%s (error %lu)", fsr_rr_stage_name(runtime.stage), GetLastError()));
|
||||
return finish(1);
|
||||
}
|
||||
stage("load", true, "mapped=" + narrow(runtime.mappedPath.c_str()));
|
||||
if (driverExtension)
|
||||
{
|
||||
std::string detail;
|
||||
const bool loaded = d4r_fsr_rr_driver_extension(g_device, detail);
|
||||
stage("driver_extension", loaded, detail);
|
||||
}
|
||||
const ffxReturnCode_t debug = runtime.set_message_callback(&ffx_message, FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_VERBOSE);
|
||||
std::printf("ffx global debug callback: %s\n", fsr_rr_return_name(debug));
|
||||
const bool provider = runtime.query_providers(g_device);
|
||||
{
|
||||
std::string list = "[";
|
||||
for (size_t i = 0; i < runtime.versionIds.size(); ++i)
|
||||
list += format("%s{\"id\": \"0x%016llx\", \"name\": %s}", i ? ", " : "",
|
||||
static_cast<unsigned long long>(runtime.versionIds[i]),
|
||||
quoted(runtime.versionNames[i]).c_str());
|
||||
json_field("providers", list + "]");
|
||||
}
|
||||
stage("provider", provider,
|
||||
provider ? format("count=%zu selected=0x%016llx name=\"%s\"", runtime.versionIds.size(),
|
||||
static_cast<unsigned long long>(runtime.providerId), runtime.providerName.c_str())
|
||||
: format("%s (query %s)", fsr_rr_stage_name(runtime.stage), fsr_rr_return_name(runtime.queryResult)));
|
||||
if (!provider || queryOnly)
|
||||
return finish(provider ? 0 : 1);
|
||||
|
||||
FfxApiEffectMemoryUsage memory = {};
|
||||
const ffxReturnCode_t memoryResult = fsr_rr_query_memory(runtime, g_device, width, height, signals, 0, memory);
|
||||
json_field("gpu_memory", format("{\"result\": %u, \"total_mb\": %.2f, \"aliasable_mb\": %.2f}", memoryResult,
|
||||
memory.totalUsageInBytes / 1048576.0, memory.aliasableUsageInBytes / 1048576.0));
|
||||
std::printf("gpu memory query: %s total=%.2f MB aliasable=%.2f MB\n", fsr_rr_return_name(memoryResult),
|
||||
memory.totalUsageInBytes / 1048576.0, memory.aliasableUsageInBytes / 1048576.0);
|
||||
|
||||
// 3. Context (shader pipelines are created here: the slow and the most likely to fail part).
|
||||
ffxContext context = nullptr;
|
||||
const auto createStart = std::chrono::steady_clock::now();
|
||||
const ffxReturnCode_t created = fsr_rr_create_context(runtime, g_device, width, height, signals, 0, context);
|
||||
const double createMs =
|
||||
std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - createStart).count();
|
||||
json_field("context_create_ms", format("%.1f", createMs));
|
||||
if (created != FFX_API_RETURN_OK)
|
||||
{
|
||||
stage("context", false, format("ffxCreateContext -> %s after %.1f ms", fsr_rr_return_name(created), createMs));
|
||||
return finish(1);
|
||||
}
|
||||
uint64_t actualId = 0;
|
||||
const std::string actual = fsr_rr_context_provider(runtime, context, &actualId);
|
||||
stage("context", true, format("%.1f ms, provider in use 0x%016llx \"%s\"", createMs,
|
||||
static_cast<unsigned long long>(actualId), actual.c_str()));
|
||||
json_field("provider_in_use", quoted(actual));
|
||||
|
||||
// 4. Resources and frames.
|
||||
ID3D12CommandQueue* queue = nullptr;
|
||||
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
|
||||
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
|
||||
ID3D12CommandAllocator* allocator = nullptr;
|
||||
ID3D12GraphicsCommandList* list = nullptr;
|
||||
ID3D12Fence* fence = nullptr;
|
||||
if (FAILED(g_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&queue))) ||
|
||||
FAILED(g_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&allocator))) ||
|
||||
FAILED(g_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator, nullptr, IID_PPV_ARGS(&list))) ||
|
||||
FAILED(g_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence))))
|
||||
{
|
||||
stage("dispatch", false, "command objects");
|
||||
return finish(1);
|
||||
}
|
||||
list->Close();
|
||||
HANDLE event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
uint64_t fenceValue = 0;
|
||||
const D3D12_RESOURCE_STATES readState =
|
||||
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
|
||||
Texture depth, motion, normals, diffuseAlbedo, specularAlbedo, input[kSignalCount], output[kSignalCount];
|
||||
bool ok = make_texture(depth, width, height, DXGI_FORMAT_R32_FLOAT, false, readState) &&
|
||||
make_texture(motion, width, height, DXGI_FORMAT_R16G16B16A16_FLOAT, false, readState) &&
|
||||
make_texture(normals, width, height, DXGI_FORMAT_R10G10B10A2_UNORM, false, readState) &&
|
||||
make_texture(diffuseAlbedo, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, false, readState) &&
|
||||
make_texture(specularAlbedo, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, false, readState);
|
||||
for (int s = 0; s < kSignalCount && ok; ++s)
|
||||
ok = make_texture(input[s], width, height, DXGI_FORMAT_R16G16B16A16_FLOAT, false, readState) &&
|
||||
make_texture(output[s], width, height, DXGI_FORMAT_R16G16B16A16_FLOAT, true,
|
||||
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||
ID3D12Resource *staging = nullptr, *readbackBuffer = nullptr, *timestampBuffer = nullptr;
|
||||
uint8_t *stagingMapped = nullptr, *readbackMapped = nullptr, *timestampMapped = nullptr;
|
||||
const UINT64 planeBytes = output[0].total;
|
||||
ok = ok && make_buffer(staging, 16 * (planeBytes + 65536), D3D12_HEAP_TYPE_UPLOAD, stagingMapped) &&
|
||||
make_buffer(readbackBuffer, kSignalCount * planeBytes, D3D12_HEAP_TYPE_READBACK, readbackMapped) &&
|
||||
make_buffer(timestampBuffer, 16, D3D12_HEAP_TYPE_READBACK, timestampMapped);
|
||||
ID3D12QueryHeap* timestamps = nullptr;
|
||||
D3D12_QUERY_HEAP_DESC queryDesc = {};
|
||||
queryDesc.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP;
|
||||
queryDesc.Count = 2;
|
||||
ok = ok && SUCCEEDED(g_device->CreateQueryHeap(&queryDesc, IID_PPV_ARGS(×tamps)));
|
||||
UINT64 ticksPerSecond = 0;
|
||||
queue->GetTimestampFrequency(&ticksPerSecond);
|
||||
if (!ok)
|
||||
{
|
||||
stage("dispatch", false, "resource creation");
|
||||
return finish(1);
|
||||
}
|
||||
|
||||
const float aspect = static_cast<float>(width) / height;
|
||||
const M4 projection = projection_matrix(1.0f, aspect, 0.1f, 1000.0f);
|
||||
const uint32_t cutFrame = frames * 2 / 3, moveStart = frames / 3;
|
||||
auto camera_at = [&](uint32_t f) -> Camera {
|
||||
if (f >= cutFrame)
|
||||
return {{3.5f, 1.6f, 1.5f}, {-0.5f, -0.6f, 7.5f}};
|
||||
const float t = f >= moveStart ? static_cast<float>(f - moveStart) : 0.0f;
|
||||
return {{-0.6f + 0.04f * t, 0.6f, -1.5f}, {0.0f + 0.02f * t, -0.1f, 6.5f}};
|
||||
};
|
||||
|
||||
Frame frameData, previousFrame;
|
||||
std::vector<double> gpuMs, cpuMs;
|
||||
std::string frameLog = "[";
|
||||
bool dispatchOk = true, converged = true, resetOk = true, finite = true, notPassthrough = true;
|
||||
Camera previousCamera = camera_at(0);
|
||||
for (uint32_t f = 0; f < frames; ++f)
|
||||
{
|
||||
const Camera camera = camera_at(f);
|
||||
const bool reset = f == 0 || f == cutFrame;
|
||||
std::swap(previousFrame, frameData);
|
||||
render(frameData, width, height, camera, reset ? camera : previousCamera, projection, f);
|
||||
|
||||
allocator->Reset();
|
||||
list->Reset(allocator, nullptr);
|
||||
UINT64 offset = 0;
|
||||
upload(list, depth, staging, stagingMapped, offset, frameData.depth.data(), 4);
|
||||
upload(list, motion, staging, stagingMapped, offset, frameData.motion.data(), 8);
|
||||
upload(list, normals, staging, stagingMapped, offset, frameData.normals.data(), 4);
|
||||
upload(list, diffuseAlbedo, staging, stagingMapped, offset, frameData.diffuseAlbedo.data(), 4);
|
||||
upload(list, specularAlbedo, staging, stagingMapped, offset, frameData.specularAlbedo.data(), 4);
|
||||
for (int s = 0; s < kSignalCount; ++s)
|
||||
if (signals & kSignalFlags[s])
|
||||
upload(list, input[s], staging, stagingMapped, offset, frameData.noisy[s].data(), 8);
|
||||
Texture* all[] = {&depth, &motion, &normals, &diffuseAlbedo, &specularAlbedo};
|
||||
for (Texture* t : all)
|
||||
barrier(list, *t, readState);
|
||||
for (int s = 0; s < kSignalCount; ++s)
|
||||
{
|
||||
barrier(list, input[s], readState);
|
||||
barrier(list, output[s], D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||
}
|
||||
|
||||
ffxDispatchDescDenoiser desc = {};
|
||||
desc.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER;
|
||||
desc.commandList = list;
|
||||
const uint32_t readFfx = FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ;
|
||||
desc.linearDepth = fsr_rr_texture(depth.resource, depth.desc, FFX_API_SURFACE_FORMAT_R32_FLOAT, readFfx);
|
||||
desc.motionVectors = fsr_rr_texture(motion.resource, motion.desc, FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT, readFfx);
|
||||
desc.normals = fsr_rr_texture(normals.resource, normals.desc, FFX_API_SURFACE_FORMAT_R10G10B10A2_UNORM, readFfx);
|
||||
desc.specularAlbedo = fsr_rr_texture(specularAlbedo.resource, specularAlbedo.desc, FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM, readFfx);
|
||||
desc.diffuseAlbedo = fsr_rr_texture(diffuseAlbedo.resource, diffuseAlbedo.desc, FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM, readFfx);
|
||||
desc.motionVectorScale = {1.0f, 1.0f, 1.0f};
|
||||
desc.jitterOffsets = {0.0f, 0.0f};
|
||||
const V3 delta = previousCamera.eye - camera.eye;
|
||||
desc.cameraPositionDelta = reset ? FfxApiFloatCoords3D{0, 0, 0} : FfxApiFloatCoords3D{delta.x, delta.y, delta.z};
|
||||
const M4 view = view_matrix(camera.eye, camera.target);
|
||||
std::memcpy(&desc.view, &view, sizeof(desc.view));
|
||||
std::memcpy(&desc.projection, &projection, sizeof(desc.projection));
|
||||
desc.linearDepthBounds = {0.0f, 1000.0f};
|
||||
desc.renderSize = {width, height};
|
||||
desc.frameIndex = f;
|
||||
desc.flags = reset ? FFX_DENOISER_DISPATCH_RESET : 0;
|
||||
ffxDispatchDescDenoiserDirectDiffuse dd = {};
|
||||
ffxDispatchDescDenoiserDirectSpecular ds = {};
|
||||
ffxDispatchDescDenoiserIndirectDiffuse id = {};
|
||||
ffxDispatchDescDenoiserIndirectSpecular is = {};
|
||||
dd.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_DIFFUSE;
|
||||
ds.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_SPECULAR;
|
||||
id.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_DIFFUSE;
|
||||
is.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_SPECULAR;
|
||||
FfxApiDenoiserSignal* signalDescs[kSignalCount] = {&dd.signal, &ds.signal, &id.signal, &is.signal};
|
||||
ffxApiHeader* headers[kSignalCount] = {&dd.header, &ds.header, &id.header, &is.header};
|
||||
ffxApiHeader* tail = &desc.header;
|
||||
for (int s = 0; s < kSignalCount; ++s)
|
||||
{
|
||||
if (!(signals & kSignalFlags[s]))
|
||||
continue;
|
||||
signalDescs[s]->input = fsr_rr_texture(input[s].resource, input[s].desc, FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT, readFfx);
|
||||
signalDescs[s]->output = fsr_rr_texture(output[s].resource, output[s].desc, FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT,
|
||||
FFX_API_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||
tail->pNext = headers[s];
|
||||
tail = headers[s];
|
||||
}
|
||||
list->EndQuery(timestamps, D3D12_QUERY_TYPE_TIMESTAMP, 0);
|
||||
const auto cpuStart = std::chrono::steady_clock::now();
|
||||
const ffxReturnCode_t dispatched = runtime.dispatch(&context, &desc.header);
|
||||
cpuMs.push_back(std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - cpuStart).count());
|
||||
list->EndQuery(timestamps, D3D12_QUERY_TYPE_TIMESTAMP, 1);
|
||||
list->ResolveQueryData(timestamps, D3D12_QUERY_TYPE_TIMESTAMP, 0, 2, timestampBuffer, 0);
|
||||
if (dispatched != FFX_API_RETURN_OK)
|
||||
{
|
||||
stage("dispatch", false, format("frame %u: ffxDispatch -> %s", f, fsr_rr_return_name(dispatched)));
|
||||
dispatchOk = false;
|
||||
list->Close();
|
||||
break;
|
||||
}
|
||||
for (int s = 0; s < kSignalCount; ++s)
|
||||
if (signals & kSignalFlags[s])
|
||||
readback(list, output[s], readbackBuffer, s * planeBytes);
|
||||
if (FAILED(list->Close()))
|
||||
{
|
||||
stage("dispatch", false, format("frame %u: Close failed", f));
|
||||
dispatchOk = false;
|
||||
break;
|
||||
}
|
||||
ID3D12CommandList* lists[] = {list};
|
||||
queue->ExecuteCommandLists(1, lists);
|
||||
queue->Signal(fence, ++fenceValue);
|
||||
fence->SetEventOnCompletion(fenceValue, event);
|
||||
if (WaitForSingleObject(event, 20000) != WAIT_OBJECT_0)
|
||||
{
|
||||
stage("dispatch", false, format("frame %u: GPU did not finish within 20 s", f));
|
||||
dispatchOk = false;
|
||||
break;
|
||||
}
|
||||
const HRESULT removed = g_device->GetDeviceRemovedReason();
|
||||
if (FAILED(removed))
|
||||
{
|
||||
stage("dispatch", false, format("frame %u: device removed 0x%08lx", f, static_cast<unsigned long>(removed)));
|
||||
dispatchOk = false;
|
||||
break;
|
||||
}
|
||||
uint64_t stamps[2];
|
||||
std::memcpy(stamps, timestampMapped, sizeof(stamps));
|
||||
const double gpu = ticksPerSecond ? (stamps[1] - stamps[0]) * 1000.0 / ticksPerSecond : -1.0;
|
||||
gpuMs.push_back(gpu);
|
||||
|
||||
// 5. Compare against the clean signals.
|
||||
frameLog += format("%s\n {\"frame\": %u, \"reset\": %s, \"gpu_ms\": %.3f, \"cpu_ms\": %.3f, \"signals\": {",
|
||||
f ? "," : "", f, reset ? "true" : "false", gpu, cpuMs.back());
|
||||
bool firstSignal = true;
|
||||
std::vector<V3> composeOut(static_cast<size_t>(width) * height), composeIn(composeOut.size()),
|
||||
composeClean(composeOut.size());
|
||||
for (int s = 0; s < kSignalCount; ++s)
|
||||
{
|
||||
if (!(signals & kSignalFlags[s]))
|
||||
continue;
|
||||
const uint8_t* rows = readbackMapped + s * planeBytes;
|
||||
const Error e = measure(frameData, f == cutFrame ? &previousFrame : nullptr, rows,
|
||||
output[s].footprint.Footprint.RowPitch, width, height, s);
|
||||
frameLog += format("%s\"%s\": {\"rmse_in\": %.5f, \"rmse_out\": %.5f, \"mean_abs_change\": %.5f, "
|
||||
"\"rmse_out_vs_previous_scene\": %.5f, \"nonfinite\": %zu}",
|
||||
firstSignal ? "" : ", ", kSignalNames[s], e.input, e.output, e.change,
|
||||
e.outputVsPrevious, e.nonFinite);
|
||||
firstSignal = false;
|
||||
finite = finite && e.nonFinite == 0;
|
||||
notPassthrough = notPassthrough && (f < 2 || e.change > 1e-3 * (e.input + 1e-6));
|
||||
// Converged before the cut: at least 8 static or slowly moving frames into history.
|
||||
if (f + 1 == cutFrame && !(e.output < 0.5 * e.input))
|
||||
converged = false;
|
||||
if (f == cutFrame && !(e.output < e.outputVsPrevious))
|
||||
resetOk = false;
|
||||
if (f + 1 == frames && !(e.output < 0.6 * e.input))
|
||||
resetOk = false;
|
||||
const bool diffuse = s == kDirectDiffuse || s == kIndirectDiffuse;
|
||||
for (uint32_t y = 0; y < height; ++y)
|
||||
for (uint32_t x = 0; x < width; ++x)
|
||||
{
|
||||
const size_t i = static_cast<size_t>(y) * width + x;
|
||||
const V3 albedo = diffuse ? frameData.diffuseLinear[i] : frameData.specularLinear[i];
|
||||
const uint16_t* o = reinterpret_cast<const uint16_t*>(rows + static_cast<size_t>(y) * output[s].footprint.Footprint.RowPitch) + x * 4;
|
||||
composeOut[i] = composeOut[i] + mul(V3{from_half(o[0]), from_half(o[1]), from_half(o[2])}, albedo);
|
||||
composeIn[i] = composeIn[i] + mul(V3{from_half(frameData.noisy[s][i * 4]), from_half(frameData.noisy[s][i * 4 + 1]),
|
||||
from_half(frameData.noisy[s][i * 4 + 2])}, albedo);
|
||||
composeClean[i] = composeClean[i] + mul(V3{frameData.clean[s][i * 3], frameData.clean[s][i * 3 + 1],
|
||||
frameData.clean[s][i * 3 + 2]}, albedo);
|
||||
}
|
||||
}
|
||||
frameLog += "}}";
|
||||
if (f == 0 || f + 1 == moveStart || f + 1 == cutFrame || f == cutFrame || f == cutFrame + 1 || f + 1 == frames)
|
||||
{
|
||||
write_ppm(format("%s\\frame%03u_noisy.ppm", outDir.c_str(), f), width, height, composeIn);
|
||||
write_ppm(format("%s\\frame%03u_denoised.ppm", outDir.c_str(), f), width, height, composeOut);
|
||||
write_ppm(format("%s\\frame%03u_clean.ppm", outDir.c_str(), f), width, height, composeClean);
|
||||
}
|
||||
previousCamera = camera;
|
||||
}
|
||||
json_field("frame_log", frameLog + "\n ]");
|
||||
if (dispatchOk)
|
||||
{
|
||||
std::vector<double> warm(gpuMs.begin() + std::min<size_t>(gpuMs.size(), 3), gpuMs.end());
|
||||
std::sort(warm.begin(), warm.end());
|
||||
const double median = warm.empty() ? -1 : warm[warm.size() / 2];
|
||||
std::vector<double> cpuWarm(cpuMs.begin() + std::min<size_t>(cpuMs.size(), 3), cpuMs.end());
|
||||
std::sort(cpuWarm.begin(), cpuWarm.end());
|
||||
stage("dispatch", true, format("%u frames, GPU median %.3f ms (first frame %.3f ms), CPU record median %.3f ms",
|
||||
frames, median, gpuMs.empty() ? -1.0 : gpuMs[0],
|
||||
cpuWarm.empty() ? -1.0 : cpuWarm[cpuWarm.size() / 2]));
|
||||
json_field("gpu_ms_median", format("%.4f", median));
|
||||
stage("output_finite", finite, finite ? "no NaN/Inf in any output" : "non-finite output values");
|
||||
stage("not_passthrough", notPassthrough, "outputs differ from the noisy inputs");
|
||||
stage("denoised", converged, format("before the cut (frame %u) every signal's RMSE vs clean is < 0.5x the input's",
|
||||
cutFrame - 1));
|
||||
stage("reset", resetOk, format("frame %u (reset) is closer to the new scene than the old one; final frame < 0.6x input",
|
||||
cutFrame));
|
||||
exitCode = finite && notPassthrough && converged && resetOk ? 0 : 1;
|
||||
}
|
||||
std::string messages = "[";
|
||||
for (size_t i = 0; i < g_messages.size(); ++i)
|
||||
messages += (i ? ", " : "") + quoted(g_messages[i]);
|
||||
json_field("ffx_messages", messages + "]");
|
||||
runtime.destroyContext(&context, nullptr);
|
||||
return finish(exitCode);
|
||||
}
|
||||
@@ -0,0 +1,515 @@
|
||||
// The synthetic ray-traced scene shared by tools/fsr_rr_probe.cpp (AMD's denoiser alone) and
|
||||
// tools/rr_backend_check.cpp (a game's NGX Ray Reconstruction request through the shim): a camera over
|
||||
// a checkered floor, three spheres and a wall, with smooth noise-free lighting signals and one-sample
|
||||
// white-noise versions of them, every guide the denoisers read, and exact 2.5D motion. Because the
|
||||
// noise-free signals are known, outputs are compared against ground truth rather than another
|
||||
// denoiser.
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace fsr_scene
|
||||
{
|
||||
|
||||
// --- small math -----------------------------------------------------------------
|
||||
|
||||
struct V3
|
||||
{
|
||||
float x, y, z;
|
||||
};
|
||||
inline V3 operator+(V3 a, V3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
|
||||
inline V3 operator-(V3 a, V3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
|
||||
inline V3 operator*(V3 a, float s) { return {a.x * s, a.y * s, a.z * s}; }
|
||||
inline V3 mul(V3 a, V3 b) { return {a.x * b.x, a.y * b.y, a.z * b.z}; }
|
||||
inline float dot(V3 a, V3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
|
||||
inline V3 cross(V3 a, V3 b) { return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; }
|
||||
inline V3 normalize(V3 a) { return a * (1.0f / std::sqrt(dot(a, a))); }
|
||||
inline float saturate(float v) { return std::min(1.0f, std::max(0.0f, v)); }
|
||||
|
||||
inline uint16_t to_half(float value)
|
||||
{
|
||||
uint32_t bits;
|
||||
std::memcpy(&bits, &value, 4);
|
||||
const uint32_t sign = (bits >> 16) & 0x8000u;
|
||||
int exponent = static_cast<int>((bits >> 23) & 0xff) - 127 + 15;
|
||||
uint32_t mantissa = bits & 0x7fffffu;
|
||||
if (((bits >> 23) & 0xff) == 0xff)
|
||||
return static_cast<uint16_t>(sign | 0x7c00u | (mantissa ? 0x200u : 0u));
|
||||
if (exponent >= 31)
|
||||
return static_cast<uint16_t>(sign | 0x7bffu); // clamp to the largest finite half
|
||||
if (exponent <= 0)
|
||||
{
|
||||
if (exponent < -10)
|
||||
return static_cast<uint16_t>(sign);
|
||||
mantissa |= 0x800000u;
|
||||
const uint32_t shift = static_cast<uint32_t>(14 - exponent);
|
||||
uint32_t half = mantissa >> shift;
|
||||
if ((mantissa >> (shift - 1)) & 1u)
|
||||
++half;
|
||||
return static_cast<uint16_t>(sign | half);
|
||||
}
|
||||
uint32_t half = sign | (static_cast<uint32_t>(exponent) << 10) | (mantissa >> 13);
|
||||
if (mantissa & 0x1000u)
|
||||
++half;
|
||||
return static_cast<uint16_t>(half);
|
||||
}
|
||||
|
||||
inline float from_half(uint16_t half)
|
||||
{
|
||||
const uint32_t sign = (half & 0x8000u) << 16;
|
||||
uint32_t exponent = (half >> 10) & 0x1fu;
|
||||
uint32_t mantissa = half & 0x3ffu;
|
||||
uint32_t bits;
|
||||
if (exponent == 0)
|
||||
{
|
||||
if (mantissa == 0)
|
||||
bits = sign;
|
||||
else
|
||||
{
|
||||
exponent = 127 - 15 + 1;
|
||||
while ((mantissa & 0x400u) == 0)
|
||||
{
|
||||
mantissa <<= 1;
|
||||
--exponent;
|
||||
}
|
||||
bits = sign | (exponent << 23) | ((mantissa & 0x3ffu) << 13);
|
||||
}
|
||||
}
|
||||
else if (exponent == 31)
|
||||
bits = sign | 0x7f800000u | (mantissa << 13);
|
||||
else
|
||||
bits = sign | ((exponent - 15 + 127) << 23) | (mantissa << 13);
|
||||
float value;
|
||||
std::memcpy(&value, &bits, 4);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Row-major, row-vector (v' = vM) 4x4, the convention ffx_denoiser.h asks for.
|
||||
struct M4
|
||||
{
|
||||
float m[4][4];
|
||||
};
|
||||
|
||||
inline M4 view_matrix(V3 eye, V3 target)
|
||||
{
|
||||
const V3 forward = normalize(target - eye);
|
||||
const V3 right = normalize(cross({0, 1, 0}, forward));
|
||||
const V3 up = cross(forward, right);
|
||||
M4 v = {};
|
||||
v.m[0][0] = right.x, v.m[0][1] = up.x, v.m[0][2] = forward.x;
|
||||
v.m[1][0] = right.y, v.m[1][1] = up.y, v.m[1][2] = forward.y;
|
||||
v.m[2][0] = right.z, v.m[2][1] = up.z, v.m[2][2] = forward.z;
|
||||
v.m[3][0] = -dot(eye, right), v.m[3][1] = -dot(eye, up), v.m[3][2] = -dot(eye, forward), v.m[3][3] = 1;
|
||||
return v;
|
||||
}
|
||||
|
||||
inline M4 projection_matrix(float fovY, float aspect, float zNear, float zFar)
|
||||
{
|
||||
const float ys = 1.0f / std::tan(fovY * 0.5f);
|
||||
M4 p = {};
|
||||
p.m[0][0] = ys / aspect;
|
||||
p.m[1][1] = ys;
|
||||
p.m[2][2] = zFar / (zFar - zNear);
|
||||
p.m[2][3] = 1;
|
||||
p.m[3][2] = -zNear * zFar / (zFar - zNear);
|
||||
return p;
|
||||
}
|
||||
|
||||
inline void transform(const M4& m, V3 v, float out[4])
|
||||
{
|
||||
for (int c = 0; c < 4; ++c)
|
||||
out[c] = v.x * m.m[0][c] + v.y * m.m[1][c] + v.z * m.m[2][c] + m.m[3][c];
|
||||
}
|
||||
|
||||
// --- the synthetic scene ----------------------------------------------------------
|
||||
|
||||
struct Camera
|
||||
{
|
||||
V3 eye, target;
|
||||
};
|
||||
|
||||
struct Sphere
|
||||
{
|
||||
V3 center;
|
||||
float radius;
|
||||
V3 base;
|
||||
float roughness, metal;
|
||||
};
|
||||
|
||||
inline const Sphere kSpheres[] = {
|
||||
{{0.0f, 0.2f, 6.0f}, 1.2f, {0.8f, 0.3f, 0.2f}, 0.35f, 0.0f},
|
||||
{{2.4f, -0.3f, 7.5f}, 0.7f, {0.95f, 0.9f, 0.8f}, 0.15f, 1.0f},
|
||||
{{-2.2f, -0.4f, 5.0f}, 0.6f, {0.2f, 0.5f, 0.85f}, 0.6f, 0.0f},
|
||||
};
|
||||
inline constexpr float kGroundY = -1.0f;
|
||||
inline constexpr float kWallZ = 14.0f;
|
||||
// Optional near-mirror pond in the floor (rr_backend_check --water): the reflection is the traced scene, with
|
||||
// guides as Alan Wake 2 hands them for its water (flat diffuse albedo 0.5, specular albedo ~0.96, roughness ~0.047).
|
||||
inline bool g_water = false;
|
||||
inline constexpr float kWaterRoughness = 0.047f;
|
||||
inline constexpr float kWaterMinX = -4.0f, kWaterMaxX = 4.5f, kWaterMinZ = 0.5f, kWaterMaxZ = 4.8f;
|
||||
inline const V3 kLight = normalize({0.45f, 0.9f, -0.35f});
|
||||
|
||||
struct Hit
|
||||
{
|
||||
float t = 1e30f;
|
||||
V3 position{}, normal{}, base{};
|
||||
float roughness = 1, metal = 0;
|
||||
bool valid = false, water = false;
|
||||
};
|
||||
|
||||
inline bool hit_sphere(V3 origin, V3 direction, const Sphere& s, float& t)
|
||||
{
|
||||
const V3 oc = origin - s.center;
|
||||
const float b = dot(oc, direction);
|
||||
const float c = dot(oc, oc) - s.radius * s.radius;
|
||||
const float h = b * b - c;
|
||||
if (h < 0)
|
||||
return false;
|
||||
const float root = -b - std::sqrt(h);
|
||||
if (root <= 1e-3f)
|
||||
return false;
|
||||
t = root;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline Hit trace(V3 origin, V3 direction)
|
||||
{
|
||||
Hit hit;
|
||||
float t;
|
||||
for (const Sphere& s : kSpheres)
|
||||
if (hit_sphere(origin, direction, s, t) && t < hit.t)
|
||||
{
|
||||
hit.t = t;
|
||||
hit.position = origin + direction * t;
|
||||
hit.normal = normalize(hit.position - s.center);
|
||||
hit.base = s.base;
|
||||
hit.roughness = s.roughness;
|
||||
hit.metal = s.metal;
|
||||
hit.valid = true;
|
||||
}
|
||||
if (direction.y < -1e-4f)
|
||||
{
|
||||
t = (kGroundY - origin.y) / direction.y;
|
||||
if (t > 1e-3f && t < hit.t)
|
||||
{
|
||||
hit.t = t;
|
||||
hit.position = origin + direction * t;
|
||||
hit.normal = {0, 1, 0};
|
||||
const int checker = (static_cast<int>(std::floor(hit.position.x)) + static_cast<int>(std::floor(hit.position.z))) & 1;
|
||||
hit.base = checker ? V3{0.75f, 0.75f, 0.72f} : V3{0.22f, 0.24f, 0.25f};
|
||||
hit.roughness = 0.55f;
|
||||
hit.metal = 0;
|
||||
hit.valid = true;
|
||||
const V3 q = hit.position;
|
||||
if (g_water && q.x > kWaterMinX && q.x < kWaterMaxX && q.z > kWaterMinZ && q.z < kWaterMaxZ)
|
||||
{
|
||||
// small world-space ripples, so the reflection has fine detail a denoiser can lose
|
||||
hit.normal = normalize({0.035f * std::sin(5.1f * q.x + 1.3f * q.z) + 0.02f * std::sin(11.7f * q.z),
|
||||
1.0f,
|
||||
0.035f * std::cos(4.3f * q.z - 0.7f * q.x) + 0.02f * std::cos(13.1f * q.x)});
|
||||
hit.base = {0.5f, 0.5f, 0.5f};
|
||||
hit.roughness = kWaterRoughness;
|
||||
hit.water = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (direction.z > 1e-4f)
|
||||
{
|
||||
t = (kWallZ - origin.z) / direction.z;
|
||||
if (t > 1e-3f && t < hit.t)
|
||||
{
|
||||
hit.t = t;
|
||||
hit.position = origin + direction * t;
|
||||
hit.normal = {0, 0, -1};
|
||||
hit.base = {0.55f, 0.5f, 0.45f};
|
||||
hit.roughness = 0.85f;
|
||||
hit.metal = 0;
|
||||
hit.valid = true;
|
||||
}
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
inline bool shadowed(V3 position, V3 normal)
|
||||
{
|
||||
float t;
|
||||
const V3 origin = position + normal * 2e-3f;
|
||||
for (const Sphere& s : kSpheres)
|
||||
if (hit_sphere(origin, kLight, s, t))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// AMD's ApproximateBRDF (denoising.md 5.5.2, after Ray Tracing Gems ch. 32).
|
||||
inline V3 approximate_brdf(V3 f0, float alpha, float nov)
|
||||
{
|
||||
nov = std::fabs(nov);
|
||||
const float x[4] = {1, nov, nov * nov, nov * nov * nov};
|
||||
const float y[4] = {1, alpha, alpha * alpha, alpha * alpha * alpha};
|
||||
const float m1[2][2] = {{0.99044f, -1.28514f}, {1.29678f, -0.755907f}};
|
||||
const float m2[3][3] = {{1.0f, 2.92338f, 59.4188f}, {20.3225f, -27.0302f, 222.592f}, {121.563f, 626.13f, 316.627f}};
|
||||
const float m3[2][2] = {{0.0365463f, 3.32707f}, {9.0632f, -9.04756f}};
|
||||
const float m4[3][3] = {{1.0f, 3.59685f, -1.36772f}, {9.04401f, -16.3174f, 9.22949f}, {5.56589f, 19.7886f, -20.2123f}};
|
||||
// HLSL mul(M, v) with M row-major: (M v)_i = sum_j M[i][j] v_j
|
||||
const float a0 = m1[0][0] * x[0] + m1[0][1] * x[1], a1 = m1[1][0] * x[0] + m1[1][1] * x[1];
|
||||
const float xw[3] = {x[0], x[1], x[3]}, yw[3] = {y[0], y[1], y[3]};
|
||||
float b[3];
|
||||
for (int i = 0; i < 3; ++i)
|
||||
b[i] = m2[i][0] * xw[0] + m2[i][1] * xw[1] + m2[i][2] * xw[2];
|
||||
const float c0 = m3[0][0] * x[0] + m3[0][1] * x[1], c1 = m3[1][0] * x[0] + m3[1][1] * x[1];
|
||||
const float xz[3] = {x[0], x[2], x[3]};
|
||||
float d[3];
|
||||
for (int i = 0; i < 3; ++i)
|
||||
d[i] = m4[i][0] * xz[0] + m4[i][1] * xz[1] + m4[i][2] * xz[2];
|
||||
float bias = (a0 * y[0] + a1 * y[1]) / (b[0] * yw[0] + b[1] * yw[1] + b[2] * yw[2]);
|
||||
const float scale = (c0 * y[0] + c1 * y[1]) / (d[0] * yw[0] + d[1] * yw[1] + d[2] * yw[2]);
|
||||
bias *= saturate(f0.y * 50);
|
||||
const float s = std::max(0.0f, scale), bb = std::max(0.0f, bias);
|
||||
return {f0.x * s + bb, f0.y * s + bb, f0.z * s + bb};
|
||||
}
|
||||
|
||||
inline V3 reflect_dir(V3 direction, V3 normal) { return direction - normal * (2 * dot(direction, normal)); }
|
||||
|
||||
inline V3 environment(V3 direction)
|
||||
{
|
||||
const float up = saturate(direction.y * 0.5f + 0.5f);
|
||||
return V3{0.35f, 0.45f, 0.6f} * (0.4f + 1.2f * up);
|
||||
}
|
||||
|
||||
inline uint32_t hash32(uint32_t x)
|
||||
{
|
||||
// lowbias32, as AMD's guide recommends for white noise
|
||||
x ^= x >> 16;
|
||||
x *= 0x7feb352du;
|
||||
x ^= x >> 15;
|
||||
x *= 0x846ca68bu;
|
||||
x ^= x >> 16;
|
||||
return x;
|
||||
}
|
||||
|
||||
inline float uniform(uint32_t pixel, uint32_t frame, uint32_t stream)
|
||||
{
|
||||
return (hash32(pixel * 0x9e3779b9u ^ hash32(frame * 4u + stream + 0x1234567u)) >> 8) * (1.0f / 16777216.0f);
|
||||
}
|
||||
|
||||
enum Signal
|
||||
{
|
||||
kDirectDiffuse,
|
||||
kDirectSpecular,
|
||||
kIndirectDiffuse,
|
||||
kIndirectSpecular,
|
||||
kSignalCount
|
||||
};
|
||||
// FfxApiDenoiserSignalFlags of each signal (DIRECT_DIFFUSE 2, DIRECT_SPECULAR 4, INDIRECT_DIFFUSE 16,
|
||||
// INDIRECT_SPECULAR 32)
|
||||
inline const uint32_t kSignalFlags[kSignalCount] = {1u << 1, 1u << 2, 1u << 4, 1u << 5};
|
||||
inline const char* const kSignalNames[kSignalCount] = {"direct_diffuse", "direct_specular", "indirect_diffuse",
|
||||
"indirect_specular"};
|
||||
|
||||
struct Frame
|
||||
{
|
||||
// FSR Ray Regeneration's own inputs (fsr_rr_probe)
|
||||
std::vector<float> depth; // signed linear (view z)
|
||||
std::vector<uint16_t> motion; // RGBA16F: prevUV - curUV, prev z - cur z
|
||||
std::vector<uint32_t> normals; // R10G10B10A2: oct normal, roughness, material 0
|
||||
std::vector<uint32_t> diffuseAlbedo, specularAlbedo; // RGBA8, sqrt encoded
|
||||
std::vector<float> clean[kSignalCount]; // RGB
|
||||
std::vector<uint16_t> noisy[kSignalCount]; // RGBA16F
|
||||
std::vector<uint8_t> valid;
|
||||
std::vector<V3> diffuseLinear, specularLinear;
|
||||
// What a DLSS Ray Reconstruction game hands NGX (rr_backend_check)
|
||||
std::vector<uint16_t> color; // RGBA16F: the noisy signals remodulated and summed, sky added
|
||||
std::vector<V3> colorClean; // the same from the noise-free signals: the ground truth
|
||||
std::vector<float> hardwareDepth; // D32: NDC z of the projection (1 for the sky)
|
||||
std::vector<uint16_t> motionPixels; // RG16F: (previous - current) position in render pixels
|
||||
std::vector<uint16_t> normalsRoughness; // RGBA16F: signed world normal, w = linear roughness
|
||||
std::vector<uint32_t> diffuseAlbedoLinear, specularAlbedoLinear; // RGBA8 UNORM, linear
|
||||
};
|
||||
|
||||
inline uint32_t pack_unorm8(V3 v)
|
||||
{
|
||||
auto q = [](float f) { return static_cast<uint32_t>(std::lround(saturate(f) * 255.0f)); };
|
||||
return q(v.x) | (q(v.y) << 8) | (q(v.z) << 16) | (255u << 24);
|
||||
}
|
||||
|
||||
inline uint32_t pack_normal(V3 n, float roughness)
|
||||
{
|
||||
// AMD's NormalToOctahedronUv (denoising.md 5.3.1)
|
||||
const float l1 = std::fabs(n.x) + std::fabs(n.y) + std::fabs(n.z);
|
||||
float x = n.x / l1, y = n.y / l1;
|
||||
if (n.z < 0)
|
||||
{
|
||||
const float ox = (1.0f - std::fabs(y)) * (x >= 0 ? 1.0f : -1.0f);
|
||||
const float oy = (1.0f - std::fabs(x)) * (y >= 0 ? 1.0f : -1.0f);
|
||||
x = ox, y = oy;
|
||||
}
|
||||
auto q10 = [](float f) { return static_cast<uint32_t>(std::lround(saturate(f) * 1023.0f)); };
|
||||
return q10(x * 0.5f + 0.5f) | (q10(y * 0.5f + 0.5f) << 10) | (q10(roughness) << 20) | (0u << 30);
|
||||
}
|
||||
|
||||
// Renders one frame. (jitterX, jitterY): the sample offset from each pixel's centre in pixels (DLSS's
|
||||
// Jitter.Offset is its negation). Motion is that of the sampled surface point.
|
||||
inline void render(Frame& frame, uint32_t width, uint32_t height, const Camera& camera, const Camera& previous,
|
||||
const M4& projection, uint32_t frameIndex, float jitterX = 0.0f, float jitterY = 0.0f)
|
||||
{
|
||||
const size_t count = static_cast<size_t>(width) * height;
|
||||
frame.depth.assign(count, 0);
|
||||
frame.motion.assign(count * 4, 0);
|
||||
frame.normals.assign(count, 0);
|
||||
frame.diffuseAlbedo.assign(count, 0);
|
||||
frame.specularAlbedo.assign(count, 0);
|
||||
frame.valid.assign(count, 0);
|
||||
frame.diffuseLinear.assign(count, V3{});
|
||||
frame.specularLinear.assign(count, V3{});
|
||||
frame.color.assign(count * 4, 0);
|
||||
frame.colorClean.assign(count, V3{});
|
||||
frame.hardwareDepth.assign(count, 1.0f);
|
||||
frame.motionPixels.assign(count * 2, 0);
|
||||
frame.normalsRoughness.assign(count * 4, 0);
|
||||
frame.diffuseAlbedoLinear.assign(count, 0);
|
||||
frame.specularAlbedoLinear.assign(count, 0);
|
||||
for (int s = 0; s < kSignalCount; ++s)
|
||||
{
|
||||
frame.clean[s].assign(count * 3, 0);
|
||||
frame.noisy[s].assign(count * 4, 0);
|
||||
}
|
||||
const M4 view = view_matrix(camera.eye, camera.target);
|
||||
const M4 previousView = view_matrix(previous.eye, previous.target);
|
||||
const V3 forward = normalize(camera.target - camera.eye);
|
||||
const V3 right = normalize(cross({0, 1, 0}, forward));
|
||||
const V3 up = cross(forward, right);
|
||||
const float tanX = 1.0f / projection.m[0][0], tanY = 1.0f / projection.m[1][1];
|
||||
for (uint32_t py = 0; py < height; ++py)
|
||||
for (uint32_t px = 0; px < width; ++px)
|
||||
{
|
||||
const size_t i = static_cast<size_t>(py) * width + px;
|
||||
const float u = (px + 0.5f + jitterX) / width, v = (py + 0.5f + jitterY) / height;
|
||||
const float ndcX = u * 2 - 1, ndcY = 1 - v * 2;
|
||||
const V3 direction = normalize(forward + right * (ndcX * tanX) + up * (ndcY * tanY));
|
||||
const Hit hit = trace(camera.eye, direction);
|
||||
const uint32_t pixelId = static_cast<uint32_t>(i);
|
||||
frame.color[i * 4 + 3] = to_half(1.0f);
|
||||
frame.normalsRoughness[i * 4 + 2] = to_half(-1.0f);
|
||||
frame.normalsRoughness[i * 4 + 3] = to_half(1.0f);
|
||||
if (!hit.valid)
|
||||
{
|
||||
// Sky: outside linearDepthBounds, so the runtime passes it through.
|
||||
frame.depth[i] = 5000.0f;
|
||||
frame.normals[i] = pack_normal({0, 0, -1}, 1);
|
||||
const V3 sky = environment(direction);
|
||||
frame.colorClean[i] = sky;
|
||||
frame.color[i * 4 + 0] = to_half(sky.x);
|
||||
frame.color[i * 4 + 1] = to_half(sky.y);
|
||||
frame.color[i * 4 + 2] = to_half(sky.z);
|
||||
continue;
|
||||
}
|
||||
frame.valid[i] = 1;
|
||||
float clip[4];
|
||||
transform(view, hit.position, clip);
|
||||
frame.depth[i] = clip[2];
|
||||
float projected[4];
|
||||
transform(projection, {clip[0], clip[1], clip[2]}, projected);
|
||||
frame.hardwareDepth[i] = projected[2] / projected[3];
|
||||
float prevView[4], prevClip[4];
|
||||
transform(previousView, hit.position, prevView);
|
||||
const M4& p = projection;
|
||||
const V3 pv = {prevView[0], prevView[1], prevView[2]};
|
||||
transform(p, pv, prevClip);
|
||||
const float prevU = (prevClip[0] / prevClip[3]) * 0.5f + 0.5f;
|
||||
const float prevV = 0.5f - (prevClip[1] / prevClip[3]) * 0.5f;
|
||||
frame.motion[i * 4 + 0] = to_half(prevU - u);
|
||||
frame.motion[i * 4 + 1] = to_half(prevV - v);
|
||||
frame.motion[i * 4 + 2] = to_half(prevView[2] - clip[2]);
|
||||
frame.motion[i * 4 + 3] = to_half(0);
|
||||
frame.motionPixels[i * 2 + 0] = to_half((prevU - u) * width);
|
||||
frame.motionPixels[i * 2 + 1] = to_half((prevV - v) * height);
|
||||
|
||||
const V3 diffuse = hit.base * (1.0f - hit.metal);
|
||||
const V3 f0 = V3{0.04f, 0.04f, 0.04f} * (1.0f - hit.metal) + hit.base * hit.metal;
|
||||
const V3 toEye = normalize(camera.eye - hit.position);
|
||||
const float nov = std::max(dot(hit.normal, toEye), 1e-3f);
|
||||
const V3 specular = hit.water ? V3{0.96f, 0.96f, 0.96f} : approximate_brdf(f0, hit.roughness * hit.roughness, nov);
|
||||
frame.diffuseLinear[i] = diffuse;
|
||||
frame.specularLinear[i] = specular;
|
||||
frame.diffuseAlbedo[i] = pack_unorm8({std::sqrt(diffuse.x), std::sqrt(diffuse.y), std::sqrt(diffuse.z)});
|
||||
frame.specularAlbedo[i] = pack_unorm8({std::sqrt(specular.x), std::sqrt(specular.y), std::sqrt(specular.z)});
|
||||
frame.diffuseAlbedoLinear[i] = pack_unorm8(diffuse);
|
||||
frame.specularAlbedoLinear[i] = pack_unorm8(specular);
|
||||
frame.normals[i] = pack_normal(hit.normal, hit.roughness);
|
||||
frame.normalsRoughness[i * 4 + 0] = to_half(hit.normal.x);
|
||||
frame.normalsRoughness[i * 4 + 1] = to_half(hit.normal.y);
|
||||
frame.normalsRoughness[i * 4 + 2] = to_half(hit.normal.z);
|
||||
frame.normalsRoughness[i * 4 + 3] = to_half(hit.roughness);
|
||||
|
||||
// Demodulated (albedo-free) clean signals, smooth in world space.
|
||||
const float visibility = shadowed(hit.position, hit.normal) ? 0.0f : 1.0f;
|
||||
const float ndl = std::max(dot(hit.normal, kLight), 0.0f);
|
||||
const V3 reflected = direction - hit.normal * (2 * dot(direction, hit.normal));
|
||||
const float rdl = std::max(dot(reflected, kLight), 0.0f);
|
||||
const float glossExponent = 2.0f / (hit.roughness * hit.roughness * hit.roughness * hit.roughness + 0.02f);
|
||||
V3 clean[kSignalCount];
|
||||
clean[kDirectDiffuse] = V3{1.0f, 0.95f, 0.85f} * (2.5f * ndl * visibility);
|
||||
clean[kDirectSpecular] = V3{1.0f, 0.95f, 0.85f} * std::min(8.0f, 3.0f * std::pow(rdl, glossExponent) * visibility * (ndl > 0));
|
||||
const float wobble = 0.12f * std::sin(1.7f * hit.position.x) * std::sin(1.3f * hit.position.z);
|
||||
clean[kIndirectDiffuse] = environment(hit.normal) * (0.6f + wobble);
|
||||
clean[kIndirectSpecular] = environment(reflected) * (0.8f + 0.2f * (1 - hit.roughness));
|
||||
if (hit.water)
|
||||
{
|
||||
// The pond's light is its reflection: the traced scene, lit noise-free. Its diffuse lobe is
|
||||
// nearly dark despite the 0.5 guide albedo.
|
||||
clean[kDirectDiffuse] = clean[kDirectDiffuse] * 0.01f;
|
||||
clean[kIndirectDiffuse] = clean[kIndirectDiffuse] * 0.01f;
|
||||
const Hit bounce = trace(hit.position + hit.normal * 2e-3f, reflected);
|
||||
if (bounce.valid && !bounce.water)
|
||||
{
|
||||
const float bounceVisibility = shadowed(bounce.position, bounce.normal) ? 0.0f : 1.0f;
|
||||
const float bounceNdl = std::max(dot(bounce.normal, kLight), 0.0f);
|
||||
const V3 light = V3{1.0f, 0.95f, 0.85f} * (2.5f * bounceNdl * bounceVisibility) +
|
||||
environment(bounce.normal) * 0.6f;
|
||||
clean[kIndirectSpecular] = mul(bounce.base * (1.0f - bounce.metal), light) +
|
||||
environment(reflect_dir(reflected, bounce.normal)) * (0.04f + 0.9f * bounce.metal);
|
||||
}
|
||||
else
|
||||
clean[kIndirectSpecular] = environment(reflected);
|
||||
}
|
||||
V3 combined{}, combinedClean{};
|
||||
for (int s = 0; s < kSignalCount; ++s)
|
||||
{
|
||||
// One-sample-per-pixel white noise: a mean-one exponential weight shared by RGB,
|
||||
// like a single path sample's throughput.
|
||||
float sample = -std::log(std::max(1e-6f, 1.0f - uniform(pixelId, frameIndex, s)));
|
||||
if (hit.water && s == kIndirectSpecular)
|
||||
sample = 1.0f + 0.35f * (sample - 1.0f); // a mirror's reflection ray is far less noisy
|
||||
frame.clean[s][i * 3 + 0] = clean[s].x;
|
||||
frame.clean[s][i * 3 + 1] = clean[s].y;
|
||||
frame.clean[s][i * 3 + 2] = clean[s].z;
|
||||
frame.noisy[s][i * 4 + 0] = to_half(clean[s].x * sample);
|
||||
frame.noisy[s][i * 4 + 1] = to_half(clean[s].y * sample);
|
||||
frame.noisy[s][i * 4 + 2] = to_half(clean[s].z * sample);
|
||||
// A: ray hit distance for the indirect signals, any non-negative value for direct.
|
||||
float alpha = 0.0f;
|
||||
if (s == kIndirectDiffuse)
|
||||
alpha = 0.5f + 3.0f * uniform(pixelId, frameIndex, 10 + s);
|
||||
else if (s == kIndirectSpecular)
|
||||
{
|
||||
const Hit bounce = trace(hit.position + hit.normal * 2e-3f, reflected);
|
||||
alpha = bounce.valid ? std::min(bounce.t, 100.0f) : 100.0f;
|
||||
}
|
||||
frame.noisy[s][i * 4 + 3] = to_half(alpha);
|
||||
const V3 albedo = s == kDirectDiffuse || s == kIndirectDiffuse ? diffuse : specular;
|
||||
combined = combined + mul(clean[s] * sample, albedo);
|
||||
combinedClean = combinedClean + mul(clean[s], albedo);
|
||||
}
|
||||
frame.colorClean[i] = combinedClean;
|
||||
frame.color[i * 4 + 0] = to_half(combined.x);
|
||||
frame.color[i * 4 + 1] = to_half(combined.y);
|
||||
frame.color[i * 4 + 2] = to_half(combined.z);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fsr_scene
|
||||
@@ -0,0 +1,733 @@
|
||||
// Drives d4r_nvngx.dll the way a DLSS Ray Reconstruction game does, with the synthetic ray-traced
|
||||
// scene of fsr_rr_scene.h, and measures the result against ground truth: the scene's noise-free image
|
||||
// rendered directly at the output resolution. It exercises whichever Ray Reconstruction backend the
|
||||
// shim is configured with ([RayReconstruction] Backend / D4R_RR_BACKEND), or Super Sampling alone on
|
||||
// the same noisy colour (--feature 1) as the no-denoiser baseline.
|
||||
//
|
||||
// Per frame the game-side inputs are: the noisy colour (each signal remodulated by its albedo, summed),
|
||||
// linear or hardware depth, render-resolution motion vectors in pixels (previous - current), world
|
||||
// normals with the roughness in w (DLSS.Roughness.Mode 1), linear diffuse and specular albedos, the
|
||||
// unjittered WorldToView/ViewToClip matrices, and Halton(2,3) jitter. The camera holds still, then
|
||||
// pans, then cuts (Reset) to another view.
|
||||
//
|
||||
// It checks: capability and requirement answers, feature creation, every evaluation's result, output
|
||||
// written at exactly the requested size, finite values, error against ground truth before and after
|
||||
// the cut, release and shutdown. Timing: CPU time of each EvaluateFeature call and of submit + wait.
|
||||
//
|
||||
// usage: rr_backend_check.exe SHIM_DLL OUT_DIR [--render WxH] [--output WxH] [--frames N] [--feature 13|1]
|
||||
// [--hw-depth] [--separate-roughness] [--specular-hit]
|
||||
#define WIDL_EXPLICIT_AGGREGATE_RETURNS
|
||||
#include <windows.h>
|
||||
#include <d3d12.h>
|
||||
#include <dxgi1_4.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fsr_rr_scene.h"
|
||||
|
||||
using namespace fsr_scene;
|
||||
|
||||
using NgxResult = unsigned int;
|
||||
constexpr NgxResult NGX_SUCCESS = 1;
|
||||
struct NgxHandle
|
||||
{
|
||||
unsigned int Id;
|
||||
};
|
||||
|
||||
extern "C"
|
||||
{
|
||||
void d4r_ngx_set_float(void* parameters, const char* name, float value);
|
||||
void d4r_ngx_set_uint(void* parameters, const char* name, unsigned int value);
|
||||
void d4r_ngx_set_int(void* parameters, const char* name, int value);
|
||||
void d4r_ngx_set_d3d12_resource(void* parameters, const char* name, ID3D12Resource* value);
|
||||
void d4r_ngx_set_void(void* parameters, const char* name, void* value);
|
||||
NgxResult d4r_ngx_get_int(void* parameters, const char* name, int* value);
|
||||
NgxResult d4r_ngx_get_void(void* parameters, const char* name, void** value);
|
||||
}
|
||||
|
||||
using PFN_Init_Ext = NgxResult (*)(unsigned long long, const wchar_t*, ID3D12Device*, unsigned int, const void*);
|
||||
using PFN_Parameters = NgxResult (*)(void**);
|
||||
using PFN_CreateFeature = NgxResult (*)(ID3D12GraphicsCommandList*, unsigned int, void*, NgxHandle**);
|
||||
using PFN_Evaluate = NgxResult (*)(ID3D12GraphicsCommandList*, const NgxHandle*, void*, void*);
|
||||
using PFN_Release = NgxResult (*)(NgxHandle*);
|
||||
using PFN_Shutdown = NgxResult (*)();
|
||||
struct NgxApplicationIdentifier
|
||||
{
|
||||
unsigned int type;
|
||||
union
|
||||
{
|
||||
unsigned long long applicationId;
|
||||
struct
|
||||
{
|
||||
const char* id;
|
||||
int engineType;
|
||||
const char* engineVersion;
|
||||
} project;
|
||||
} value;
|
||||
};
|
||||
struct NgxFeatureDiscovery
|
||||
{
|
||||
unsigned int sdkVersion;
|
||||
unsigned int feature;
|
||||
NgxApplicationIdentifier identifier;
|
||||
const wchar_t* dataPath;
|
||||
const void* featureInfo;
|
||||
};
|
||||
struct NgxFeatureRequirement
|
||||
{
|
||||
unsigned int supported;
|
||||
unsigned int minArchitecture;
|
||||
char minOsVersion[255];
|
||||
};
|
||||
using PFN_Requirements = NgxResult (*)(void*, const NgxFeatureDiscovery*, NgxFeatureRequirement*);
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
ID3D12Device* g_device;
|
||||
ID3D12CommandQueue* g_queue;
|
||||
ID3D12CommandAllocator* g_allocator;
|
||||
ID3D12GraphicsCommandList* g_list;
|
||||
ID3D12Fence* g_fence;
|
||||
HANDLE g_event;
|
||||
UINT64 g_fenceValue;
|
||||
FILE* g_json;
|
||||
bool g_jsonFirst = true;
|
||||
|
||||
std::string format(const char* pattern, ...)
|
||||
{
|
||||
char buffer[4096];
|
||||
va_list args;
|
||||
va_start(args, pattern);
|
||||
std::vsnprintf(buffer, sizeof(buffer), pattern, args);
|
||||
va_end(args);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void json_field(const char* key, const std::string& value)
|
||||
{
|
||||
if (g_json)
|
||||
std::fprintf(g_json, "%s\n \"%s\": %s", g_jsonFirst ? "" : ",", key, value.c_str());
|
||||
g_jsonFirst = false;
|
||||
}
|
||||
|
||||
bool g_failed = false;
|
||||
void check(const char* name, bool ok, const std::string& detail)
|
||||
{
|
||||
std::printf("check=%s result=%s %s\n", name, ok ? "ok" : "FAIL", detail.c_str());
|
||||
std::fflush(stdout);
|
||||
std::string escaped;
|
||||
for (char c : detail)
|
||||
if (c == '"' || c == '\\')
|
||||
escaped += '\\', escaped += c;
|
||||
else if (static_cast<unsigned char>(c) >= 0x20)
|
||||
escaped += c;
|
||||
json_field((std::string("check_") + name).c_str(),
|
||||
format("{\"ok\": %s, \"detail\": \"%s\"}", ok ? "true" : "false", escaped.c_str()));
|
||||
g_failed |= !ok;
|
||||
}
|
||||
|
||||
// Process VRAM from the DRM fdinfo of this process (Wine maps Z: to /), as the D3D12 harness does.
|
||||
unsigned long long process_vram_kib()
|
||||
{
|
||||
std::vector<unsigned long long> clients;
|
||||
unsigned long long total = 0;
|
||||
for (int fd = 0; fd < 4096; ++fd)
|
||||
{
|
||||
char path[64];
|
||||
std::snprintf(path, sizeof(path), "Z:\\proc\\self\\fdinfo\\%d", fd);
|
||||
FILE* file = std::fopen(path, "r");
|
||||
if (file == nullptr)
|
||||
continue;
|
||||
unsigned long long client = 0, vram = 0;
|
||||
bool drm = false;
|
||||
char line[256];
|
||||
while (std::fgets(line, sizeof(line), file) != nullptr)
|
||||
if (std::sscanf(line, "drm-client-id: %llu", &client) == 1)
|
||||
drm = true;
|
||||
else
|
||||
std::sscanf(line, "drm-memory-vram: %llu", &vram);
|
||||
std::fclose(file);
|
||||
if (drm && std::find(clients.begin(), clients.end(), client) == clients.end())
|
||||
{
|
||||
clients.push_back(client);
|
||||
total += vram;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
ID3D12Resource* create_texture(UINT width, UINT height, DXGI_FORMAT format, D3D12_RESOURCE_FLAGS flags,
|
||||
D3D12_RESOURCE_STATES state)
|
||||
{
|
||||
D3D12_RESOURCE_DESC desc = {};
|
||||
desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||
desc.Width = width;
|
||||
desc.Height = height;
|
||||
desc.DepthOrArraySize = 1;
|
||||
desc.MipLevels = 1;
|
||||
desc.Format = format;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.Flags = flags;
|
||||
D3D12_HEAP_PROPERTIES heap = {};
|
||||
heap.Type = D3D12_HEAP_TYPE_DEFAULT;
|
||||
ID3D12Resource* resource = nullptr;
|
||||
if (FAILED(g_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, state, nullptr,
|
||||
__uuidof(ID3D12Resource), reinterpret_cast<void**>(&resource))))
|
||||
return nullptr;
|
||||
return resource;
|
||||
}
|
||||
|
||||
ID3D12Resource* create_buffer(UINT64 bytes, D3D12_HEAP_TYPE type, uint8_t** mapped)
|
||||
{
|
||||
D3D12_RESOURCE_DESC desc = {};
|
||||
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
|
||||
desc.Width = bytes;
|
||||
desc.Height = 1;
|
||||
desc.DepthOrArraySize = 1;
|
||||
desc.MipLevels = 1;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
||||
D3D12_HEAP_PROPERTIES heap = {};
|
||||
heap.Type = type;
|
||||
ID3D12Resource* buffer = nullptr;
|
||||
const D3D12_RESOURCE_STATES state =
|
||||
type == D3D12_HEAP_TYPE_UPLOAD ? D3D12_RESOURCE_STATE_GENERIC_READ : D3D12_RESOURCE_STATE_COPY_DEST;
|
||||
if (FAILED(g_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, state, nullptr,
|
||||
__uuidof(ID3D12Resource), reinterpret_cast<void**>(&buffer))) ||
|
||||
FAILED(buffer->Map(0, nullptr, reinterpret_cast<void**>(mapped))))
|
||||
return nullptr;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void transition(ID3D12Resource* resource, D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after)
|
||||
{
|
||||
if (before == after)
|
||||
return;
|
||||
D3D12_RESOURCE_BARRIER barrier = {};
|
||||
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
||||
barrier.Transition.pResource = resource;
|
||||
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
||||
barrier.Transition.StateBefore = before;
|
||||
barrier.Transition.StateAfter = after;
|
||||
g_list->ResourceBarrier(1, &barrier);
|
||||
}
|
||||
|
||||
bool submit_and_wait()
|
||||
{
|
||||
if (FAILED(g_list->Close()))
|
||||
return false;
|
||||
ID3D12CommandList* lists[] = {g_list};
|
||||
g_queue->ExecuteCommandLists(1, lists);
|
||||
g_queue->Signal(g_fence, ++g_fenceValue);
|
||||
g_fence->SetEventOnCompletion(g_fenceValue, g_event);
|
||||
const bool done = WaitForSingleObject(g_event, 30000) == WAIT_OBJECT_0;
|
||||
g_allocator->Reset();
|
||||
g_list->Reset(g_allocator, nullptr);
|
||||
return done && SUCCEEDED(g_device->GetDeviceRemovedReason());
|
||||
}
|
||||
|
||||
// Uploads tightly packed rows into a texture (left in `state`).
|
||||
struct Uploader
|
||||
{
|
||||
ID3D12Resource* staging = nullptr;
|
||||
uint8_t* mapped = nullptr;
|
||||
UINT64 capacity = 0, offset = 0;
|
||||
void upload(ID3D12Resource* texture, UINT width, UINT height, DXGI_FORMAT format, const void* data,
|
||||
size_t texelBytes, D3D12_RESOURCE_STATES state)
|
||||
{
|
||||
D3D12_RESOURCE_DESC desc = {};
|
||||
desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||
desc.Width = width;
|
||||
desc.Height = height;
|
||||
desc.DepthOrArraySize = 1;
|
||||
desc.MipLevels = 1;
|
||||
desc.Format = format;
|
||||
desc.SampleDesc.Count = 1;
|
||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {};
|
||||
UINT rows = 0;
|
||||
UINT64 rowBytes = 0, total = 0;
|
||||
g_device->GetCopyableFootprints(&desc, 0, 1, 0, &footprint, &rows, &rowBytes, &total);
|
||||
offset = (offset + 511) & ~UINT64(511);
|
||||
footprint.Offset = offset;
|
||||
for (UINT row = 0; row < rows; ++row)
|
||||
std::memcpy(mapped + offset + row * footprint.Footprint.RowPitch,
|
||||
static_cast<const uint8_t*>(data) + row * texelBytes * width, texelBytes * width);
|
||||
offset += total;
|
||||
transition(texture, state, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
D3D12_TEXTURE_COPY_LOCATION dst = {};
|
||||
dst.pResource = texture;
|
||||
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
||||
D3D12_TEXTURE_COPY_LOCATION src = {};
|
||||
src.pResource = staging;
|
||||
src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
||||
src.PlacedFootprint = footprint;
|
||||
g_list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
|
||||
transition(texture, D3D12_RESOURCE_STATE_COPY_DEST, state);
|
||||
}
|
||||
};
|
||||
|
||||
float halton(uint32_t index, uint32_t base)
|
||||
{
|
||||
float result = 0, fraction = 1;
|
||||
while (index > 0)
|
||||
{
|
||||
fraction /= base;
|
||||
result += fraction * (index % base);
|
||||
index /= base;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void write_ppm(const std::string& path, uint32_t width, uint32_t height, const std::vector<V3>& radiance)
|
||||
{
|
||||
FILE* file = std::fopen(path.c_str(), "wb");
|
||||
if (file == nullptr)
|
||||
return;
|
||||
std::fprintf(file, "P6\n%u %u\n255\n", width, height);
|
||||
std::vector<uint8_t> row(width * 3);
|
||||
for (uint32_t y = 0; y < height; ++y)
|
||||
{
|
||||
for (uint32_t x = 0; x < width; ++x)
|
||||
{
|
||||
const V3 c = radiance[static_cast<size_t>(y) * width + x];
|
||||
const float rgb[3] = {c.x, c.y, c.z};
|
||||
for (int k = 0; k < 3; ++k)
|
||||
{
|
||||
const float v = std::max(0.0f, rgb[k]);
|
||||
row[x * 3 + k] = static_cast<uint8_t>(std::lround(saturate(std::pow(v / (1 + v), 1 / 2.2f)) * 255));
|
||||
}
|
||||
}
|
||||
std::fwrite(row.data(), 1, row.size(), file);
|
||||
}
|
||||
std::fclose(file);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc < 3)
|
||||
{
|
||||
std::printf("usage: rr_backend_check.exe SHIM_DLL OUT_DIR [--render WxH] [--output WxH] [--frames N] "
|
||||
"[--feature 13|1] [--hw-depth] [--separate-roughness] [--specular-hit] [--separate-eval-params] [--water] [--measure-all]\n");
|
||||
return 2;
|
||||
}
|
||||
const std::string outDir = argv[2];
|
||||
UINT rw = 960, rh = 540, ow = 1920, oh = 1080, frames = 48, featureId = 13;
|
||||
bool hwDepth = false, separateRoughness = false, specularHit = false, separateEvalParams = false, measureAll = false;
|
||||
for (int i = 3; i < argc; ++i)
|
||||
{
|
||||
const std::string arg = argv[i];
|
||||
if (arg == "--render" && i + 1 < argc)
|
||||
std::sscanf(argv[++i], "%ux%u", &rw, &rh);
|
||||
else if (arg == "--output" && i + 1 < argc)
|
||||
std::sscanf(argv[++i], "%ux%u", &ow, &oh);
|
||||
else if (arg == "--frames" && i + 1 < argc)
|
||||
frames = static_cast<UINT>(std::atoi(argv[++i]));
|
||||
else if (arg == "--feature" && i + 1 < argc)
|
||||
featureId = static_cast<UINT>(std::atoi(argv[++i]));
|
||||
else if (arg == "--hw-depth")
|
||||
hwDepth = true;
|
||||
else if (arg == "--separate-roughness")
|
||||
separateRoughness = true;
|
||||
else if (arg == "--specular-hit")
|
||||
specularHit = true;
|
||||
else if (arg == "--separate-eval-params")
|
||||
separateEvalParams = true;
|
||||
else if (arg == "--water")
|
||||
fsr_scene::g_water = true;
|
||||
else if (arg == "--measure-all")
|
||||
measureAll = true;
|
||||
}
|
||||
const bool rr = featureId == 13;
|
||||
CreateDirectoryA(outDir.c_str(), nullptr);
|
||||
g_json = std::fopen((outDir + "\\check.json").c_str(), "w");
|
||||
if (g_json)
|
||||
std::fputs("{", g_json);
|
||||
json_field("feature", format("%u", featureId));
|
||||
json_field("render_size", format("[%u, %u]", rw, rh));
|
||||
json_field("output_size", format("[%u, %u]", ow, oh));
|
||||
json_field("options", format("{\"hw_depth\": %s, \"separate_roughness\": %s, \"specular_hit\": %s, \"separate_eval_params\": %s}",
|
||||
hwDepth ? "true" : "false", separateRoughness ? "true" : "false",
|
||||
specularHit ? "true" : "false", separateEvalParams ? "true" : "false"));
|
||||
auto finish = [&](int code) {
|
||||
if (g_json)
|
||||
{
|
||||
std::fputs("\n}\n", g_json);
|
||||
std::fclose(g_json);
|
||||
}
|
||||
std::printf("overall=%s\n", code == 0 ? "PASS" : "FAIL");
|
||||
return code;
|
||||
};
|
||||
|
||||
if (FAILED(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_0, __uuidof(ID3D12Device), reinterpret_cast<void**>(&g_device))))
|
||||
{
|
||||
check("device", false, "D3D12CreateDevice failed");
|
||||
return finish(1);
|
||||
}
|
||||
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
|
||||
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
|
||||
g_device->CreateCommandQueue(&queueDesc, __uuidof(ID3D12CommandQueue), reinterpret_cast<void**>(&g_queue));
|
||||
g_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, __uuidof(ID3D12CommandAllocator),
|
||||
reinterpret_cast<void**>(&g_allocator));
|
||||
g_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_allocator, nullptr,
|
||||
__uuidof(ID3D12GraphicsCommandList), reinterpret_cast<void**>(&g_list));
|
||||
g_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence), reinterpret_cast<void**>(&g_fence));
|
||||
g_event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
const unsigned long long vramStart = process_vram_kib();
|
||||
|
||||
HMODULE shim = LoadLibraryA(argv[1]);
|
||||
if (shim == nullptr)
|
||||
{
|
||||
check("load", false, format("LoadLibrary(%s) failed: %lu", argv[1], GetLastError()));
|
||||
return finish(1);
|
||||
}
|
||||
auto init = reinterpret_cast<PFN_Init_Ext>(reinterpret_cast<void*>(GetProcAddress(shim, "NVSDK_NGX_D3D12_Init_Ext")));
|
||||
auto capability = reinterpret_cast<PFN_Parameters>(reinterpret_cast<void*>(GetProcAddress(shim, "NVSDK_NGX_D3D12_GetCapabilityParameters")));
|
||||
auto allocate = reinterpret_cast<PFN_Parameters>(reinterpret_cast<void*>(GetProcAddress(shim, "NVSDK_NGX_D3D12_AllocateParameters")));
|
||||
auto createFeature = reinterpret_cast<PFN_CreateFeature>(reinterpret_cast<void*>(GetProcAddress(shim, "NVSDK_NGX_D3D12_CreateFeature")));
|
||||
auto evaluate = reinterpret_cast<PFN_Evaluate>(reinterpret_cast<void*>(GetProcAddress(shim, "NVSDK_NGX_D3D12_EvaluateFeature")));
|
||||
auto release = reinterpret_cast<PFN_Release>(reinterpret_cast<void*>(GetProcAddress(shim, "NVSDK_NGX_D3D12_ReleaseFeature")));
|
||||
auto shutdown = reinterpret_cast<PFN_Shutdown>(reinterpret_cast<void*>(GetProcAddress(shim, "NVSDK_NGX_D3D12_Shutdown")));
|
||||
auto requirements = reinterpret_cast<PFN_Requirements>(reinterpret_cast<void*>(GetProcAddress(shim, "NVSDK_NGX_D3D12_GetFeatureRequirements")));
|
||||
if (!init || !capability || !allocate || !createFeature || !evaluate || !release || !shutdown || !requirements)
|
||||
{
|
||||
check("load", false, "missing NGX exports");
|
||||
return finish(1);
|
||||
}
|
||||
|
||||
// Discovery before Init, as games do.
|
||||
wchar_t dataPath[MAX_PATH];
|
||||
GetTempPathW(MAX_PATH, dataPath);
|
||||
NgxFeatureDiscovery discovery = {};
|
||||
discovery.sdkVersion = 0x15;
|
||||
discovery.feature = featureId;
|
||||
discovery.identifier.value.applicationId = 241534723ULL;
|
||||
discovery.dataPath = dataPath;
|
||||
NgxFeatureRequirement requirement = {};
|
||||
IDXGIFactory4* factory = nullptr;
|
||||
IDXGIAdapter* adapter = nullptr;
|
||||
LUID luid;
|
||||
g_device->GetAdapterLuid(&luid);
|
||||
CreateDXGIFactory1(__uuidof(IDXGIFactory4), reinterpret_cast<void**>(&factory));
|
||||
factory->EnumAdapterByLuid(luid, __uuidof(IDXGIAdapter), reinterpret_cast<void**>(&adapter));
|
||||
const NgxResult discovered = requirements(adapter, &discovery, &requirement);
|
||||
check("requirements_before_init", discovered == NGX_SUCCESS && requirement.supported == 0,
|
||||
format("result 0x%08x, FeatureSupported %u", discovered, requirement.supported));
|
||||
adapter->Release();
|
||||
factory->Release();
|
||||
|
||||
const auto initStart = std::chrono::steady_clock::now();
|
||||
const NgxResult initialized = init(241534723ULL, dataPath, g_device, 0x15, nullptr);
|
||||
const double initMs = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - initStart).count();
|
||||
check("init", initialized == NGX_SUCCESS, format("0x%08x in %.0f ms", initialized, initMs));
|
||||
if (initialized != NGX_SUCCESS)
|
||||
return finish(1);
|
||||
void* capabilities = nullptr;
|
||||
capability(&capabilities);
|
||||
int available = -1;
|
||||
void* optimal = nullptr;
|
||||
const char* key = rr ? "SuperSamplingDenoising.Available" : "SuperSampling.Available";
|
||||
d4r_ngx_get_int(capabilities, key, &available);
|
||||
d4r_ngx_get_void(capabilities, rr ? "DLSSDOptimalSettingsCallback" : "DLSSOptimalSettingsCallback", &optimal);
|
||||
check("capability", available == 1, format("%s=%d, optimal-settings callback %s", key, available, optimal ? "set" : "absent"));
|
||||
|
||||
// Inputs at render size, output at output size.
|
||||
const auto srv = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
||||
ID3D12Resource* color = create_texture(rw, rh, DXGI_FORMAT_R16G16B16A16_FLOAT, D3D12_RESOURCE_FLAG_NONE, srv);
|
||||
ID3D12Resource* depth = create_texture(rw, rh, DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE, srv);
|
||||
ID3D12Resource* motion = create_texture(rw, rh, DXGI_FORMAT_R16G16_FLOAT, D3D12_RESOURCE_FLAG_NONE, srv);
|
||||
ID3D12Resource* normals = create_texture(rw, rh, DXGI_FORMAT_R16G16B16A16_FLOAT, D3D12_RESOURCE_FLAG_NONE, srv);
|
||||
ID3D12Resource* roughness = create_texture(rw, rh, DXGI_FORMAT_R16_FLOAT, D3D12_RESOURCE_FLAG_NONE, srv);
|
||||
ID3D12Resource* diffuse = create_texture(rw, rh, DXGI_FORMAT_R8G8B8A8_UNORM, D3D12_RESOURCE_FLAG_NONE, srv);
|
||||
ID3D12Resource* specular = create_texture(rw, rh, DXGI_FORMAT_R8G8B8A8_UNORM, D3D12_RESOURCE_FLAG_NONE, srv);
|
||||
ID3D12Resource* hitDistance = create_texture(rw, rh, DXGI_FORMAT_R16_FLOAT, D3D12_RESOURCE_FLAG_NONE, srv);
|
||||
// The output starts out holding a sentinel, so an unwritten texel is detectable.
|
||||
ID3D12Resource* output = create_texture(ow, oh, DXGI_FORMAT_R16G16B16A16_FLOAT,
|
||||
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||
Uploader uploader;
|
||||
uploader.capacity = 64ull << 20;
|
||||
uploader.staging = create_buffer(uploader.capacity, D3D12_HEAP_TYPE_UPLOAD, &uploader.mapped);
|
||||
D3D12_RESOURCE_DESC outputDesc = {};
|
||||
outputDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||
outputDesc.Width = ow;
|
||||
outputDesc.Height = oh;
|
||||
outputDesc.DepthOrArraySize = 1;
|
||||
outputDesc.MipLevels = 1;
|
||||
outputDesc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT;
|
||||
outputDesc.SampleDesc.Count = 1;
|
||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT outputFootprint = {};
|
||||
UINT outputRows = 0;
|
||||
UINT64 outputRowBytes = 0, outputTotal = 0;
|
||||
g_device->GetCopyableFootprints(&outputDesc, 0, 1, 0, &outputFootprint, &outputRows, &outputRowBytes, &outputTotal);
|
||||
uint8_t* readMapped = nullptr;
|
||||
ID3D12Resource* readback = create_buffer(outputTotal, D3D12_HEAP_TYPE_READBACK, &readMapped);
|
||||
if (!color || !depth || !motion || !normals || !roughness || !diffuse || !specular || !hitDistance || !output ||
|
||||
!uploader.staging || !readback)
|
||||
{
|
||||
check("resources", false, "texture creation");
|
||||
return finish(1);
|
||||
}
|
||||
|
||||
void* parameters = nullptr;
|
||||
allocate(¶meters);
|
||||
d4r_ngx_set_uint(parameters, "Width", rw);
|
||||
d4r_ngx_set_uint(parameters, "Height", rh);
|
||||
d4r_ngx_set_uint(parameters, "OutWidth", ow);
|
||||
d4r_ngx_set_uint(parameters, "OutHeight", oh);
|
||||
d4r_ngx_set_int(parameters, "PerfQualityValue", 2);
|
||||
d4r_ngx_set_int(parameters, "DLSS.Feature.Create.Flags", 1 | 2); // HDR | MVLowRes
|
||||
if (rr)
|
||||
{
|
||||
d4r_ngx_set_int(parameters, "DLSS.Denoise.Mode", 1);
|
||||
d4r_ngx_set_int(parameters, "DLSS.Roughness.Mode", separateRoughness ? 0 : 1);
|
||||
d4r_ngx_set_int(parameters, "DLSS.Use.HW.Depth", hwDepth ? 1 : 0);
|
||||
}
|
||||
NgxHandle* feature = nullptr;
|
||||
const auto createStart = std::chrono::steady_clock::now();
|
||||
const NgxResult created = createFeature(g_list, featureId, parameters, &feature);
|
||||
const double createMs = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - createStart).count();
|
||||
check("create", created == NGX_SUCCESS && feature != nullptr, format("0x%08x in %.0f ms", created, createMs));
|
||||
json_field("create_ms", format("%.1f", createMs));
|
||||
if (created != NGX_SUCCESS)
|
||||
return finish(1);
|
||||
|
||||
// Keep the depth/roughness modes only on the creation object, as callers are allowed to do.
|
||||
// Frame resources and camera settings below go onto a fresh object without those scalars.
|
||||
if (separateEvalParams)
|
||||
allocate(¶meters);
|
||||
|
||||
const M4 projection = projection_matrix(1.0f, static_cast<float>(rw) / rh, 0.1f, 1000.0f);
|
||||
const uint32_t moveStart = frames / 3, cutFrame = frames * 2 / 3;
|
||||
auto camera_at = [&](uint32_t f) -> Camera {
|
||||
if (f >= cutFrame)
|
||||
return {{3.5f, 1.6f, 1.5f}, {-0.5f, -0.6f, 7.5f}};
|
||||
const float t = f >= moveStart ? static_cast<float>(f - moveStart) : 0.0f;
|
||||
return {{-0.6f + 0.04f * t, 0.6f, -1.5f}, {0.0f + 0.02f * t, -0.1f, 6.5f}};
|
||||
};
|
||||
Frame scene, truth;
|
||||
Camera previous = camera_at(0);
|
||||
std::vector<double> callUs, totalUs;
|
||||
std::string log = "[";
|
||||
bool allEvaluated = true, sized = true, finite = true;
|
||||
double errorBeforeCut = -1, errorFinal = -1, errorAtCut = -1;
|
||||
std::vector<uint16_t> hit(static_cast<size_t>(rw) * rh), rough(static_cast<size_t>(rw) * rh);
|
||||
std::vector<float> view(16), clip(16);
|
||||
for (uint32_t f = 0; f < frames; ++f)
|
||||
{
|
||||
const Camera camera = camera_at(f);
|
||||
const bool reset = f == 0 || f == cutFrame;
|
||||
const uint32_t phase = f % 16 + 1;
|
||||
const float jx = halton(phase, 2) - 0.5f, jy = halton(phase, 3) - 0.5f;
|
||||
render(scene, rw, rh, camera, reset ? camera : previous, projection, f, jx, jy);
|
||||
uploader.offset = 0;
|
||||
uploader.upload(color, rw, rh, DXGI_FORMAT_R16G16B16A16_FLOAT, scene.color.data(), 8, srv);
|
||||
if (hwDepth)
|
||||
uploader.upload(depth, rw, rh, DXGI_FORMAT_R32_FLOAT, scene.hardwareDepth.data(), 4, srv);
|
||||
else
|
||||
{
|
||||
std::vector<float> linear(scene.depth);
|
||||
for (size_t i = 0; i < linear.size(); ++i)
|
||||
linear[i] = scene.valid[i] ? linear[i] : 1.0e7f; // the sky: beyond any geometry
|
||||
uploader.upload(depth, rw, rh, DXGI_FORMAT_R32_FLOAT, linear.data(), 4, srv);
|
||||
}
|
||||
uploader.upload(motion, rw, rh, DXGI_FORMAT_R16G16_FLOAT, scene.motionPixels.data(), 4, srv);
|
||||
if (rr)
|
||||
{
|
||||
uploader.upload(normals, rw, rh, DXGI_FORMAT_R16G16B16A16_FLOAT, scene.normalsRoughness.data(), 8, srv);
|
||||
uploader.upload(diffuse, rw, rh, DXGI_FORMAT_R8G8B8A8_UNORM, scene.diffuseAlbedoLinear.data(), 4, srv);
|
||||
uploader.upload(specular, rw, rh, DXGI_FORMAT_R8G8B8A8_UNORM, scene.specularAlbedoLinear.data(), 4, srv);
|
||||
if (separateRoughness)
|
||||
{
|
||||
for (size_t i = 0; i < rough.size(); ++i)
|
||||
rough[i] = scene.normalsRoughness[i * 4 + 3];
|
||||
uploader.upload(roughness, rw, rh, DXGI_FORMAT_R16_FLOAT, rough.data(), 2, srv);
|
||||
}
|
||||
if (specularHit)
|
||||
{
|
||||
for (size_t i = 0; i < hit.size(); ++i)
|
||||
hit[i] = scene.noisy[kIndirectSpecular][i * 4 + 3];
|
||||
uploader.upload(hitDistance, rw, rh, DXGI_FORMAT_R16_FLOAT, hit.data(), 2, srv);
|
||||
}
|
||||
}
|
||||
d4r_ngx_set_d3d12_resource(parameters, "Color", color);
|
||||
d4r_ngx_set_d3d12_resource(parameters, "Depth", depth);
|
||||
d4r_ngx_set_d3d12_resource(parameters, "MotionVectors", motion);
|
||||
d4r_ngx_set_d3d12_resource(parameters, "Output", output);
|
||||
d4r_ngx_set_float(parameters, "Jitter.Offset.X", -jx); // DLSS: minus the sample offset
|
||||
d4r_ngx_set_float(parameters, "Jitter.Offset.Y", -jy);
|
||||
d4r_ngx_set_float(parameters, "MV.Scale.X", 1.0f);
|
||||
d4r_ngx_set_float(parameters, "MV.Scale.Y", 1.0f);
|
||||
d4r_ngx_set_int(parameters, "Reset", reset ? 1 : 0);
|
||||
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Width", rw);
|
||||
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Height", rh);
|
||||
if (rr)
|
||||
{
|
||||
d4r_ngx_set_d3d12_resource(parameters, "GBuffer.Normals", normals);
|
||||
d4r_ngx_set_d3d12_resource(parameters, "GBuffer.Roughness", separateRoughness ? roughness : nullptr);
|
||||
d4r_ngx_set_d3d12_resource(parameters, "GBuffer.DiffuseAlbedo", diffuse);
|
||||
d4r_ngx_set_d3d12_resource(parameters, "GBuffer.SpecularAlbedo", specular);
|
||||
d4r_ngx_set_d3d12_resource(parameters, "DLSSD.SpecularHitDistance", specularHit ? hitDistance : nullptr);
|
||||
const M4 viewMatrix = view_matrix(camera.eye, camera.target);
|
||||
std::memcpy(view.data(), &viewMatrix, 64);
|
||||
std::memcpy(clip.data(), &projection, 64);
|
||||
d4r_ngx_set_void(parameters, "WorldToViewMatrix", view.data());
|
||||
d4r_ngx_set_void(parameters, "ViewToClipMatrix", clip.data());
|
||||
}
|
||||
// Mark the output with a sentinel (NaN) the backend has to overwrite everywhere.
|
||||
{
|
||||
std::vector<uint16_t> sentinel(static_cast<size_t>(ow) * oh * 4, 0x7e00u);
|
||||
uploader.upload(output, ow, oh, DXGI_FORMAT_R16G16B16A16_FLOAT, sentinel.data(), 8,
|
||||
D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||
}
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
const NgxResult evaluated = evaluate(g_list, feature, parameters, nullptr);
|
||||
const auto recorded = std::chrono::steady_clock::now();
|
||||
transition(output, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
D3D12_TEXTURE_COPY_LOCATION dst = {};
|
||||
dst.pResource = readback;
|
||||
dst.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
||||
dst.PlacedFootprint = outputFootprint;
|
||||
D3D12_TEXTURE_COPY_LOCATION src = {};
|
||||
src.pResource = output;
|
||||
src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
||||
g_list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
|
||||
transition(output, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||
const bool submitted = submit_and_wait();
|
||||
const auto end = std::chrono::steady_clock::now();
|
||||
callUs.push_back(std::chrono::duration<double, std::micro>(recorded - start).count());
|
||||
totalUs.push_back(std::chrono::duration<double, std::micro>(end - start).count());
|
||||
if (evaluated != NGX_SUCCESS || !submitted)
|
||||
{
|
||||
allEvaluated = false;
|
||||
std::printf("frame %u: EvaluateFeature 0x%08x, submit %s\n", f, evaluated, submitted ? "ok" : "FAILED");
|
||||
if (!submitted)
|
||||
break;
|
||||
}
|
||||
|
||||
// Ground truth (the noise-free scene rendered at the output size from the same camera), on the frames
|
||||
// that are reported and every 8th.
|
||||
const bool measured = measureAll || f % 8 == 7 || f == 0 || f + 1 == moveStart || f + 1 == cutFrame || f == cutFrame ||
|
||||
f + 1 == frames;
|
||||
if (!measured)
|
||||
{
|
||||
previous = camera;
|
||||
continue;
|
||||
}
|
||||
render(truth, ow, oh, camera, camera, projection_matrix(1.0f, static_cast<float>(ow) / oh, 0.1f, 1000.0f), f);
|
||||
std::vector<V3> result(static_cast<size_t>(ow) * oh);
|
||||
double error = 0, noisyError = 0, waterError = 0;
|
||||
size_t unwritten = 0, nonFinite = 0, n = 0, waterN = 0;
|
||||
for (UINT y = 0; y < oh; ++y)
|
||||
for (UINT x = 0; x < ow; ++x)
|
||||
{
|
||||
const uint16_t* texel = reinterpret_cast<const uint16_t*>(readMapped + outputFootprint.Offset +
|
||||
y * outputFootprint.Footprint.RowPitch) + x * 4;
|
||||
const size_t i = static_cast<size_t>(y) * ow + x;
|
||||
const V3 value{from_half(texel[0]), from_half(texel[1]), from_half(texel[2])};
|
||||
if (texel[0] == 0x7e00u && texel[1] == 0x7e00u && texel[2] == 0x7e00u)
|
||||
{
|
||||
++unwritten;
|
||||
continue;
|
||||
}
|
||||
if (!std::isfinite(value.x) || !std::isfinite(value.y) || !std::isfinite(value.z))
|
||||
{
|
||||
++nonFinite;
|
||||
continue;
|
||||
}
|
||||
result[i] = value;
|
||||
// Compared in a tonemapped space (x / (1 + x)), so highlights do not dominate.
|
||||
const V3 t = truth.colorClean[i];
|
||||
const float tv[3] = {t.x, t.y, t.z}, ov[3] = {value.x, value.y, value.z};
|
||||
for (int c = 0; c < 3; ++c)
|
||||
{
|
||||
const double d = ov[c] / (1 + std::max(0.0f, ov[c])) - tv[c] / (1 + tv[c]);
|
||||
error += d * d;
|
||||
++n;
|
||||
if (from_half(truth.normalsRoughness[i * 4 + 3]) < 0.06f)
|
||||
{
|
||||
waterError += d * d;
|
||||
++waterN;
|
||||
}
|
||||
}
|
||||
}
|
||||
// The noisy input itself, nearest-upsampled: the error with no denoising and no reconstruction.
|
||||
for (UINT y = 0; y < oh; ++y)
|
||||
for (UINT x = 0; x < ow; ++x)
|
||||
{
|
||||
const size_t i = static_cast<size_t>(y) * ow + x;
|
||||
const size_t s = static_cast<size_t>(y * rh / oh) * rw + x * rw / ow;
|
||||
const V3 t = truth.colorClean[i];
|
||||
const float tv[3] = {t.x, t.y, t.z};
|
||||
for (int c = 0; c < 3; ++c)
|
||||
{
|
||||
const float v = std::max(0.0f, from_half(scene.color[s * 4 + c]));
|
||||
const double d = v / (1 + v) - tv[c] / (1 + tv[c]);
|
||||
noisyError += d * d;
|
||||
}
|
||||
}
|
||||
const double rmse = n ? std::sqrt(error / n) : -1, noisyRmse = std::sqrt(noisyError / (3.0 * ow * oh));
|
||||
sized = sized && unwritten == 0;
|
||||
finite = finite && nonFinite == 0;
|
||||
if (f + 1 == cutFrame)
|
||||
errorBeforeCut = rmse;
|
||||
if (f == cutFrame)
|
||||
errorAtCut = rmse;
|
||||
if (f + 1 == frames)
|
||||
errorFinal = rmse;
|
||||
const double psnr = rmse > 0 ? 20 * std::log10(1.0 / rmse) : 0;
|
||||
const double waterRmse = waterN ? std::sqrt(waterError / waterN) : -1;
|
||||
if (fsr_scene::g_water)
|
||||
std::printf("frame %u: rmse %.5f water %.5f\n", f, rmse, waterRmse);
|
||||
log += format("%s\n {\"frame\": %u, \"reset\": %s, \"result\": %u, \"rmse\": %.5f, \"psnr_db\": %.2f, \"water_rmse\": %.5f, "
|
||||
"\"noisy_input_rmse\": %.5f, \"unwritten\": %zu, \"nonfinite\": %zu, \"call_us\": %.0f, "
|
||||
"\"submit_wait_us\": %.0f}",
|
||||
f ? "," : "", f, reset ? "true" : "false", evaluated, rmse, psnr, waterRmse, noisyRmse, unwritten, nonFinite,
|
||||
callUs.back(), totalUs.back());
|
||||
if (f == 0 || f + 1 == moveStart || f + 1 == cutFrame || f == cutFrame || f + 1 == frames)
|
||||
{
|
||||
write_ppm(format("%s\\frame%03u_output.ppm", outDir.c_str(), f), ow, oh, result);
|
||||
write_ppm(format("%s\\frame%03u_truth.ppm", outDir.c_str(), f), ow, oh, truth.colorClean);
|
||||
std::vector<V3> input(static_cast<size_t>(rw) * rh);
|
||||
for (size_t i = 0; i < input.size(); ++i)
|
||||
input[i] = {from_half(scene.color[i * 4]), from_half(scene.color[i * 4 + 1]), from_half(scene.color[i * 4 + 2])};
|
||||
write_ppm(format("%s\\frame%03u_input.ppm", outDir.c_str(), f), rw, rh, input);
|
||||
}
|
||||
previous = camera;
|
||||
}
|
||||
json_field("frame_log", log + "\n ]");
|
||||
const unsigned long long vramPeak = process_vram_kib();
|
||||
std::vector<double> warmCall(callUs.begin() + std::min<size_t>(callUs.size(), 4), callUs.end());
|
||||
std::vector<double> warmTotal(totalUs.begin() + std::min<size_t>(totalUs.size(), 4), totalUs.end());
|
||||
std::sort(warmCall.begin(), warmCall.end());
|
||||
std::sort(warmTotal.begin(), warmTotal.end());
|
||||
const double medianCall = warmCall.empty() ? -1 : warmCall[warmCall.size() / 2];
|
||||
const double medianTotal = warmTotal.empty() ? -1 : warmTotal[warmTotal.size() / 2];
|
||||
json_field("timing_us", format("{\"evaluate_call_median\": %.0f, \"submit_wait_median\": %.0f, \"first_frame_submit_wait\": %.0f}",
|
||||
medianCall, medianTotal, totalUs.empty() ? -1.0 : totalUs[0]));
|
||||
json_field("vram_mib", format("{\"before_init\": %.1f, \"after_frames\": %.1f}", vramStart / 1024.0, vramPeak / 1024.0));
|
||||
check("evaluations", allEvaluated, format("%u frames, EvaluateFeature call median %.0f us, call+submit+wait median %.0f us",
|
||||
frames, medianCall, medianTotal));
|
||||
check("output_size", sized, format("every texel of the %ux%u output written on every frame", ow, oh));
|
||||
check("output_finite", finite, "no NaN/Inf");
|
||||
json_field("rmse", format("{\"before_cut\": %.5f, \"at_cut\": %.5f, \"final\": %.5f}", errorBeforeCut, errorAtCut, errorFinal));
|
||||
std::printf("tonemapped RMSE vs ground truth: before cut %.5f, at cut %.5f, final %.5f\n", errorBeforeCut, errorAtCut,
|
||||
errorFinal);
|
||||
|
||||
const NgxResult released = release(feature);
|
||||
// Let the shim's deferred cleanup see the list retire.
|
||||
submit_and_wait();
|
||||
const NgxResult shut = shutdown();
|
||||
check("release", released == NGX_SUCCESS && shut == NGX_SUCCESS, format("release 0x%08x, shutdown 0x%08x", released, shut));
|
||||
return finish(g_failed ? 1 : 0);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// Compare fused storage output with the original RGBA16F copy + R11G11B10 blit.
|
||||
#include "d4r_vulkan_staging.h"
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
static void check(VkResult r) { if (r != VK_SUCCESS) { std::fprintf(stderr,"Vulkan error %d\n",r); std::exit(1); } }
|
||||
struct Image { VkImage image; VkImageView view; VkDeviceMemory memory; VkFormat format; bool depth; };
|
||||
int main() {
|
||||
constexpr uint32_t W=257, H=257;
|
||||
constexpr size_t BYTES=W*H*16;
|
||||
VkApplicationInfo app={VK_STRUCTURE_TYPE_APPLICATION_INFO}; app.apiVersion=VK_API_VERSION_1_1;
|
||||
VkInstanceCreateInfo ic={VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};ic.pApplicationInfo=&app;
|
||||
VkInstance instance;check(vkCreateInstance(&ic,nullptr,&instance));
|
||||
uint32_t count=0;check(vkEnumeratePhysicalDevices(instance,&count,nullptr));
|
||||
std::vector<VkPhysicalDevice> devices(count);check(vkEnumeratePhysicalDevices(instance,&count,devices.data()));
|
||||
VkPhysicalDevice physical=VK_NULL_HANDLE;
|
||||
for(auto d:devices){VkPhysicalDeviceProperties p;vkGetPhysicalDeviceProperties(d,&p);if(p.vendorID==0x1002){physical=d;std::printf("GPU: %s\n",p.deviceName);break;}}
|
||||
assert(physical);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physical,&count,nullptr);std::vector<VkQueueFamilyProperties> families(count);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physical,&count,families.data());
|
||||
uint32_t family=0;while(family<count && !(families[family].queueFlags&VK_QUEUE_COMPUTE_BIT))++family;assert(family<count);
|
||||
float priority=1;VkDeviceQueueCreateInfo qi={VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};qi.queueFamilyIndex=family;qi.queueCount=1;qi.pQueuePriorities=&priority;
|
||||
VkPhysicalDeviceFeatures supported, enabled={};vkGetPhysicalDeviceFeatures(physical,&supported);
|
||||
enabled.shaderStorageImageExtendedFormats=supported.shaderStorageImageExtendedFormats;
|
||||
VkDeviceCreateInfo dc={VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};dc.queueCreateInfoCount=1;dc.pQueueCreateInfos=&qi;dc.pEnabledFeatures=&enabled;
|
||||
VkDevice device;check(vkCreateDevice(physical,&dc,nullptr,&device));VkQueue queue;vkGetDeviceQueue(device,family,0,&queue);
|
||||
VkPhysicalDeviceMemoryProperties memory;vkGetPhysicalDeviceMemoryProperties(physical,&memory);
|
||||
auto allocate=[&](VkMemoryRequirements req,VkMemoryPropertyFlags flags){
|
||||
uint32_t type=0;while(type<memory.memoryTypeCount && (!(req.memoryTypeBits&(1u<<type)) || (memory.memoryTypes[type].propertyFlags&flags)!=flags))++type;assert(type<memory.memoryTypeCount);
|
||||
VkMemoryAllocateInfo a={VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};a.allocationSize=req.size;a.memoryTypeIndex=type;VkDeviceMemory m;check(vkAllocateMemory(device,&a,nullptr,&m));return m;
|
||||
};
|
||||
auto image=[&](VkFormat format,bool output){
|
||||
Image result={};result.format=format;result.depth=format==VK_FORMAT_D32_SFLOAT_S8_UINT;
|
||||
VkImageCreateInfo info={VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};info.imageType=VK_IMAGE_TYPE_2D;info.format=format;info.extent={W,H,1};info.mipLevels=info.arrayLayers=1;info.samples=VK_SAMPLE_COUNT_1_BIT;info.tiling=VK_IMAGE_TILING_OPTIMAL;
|
||||
// Transfers build the reference and read back packed R11G11B10 words.
|
||||
info.usage=VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | (output ? VK_IMAGE_USAGE_STORAGE_BIT : 0);
|
||||
check(vkCreateImage(device,&info,nullptr,&result.image));VkMemoryRequirements req;vkGetImageMemoryRequirements(device,result.image,&req);
|
||||
result.memory=allocate(req,0);check(vkBindImageMemory(device,result.image,result.memory,0));
|
||||
VkImageViewCreateInfo view={VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};view.image=result.image;view.viewType=VK_IMAGE_VIEW_TYPE_2D;view.format=format;
|
||||
view.subresourceRange={result.depth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1};
|
||||
check(vkCreateImageView(device,&view,nullptr,&result.view));return result;
|
||||
};
|
||||
auto destroyImage=[&](Image& i){vkDestroyImageView(device,i.view,nullptr);vkDestroyImage(device,i.image,nullptr);vkFreeMemory(device,i.memory,nullptr);};
|
||||
VkBufferCreateInfo bi={VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};bi.size=BYTES;bi.usage=VK_BUFFER_USAGE_STORAGE_BUFFER_BIT|VK_BUFFER_USAGE_TRANSFER_SRC_BIT|VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
VkBuffer buffers[2];VkDeviceMemory allocations[2];float* mapped[2];
|
||||
for(int i=0;i<2;++i){check(vkCreateBuffer(device,&bi,nullptr,&buffers[i]));VkMemoryRequirements req;vkGetBufferMemoryRequirements(device,buffers[i],&req);allocations[i]=allocate(req,VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT|VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);check(vkBindBufferMemory(device,buffers[i],allocations[i],0));check(vkMapMemory(device,allocations[i],0,VK_WHOLE_SIZE,0,reinterpret_cast<void**>(&mapped[i])));}
|
||||
D4rVkStaging staging;assert(staging.init(device,vkGetDeviceProcAddr));
|
||||
VkCommandPoolCreateInfo pi={VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};pi.queueFamilyIndex=family;VkCommandPool pool;check(vkCreateCommandPool(device,&pi,nullptr,&pool));
|
||||
VkCommandBufferAllocateInfo ca={VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};ca.commandPool=pool;ca.level=VK_COMMAND_BUFFER_LEVEL_PRIMARY;ca.commandBufferCount=1;VkCommandBuffer cmd;check(vkAllocateCommandBuffers(device,&ca,&cmd));
|
||||
auto begin=[&](){check(vkResetCommandPool(device,pool,0));VkCommandBufferBeginInfo b={VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};b.flags=VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;check(vkBeginCommandBuffer(cmd,&b));};
|
||||
auto submit=[&](){VkMemoryBarrier host={VK_STRUCTURE_TYPE_MEMORY_BARRIER};host.srcAccessMask=VK_ACCESS_SHADER_WRITE_BIT|VK_ACCESS_TRANSFER_WRITE_BIT;host.dstAccessMask=VK_ACCESS_HOST_READ_BIT;vkCmdPipelineBarrier(cmd,VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT|VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_HOST_BIT,0,1,&host,0,nullptr,0,nullptr);check(vkEndCommandBuffer(cmd));VkSubmitInfo s={VK_STRUCTURE_TYPE_SUBMIT_INFO};s.commandBufferCount=1;s.pCommandBuffers=&cmd;check(vkQueueSubmit(queue,1,&s,VK_NULL_HANDLE));check(vkQueueWaitIdle(queue));};
|
||||
auto barrier=[&](const Image& i,VkImageLayout from,VkImageLayout to,VkAccessFlags src,VkAccessFlags dst){
|
||||
VkImageMemoryBarrier b={VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};b.image=i.image;b.oldLayout=from;b.newLayout=to;b.srcAccessMask=src;b.dstAccessMask=dst;b.srcQueueFamilyIndex=b.dstQueueFamilyIndex=VK_QUEUE_FAMILY_IGNORED;
|
||||
b.subresourceRange={static_cast<VkImageAspectFlags>(i.depth ? VK_IMAGE_ASPECT_DEPTH_BIT|VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_COLOR_BIT),0,1,0,1};vkCmdPipelineBarrier(cmd,VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,0,0,nullptr,0,nullptr,1,&b);
|
||||
};
|
||||
auto source=image(VK_FORMAT_R16G16B16A16_SFLOAT,false);
|
||||
auto reference=image(VK_FORMAT_B10G11R11_UFLOAT_PACK32,true);
|
||||
auto candidate=image(VK_FORMAT_B10G11R11_UFLOAT_PACK32,true);
|
||||
D4rVkStagingSet write;assert(staging.set(write,candidate.view,candidate.format,buffers[0],W*H*8,true));
|
||||
auto* halves=reinterpret_cast<uint16_t*>(mapped[0]);
|
||||
for(unsigned i=0;i<W*H;++i){halves[i*4]=i;halves[i*4+1]=i*73+31;halves[i*4+2]=i*193+57;halves[i*4+3]=0x3c00;}
|
||||
std::memset(mapped[1],0xa5,BYTES);
|
||||
begin();barrier(source,VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,0,VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
VkBufferImageCopy region={};region.imageSubresource={VK_IMAGE_ASPECT_COLOR_BIT,0,0,1};region.imageExtent={W,H,1};
|
||||
vkCmdCopyBufferToImage(cmd,buffers[0],source.image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,®ion);
|
||||
barrier(source,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,VK_ACCESS_TRANSFER_WRITE_BIT,VK_ACCESS_TRANSFER_READ_BIT);
|
||||
barrier(reference,VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,0,VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
VkImageBlit blit={};blit.srcSubresource=blit.dstSubresource=region.imageSubresource;
|
||||
blit.srcOffsets[1]=blit.dstOffsets[1]={(int)W,(int)H,1};
|
||||
vkCmdBlitImage(cmd,source.image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,reference.image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,&blit,VK_FILTER_NEAREST);
|
||||
barrier(reference,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,VK_ACCESS_TRANSFER_WRITE_BIT,VK_ACCESS_TRANSFER_READ_BIT);
|
||||
vkCmdCopyImageToBuffer(cmd,reference.image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,buffers[1],1,®ion);
|
||||
barrier(candidate,VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_GENERAL,0,VK_ACCESS_SHADER_WRITE_BIT);
|
||||
staging.record(cmd,write,W,H);
|
||||
barrier(candidate,VK_IMAGE_LAYOUT_GENERAL,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,VK_ACCESS_SHADER_WRITE_BIT,VK_ACCESS_TRANSFER_READ_BIT);
|
||||
region.bufferOffset=W*H*4;
|
||||
vkCmdCopyImageToBuffer(cmd,candidate.image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,buffers[1],1,®ion);submit();
|
||||
auto* words=reinterpret_cast<uint32_t*>(mapped[1]);unsigned mismatches=0,finite=0,nan=0;
|
||||
for(unsigned i=0;i<W*H;++i)for(unsigned c=0;c<3;++c) {
|
||||
const auto h=halves[i*4+c];const unsigned shift=c*11,bits=c==2 ? 10 : 11,mask=(1u<<bits)-1;
|
||||
unsigned old=(words[i]>>shift)&mask,now=(words[W*H+i]>>shift)&mask;
|
||||
if((h&0x7c00)==0x7c00 && (h&0x3ff)) {
|
||||
++nan;const unsigned m=c==2 ? 5 : 6;
|
||||
assert((old>>m)==31 && (now>>m)==31 && (old&((1u<<m)-1)) && (now&((1u<<m)-1)));
|
||||
} else {++finite;if(old!=now){if(mismatches<8)std::printf("DIFF pixel=%u channel=%u half=%04x blit=%x compute=%x\n",i,c,h,old,now);++mismatches;}}
|
||||
}
|
||||
std::printf("R11 output blit vs compute: finite_or_inf_channels=%u mismatches=%u nan_channels=%u\n",finite,mismatches,nan);
|
||||
assert(mismatches==0);
|
||||
staging.free(write);destroyImage(source);destroyImage(reference);destroyImage(candidate);
|
||||
// Input conversion: every unsigned R11/R10 channel encoding, padded rows.
|
||||
auto input=image(VK_FORMAT_B10G11R11_UFLOAT_PACK32,false);
|
||||
auto input_reference=image(VK_FORMAT_R16G16B16A16_SFLOAT,true);
|
||||
D4rVkStagingSet read;assert(staging.set(read,input.view,input.format,buffers[0],BYTES,false));
|
||||
auto* raw=reinterpret_cast<uint32_t*>(mapped[0]);
|
||||
for(unsigned i=0;i<BYTES/4;++i)raw[i]=0xa5a5a5a5;
|
||||
for(unsigned i=0;i<W*H;++i)raw[i]=(i&2047u)|((i*73u+31u)&2047u)<<11|((i*193u+57u)&1023u)<<22;
|
||||
std::vector<uint32_t> initial(raw,raw+BYTES/4);
|
||||
begin();barrier(input,VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,0,VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
region.bufferOffset=0;
|
||||
vkCmdCopyBufferToImage(cmd,buffers[0],input.image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,®ion);
|
||||
barrier(input,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,VK_ACCESS_TRANSFER_WRITE_BIT,VK_ACCESS_TRANSFER_READ_BIT);
|
||||
barrier(input_reference,VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,0,VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
vkCmdBlitImage(cmd,input.image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,input_reference.image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,&blit,VK_FILTER_NEAREST);
|
||||
barrier(input_reference,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,VK_ACCESS_TRANSFER_WRITE_BIT,VK_ACCESS_TRANSFER_READ_BIT);
|
||||
vkCmdCopyImageToBuffer(cmd,input_reference.image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,buffers[1],1,®ion);
|
||||
barrier(input,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,VK_ACCESS_TRANSFER_READ_BIT,VK_ACCESS_SHADER_READ_BIT);
|
||||
VkMemoryBarrier reuse={VK_STRUCTURE_TYPE_MEMORY_BARRIER};reuse.srcAccessMask=VK_ACCESS_TRANSFER_READ_BIT;reuse.dstAccessMask=VK_ACCESS_SHADER_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(cmd,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,0,1,&reuse,0,nullptr,0,nullptr);
|
||||
constexpr unsigned stride=W+7;
|
||||
staging.record(cmd,read,W,H,1,stride);submit();
|
||||
const auto* actual=reinterpret_cast<const uint16_t*>(mapped[0]);
|
||||
const auto* expected=reinterpret_cast<const uint16_t*>(mapped[1]);
|
||||
unsigned input_bad=0,input_nan=0;
|
||||
for(unsigned y=0;y<H;++y) {
|
||||
for(unsigned x=0;x<W;++x)for(unsigned c=0;c<4;++c) {
|
||||
const auto got=actual[(size_t(y)*stride+x)*4+c],want=expected[(size_t(y)*W+x)*4+c];
|
||||
if((want&0x7c00)==0x7c00 && (want&0x3ff)) {
|
||||
++input_nan;assert((got&0x7c00)==0x7c00 && (got&0x3ff));
|
||||
} else if(got!=want){if(input_bad<8)std::printf("INPUT DIFF x=%u y=%u channel=%u blit=%04x compute=%04x\n",x,y,c,want,got);++input_bad;}
|
||||
}
|
||||
for(unsigned word=(y*stride+W)*2;word<(y+1)*stride*2;++word)assert(raw[word]==initial[word]);
|
||||
}
|
||||
for(unsigned word=H*stride*2;word<BYTES/4;++word)assert(raw[word]==initial[word]);
|
||||
std::printf("R11 input blit vs compute: mismatches=%u nan_channels=%u padded_rows_unchanged=1\n",input_bad,input_nan);
|
||||
assert(input_bad==0);
|
||||
staging.free(read);destroyImage(input);destroyImage(input_reference);
|
||||
staging.destroy();vkDestroyCommandPool(device,pool,nullptr);
|
||||
for(int i=0;i<2;++i){vkUnmapMemory(device,allocations[i]);vkDestroyBuffer(device,buffers[i],nullptr);vkFreeMemory(device,allocations[i],nullptr);}
|
||||
vkDestroyDevice(device,nullptr);vkDestroyInstance(instance,nullptr);std::puts("PASS: exhaustive half inputs, all RGB channels, partial workgroups");
|
||||
}
|
||||
+346
-3
@@ -14,6 +14,8 @@
|
||||
#include <time.h>
|
||||
#include "d4r_native_selection.h"
|
||||
#include "d4r_motion_dilation.h"
|
||||
#include "d4r_native_motion.h"
|
||||
#include "d4r_wire_history.h"
|
||||
#ifdef D4R_MOTION_KERNELS
|
||||
#include "d4r_motion_kernels.h"
|
||||
#endif
|
||||
@@ -983,9 +985,11 @@ static void* find_zluda_symbol(const char* name)
|
||||
return cuda_library != NULL ? dlsym(cuda_library, name) : NULL;
|
||||
}
|
||||
|
||||
/* d4r-owned, opt-in preprocessing. Runs on HIP's legacy/default stream, shared
|
||||
with CUDA's null stream; the shim records and waits for completion before NGX. */
|
||||
CUresult WINAPI d4rDilateMotionVectors(const D4rMotionDilationParams* parameters)
|
||||
static CUstream d4r_ngx_stream(CUstream stream);
|
||||
|
||||
/* Preprocessing, input waits and NGX null-stream launches all use d4r_ngx_stream.
|
||||
allocation_height == 0 retains the original ABI's active-height contract. */
|
||||
static CUresult dilate_motion_vectors(const D4rMotionDilationParams* parameters, uint32_t allocation_height, int thin_sky)
|
||||
{
|
||||
if (parameters == NULL || parameters->depth == 0 || parameters->motion == 0 || parameters->output == 0 ||
|
||||
parameters->radius < 1 || parameters->radius > 2 || parameters->render_width == 0 ||
|
||||
@@ -1002,6 +1006,8 @@ CUresult WINAPI d4rDilateMotionVectors(const D4rMotionDilationParams* parameters
|
||||
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static void* module;
|
||||
static void* function;
|
||||
static void* lds_function;
|
||||
static void* thin_function;
|
||||
static HIP_LAUNCH launch;
|
||||
static int initialized, result = CUDA_ERROR_NOT_SUPPORTED;
|
||||
pthread_mutex_lock(&lock);
|
||||
@@ -1018,7 +1024,11 @@ CUresult WINAPI d4rDilateMotionVectors(const D4rMotionDilationParams* parameters
|
||||
launch = hip != NULL ? (HIP_LAUNCH)dlsym(hip, "hipModuleLaunchKernel") : NULL;
|
||||
if (image != NULL && load != NULL && get != NULL && launch != NULL &&
|
||||
load(&module, image) == 0 && get(&function, module, "d4r_motion_dilate") == 0)
|
||||
{
|
||||
result = CUDA_SUCCESS;
|
||||
get(&lds_function, module, "d4r_motion_dilate_lds");
|
||||
get(&thin_function, module, "d4r_motion_dilate_lds_thin");
|
||||
}
|
||||
tracef("motion-vector dilation: %s (%s)", result == 0 ? "GPU kernel ready" : "unavailable", architecture);
|
||||
}
|
||||
const int status = result;
|
||||
@@ -1026,6 +1036,19 @@ CUresult WINAPI d4rDilateMotionVectors(const D4rMotionDilationParams* parameters
|
||||
if (status != CUDA_SUCCESS)
|
||||
return status;
|
||||
D4rMotionDilationParams copy = *parameters;
|
||||
const int lds = lds_function != NULL && d4r_motion_dilation_lds_eligible(©);
|
||||
if (allocation_height != 0 && (!lds || allocation_height < copy.motion_height ||
|
||||
copy.motion_pitch % 4 != 0 || copy.motion_pitch / 4 > 0x7fffffffu || allocation_height > 0x7fffffffu))
|
||||
return CUDA_ERROR_INVALID_VALUE;
|
||||
if (thin_sky && (!lds || thin_function == NULL))
|
||||
return CUDA_ERROR_NOT_SUPPORTED;
|
||||
if (lds)
|
||||
{
|
||||
uint32_t height = allocation_height != 0 ? allocation_height : copy.motion_height;
|
||||
void* arguments[] = {©, &height};
|
||||
return launch(thin_sky ? thin_function : lds_function, (unsigned)((copy.motion_pitch / 4 + 15) / 16), (height + 15) / 16,
|
||||
1, 256, 1, 1, 0, d4r_ngx_stream(NULL), arguments, NULL);
|
||||
}
|
||||
void* arguments[] = {©};
|
||||
const uint64_t pixels = (uint64_t)copy.motion_width * copy.motion_height;
|
||||
return launch(function, (unsigned)((pixels + 127) / 128), 1, 1, 128, 1, 1, 0, d4r_ngx_stream(NULL), arguments, NULL);
|
||||
@@ -1034,6 +1057,326 @@ CUresult WINAPI d4rDilateMotionVectors(const D4rMotionDilationParams* parameters
|
||||
#endif
|
||||
}
|
||||
|
||||
CUresult WINAPI d4rDilateMotionVectors(const D4rMotionDilationParams* parameters)
|
||||
{
|
||||
return dilate_motion_vectors(parameters, 0, 0);
|
||||
}
|
||||
|
||||
/* Additive export: retain the 72-byte parameter ABI and preserve the full padded
|
||||
allocation in the LDS launch, without a separate full-plane copy. */
|
||||
CUresult WINAPI d4rDilateMotionVectorsPadded(const D4rMotionDilationParams* parameters, uint32_t allocation_height)
|
||||
{
|
||||
if (allocation_height == 0)
|
||||
return CUDA_ERROR_INVALID_VALUE;
|
||||
return dilate_motion_vectors(parameters, allocation_height, 0);
|
||||
}
|
||||
|
||||
CUresult WINAPI d4rDilateMotionVectorsThin(const D4rMotionDilationParams* parameters, uint32_t allocation_height)
|
||||
{
|
||||
if (allocation_height == 0)
|
||||
return CUDA_ERROR_INVALID_VALUE;
|
||||
return dilate_motion_vectors(parameters, allocation_height, 1);
|
||||
}
|
||||
|
||||
/* Additive native-MV operation. The recorded input marker must precede this
|
||||
* preprocessing launch, and NGX follows it on the same effective stream. */
|
||||
static CUresult d4r_compose_native_motion(const D4rNativeMotionParams* parameters, unsigned int thin,float jitter_x,float jitter_y)
|
||||
{
|
||||
if(!d4r_native_motion_parameters_valid(parameters)) return CUDA_ERROR_INVALID_VALUE;
|
||||
#ifdef D4R_MOTION_KERNELS
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_LOAD)(void**,const void*);
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_FUNCTION)(void**,void*,const char*);
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_LAUNCH)(void*,unsigned,unsigned,unsigned,
|
||||
unsigned,unsigned,unsigned,unsigned,void*,void**,void**);
|
||||
static pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
|
||||
static void *module,*function[3];
|
||||
static HIP_LAUNCH launch;
|
||||
static int initialized,result=CUDA_ERROR_NOT_SUPPORTED;
|
||||
int status;
|
||||
pthread_mutex_lock(&lock);
|
||||
if(!initialized)
|
||||
{
|
||||
void* hip;
|
||||
const void* image;
|
||||
HIP_LOAD load;
|
||||
HIP_FUNCTION get;
|
||||
char architecture[32];
|
||||
initialized=1;pthread_once(&load_once,load_zluda);
|
||||
hip=dlopen("libamdhip64.so.7",RTLD_NOW|RTLD_LOCAL);
|
||||
gpu_architecture(architecture,sizeof(architecture));image=d4r_motion_image(architecture);
|
||||
load=hip ? (HIP_LOAD)dlsym(hip,"hipModuleLoadData") : NULL;
|
||||
get=hip ? (HIP_FUNCTION)dlsym(hip,"hipModuleGetFunction") : NULL;
|
||||
launch=hip ? (HIP_LAUNCH)dlsym(hip,"hipModuleLaunchKernel") : NULL;
|
||||
if(image && load && get && launch && load(&module,image)==0 &&
|
||||
get(&function[0],module,"d4r_native_motion_compose")==0 &&
|
||||
get(&function[1],module,"d4r_native_motion_compose_thin")==0 &&
|
||||
get(&function[2],module,"d4r_native_motion_compose_aligned")==0) result=CUDA_SUCCESS;
|
||||
tracef("native motion composition: %s (%s)",result==0 ? "GPU kernel ready" : "unavailable",architecture);
|
||||
}
|
||||
status=result;pthread_mutex_unlock(&lock);
|
||||
if(status==CUDA_SUCCESS)
|
||||
{
|
||||
D4rNativeMotionThinParams copy={*parameters,jitter_x,jitter_y};
|
||||
void* arguments[]={©};
|
||||
return launch(function[thin],(copy.base.width+15)/16,(copy.base.height+15)/16,1,256,1,1,0,
|
||||
d4r_ngx_stream(NULL),arguments,NULL);
|
||||
}
|
||||
return status;
|
||||
#else
|
||||
return CUDA_ERROR_NOT_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
CUresult WINAPI d4rComposeNativeMotion(const D4rNativeMotionParams* parameters)
|
||||
{ return d4r_compose_native_motion(parameters,0,0,0); }
|
||||
CUresult WINAPI d4rComposeNativeMotionThin(const D4rNativeMotionThinParams* parameters)
|
||||
{
|
||||
if(!parameters || !(parameters->jitter_x>=-1 && parameters->jitter_x<=1 && parameters->jitter_y>=-1 && parameters->jitter_y<=1)) return CUDA_ERROR_INVALID_VALUE;
|
||||
return d4r_compose_native_motion(¶meters->base,1,parameters->jitter_x,parameters->jitter_y);
|
||||
}
|
||||
|
||||
CUresult WINAPI d4rComposeNativeMotionAligned(const D4rNativeMotionThinParams* parameters)
|
||||
{
|
||||
if(!parameters || !(parameters->jitter_x>=-1 && parameters->jitter_x<=1 && parameters->jitter_y>=-1 && parameters->jitter_y<=1)) return CUDA_ERROR_INVALID_VALUE;
|
||||
return d4r_compose_native_motion(¶meters->base,2,parameters->jitter_x,parameters->jitter_y);
|
||||
}
|
||||
|
||||
CUresult WINAPI d4rStabilizeThinWires(const D4rWireHistoryParams* parameters)
|
||||
{
|
||||
if(!d4r_wire_history_parameters_valid(parameters)) return CUDA_ERROR_INVALID_VALUE;
|
||||
#ifdef D4R_MOTION_KERNELS
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_LOAD)(void**,const void*);
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_FUNCTION)(void**,void*,const char*);
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_LAUNCH)(void*,unsigned,unsigned,unsigned,
|
||||
unsigned,unsigned,unsigned,unsigned,void*,void**,void**);
|
||||
static pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
|
||||
static void *module,*function;
|
||||
static HIP_LAUNCH launch;
|
||||
static int initialized,result=CUDA_ERROR_NOT_SUPPORTED;
|
||||
int status;
|
||||
pthread_mutex_lock(&lock);
|
||||
if(!initialized)
|
||||
{
|
||||
void* hip;
|
||||
const void* image;
|
||||
HIP_LOAD load;
|
||||
HIP_FUNCTION get;
|
||||
char architecture[32];
|
||||
initialized=1;pthread_once(&load_once,load_zluda);
|
||||
hip=dlopen("libamdhip64.so.7",RTLD_NOW|RTLD_LOCAL);
|
||||
gpu_architecture(architecture,sizeof(architecture));image=d4r_motion_image(architecture);
|
||||
load=hip ? (HIP_LOAD)dlsym(hip,"hipModuleLoadData") : NULL;
|
||||
get=hip ? (HIP_FUNCTION)dlsym(hip,"hipModuleGetFunction") : NULL;
|
||||
launch=hip ? (HIP_LAUNCH)dlsym(hip,"hipModuleLaunchKernel") : NULL;
|
||||
if(image && load && get && launch && load(&module,image)==0 &&
|
||||
get(&function,module,"d4r_wire_history")==0) result=CUDA_SUCCESS;
|
||||
tracef("thin wire history: %s (%s)",result==0 ? "GPU kernel ready" : "unavailable",architecture);
|
||||
}
|
||||
status=result;pthread_mutex_unlock(&lock);
|
||||
if(status==CUDA_SUCCESS)
|
||||
{
|
||||
D4rWireHistoryParams copy=*parameters;
|
||||
void* arguments[]={©};
|
||||
return launch(function,(copy.width+15)/16,(copy.height+15)/16,1,256,1,1,0,
|
||||
d4r_ngx_stream(NULL),arguments,NULL);
|
||||
}
|
||||
return status;
|
||||
#else
|
||||
return CUDA_ERROR_NOT_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
CUresult WINAPI d4rComposeNativeMotionHybrid(const D4rNativeMotionHybridParams* parameters)
|
||||
{
|
||||
if(!d4r_native_motion_hybrid_parameters_valid(parameters)) return CUDA_ERROR_INVALID_VALUE;
|
||||
#ifdef D4R_MOTION_KERNELS
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_LOAD)(void**,const void*);
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_FUNCTION)(void**,void*,const char*);
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_LAUNCH)(void*,unsigned,unsigned,unsigned,
|
||||
unsigned,unsigned,unsigned,unsigned,void*,void**,void**);
|
||||
static pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
|
||||
static void *module,*function;
|
||||
static HIP_LAUNCH launch;
|
||||
static int initialized,result=CUDA_ERROR_NOT_SUPPORTED;
|
||||
int status;
|
||||
pthread_mutex_lock(&lock);
|
||||
if(!initialized)
|
||||
{
|
||||
void* hip;
|
||||
const void* image;
|
||||
HIP_LOAD load;
|
||||
HIP_FUNCTION get;
|
||||
char architecture[32];
|
||||
initialized=1;pthread_once(&load_once,load_zluda);
|
||||
hip=dlopen("libamdhip64.so.7",RTLD_NOW|RTLD_LOCAL);
|
||||
gpu_architecture(architecture,sizeof(architecture));image=d4r_motion_image(architecture);
|
||||
load=hip ? (HIP_LOAD)dlsym(hip,"hipModuleLoadData") : NULL;
|
||||
get=hip ? (HIP_FUNCTION)dlsym(hip,"hipModuleGetFunction") : NULL;
|
||||
launch=hip ? (HIP_LAUNCH)dlsym(hip,"hipModuleLaunchKernel") : NULL;
|
||||
if(image && load && get && launch && load(&module,image)==0 &&
|
||||
get(&function,module,"d4r_native_motion_compose_hybrid")==0) result=CUDA_SUCCESS;
|
||||
tracef("hybrid native motion: %s (%s)",result==0 ? "GPU kernel ready" : "unavailable",architecture);
|
||||
}
|
||||
status=result;pthread_mutex_unlock(&lock);
|
||||
if(status==CUDA_SUCCESS)
|
||||
{
|
||||
D4rNativeMotionHybridParams copy=*parameters;
|
||||
void* arguments[]={©};
|
||||
return launch(function,(copy.native.base.width+15)/16,(copy.native.base.height+15)/16,1,256,1,1,0,
|
||||
d4r_ngx_stream(NULL),arguments,NULL);
|
||||
}
|
||||
return status;
|
||||
#else
|
||||
return CUDA_ERROR_NOT_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
CUresult WINAPI d4rStabilizeThinWiresSparse(const D4rWireHistorySparseParams* parameters)
|
||||
{
|
||||
if(!d4r_wire_history_sparse_parameters_valid(parameters)) return CUDA_ERROR_INVALID_VALUE;
|
||||
#ifdef D4R_MOTION_KERNELS
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_LOAD)(void**,const void*);
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_FUNCTION)(void**,void*,const char*);
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_LAUNCH)(void*,unsigned,unsigned,unsigned,
|
||||
unsigned,unsigned,unsigned,unsigned,void*,void**,void**);
|
||||
static pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
|
||||
static void *module,*function,*apply;
|
||||
static HIP_LAUNCH launch;
|
||||
static int initialized,result=CUDA_ERROR_NOT_SUPPORTED;
|
||||
int status;
|
||||
pthread_mutex_lock(&lock);
|
||||
if(!initialized)
|
||||
{
|
||||
void* hip;
|
||||
const void* image;
|
||||
HIP_LOAD load;
|
||||
HIP_FUNCTION get;
|
||||
char architecture[32];
|
||||
initialized=1;pthread_once(&load_once,load_zluda);
|
||||
hip=dlopen("libamdhip64.so.7",RTLD_NOW|RTLD_LOCAL);
|
||||
gpu_architecture(architecture,sizeof(architecture));image=d4r_motion_image(architecture);
|
||||
load=hip ? (HIP_LOAD)dlsym(hip,"hipModuleLoadData") : NULL;
|
||||
get=hip ? (HIP_FUNCTION)dlsym(hip,"hipModuleGetFunction") : NULL;
|
||||
launch=hip ? (HIP_LAUNCH)dlsym(hip,"hipModuleLaunchKernel") : NULL;
|
||||
if(image && load && get && launch && load(&module,image)==0 &&
|
||||
get(&function,module,"d4r_wire_history_sparse")==0 &&
|
||||
get(&apply,module,"d4r_wire_history_apply")==0) result=CUDA_SUCCESS;
|
||||
tracef("sparse thin wire history: %s (%s)",result==0 ? "GPU kernel ready" : "unavailable",architecture);
|
||||
}
|
||||
status=result;pthread_mutex_unlock(&lock);
|
||||
if(status==CUDA_SUCCESS)
|
||||
{
|
||||
D4rWireHistorySparseParams copy=*parameters;
|
||||
void* arguments[]={©};
|
||||
status=launch(function,(copy.base.width+15)/16,(copy.base.height+15)/16,1,256,1,1,0,
|
||||
d4r_ngx_stream(NULL),arguments,NULL);
|
||||
if(status!=0) return status;
|
||||
return launch(apply,(copy.base.width+15)/16,(copy.base.height+15)/16,1,256,1,1,0,
|
||||
d4r_ngx_stream(NULL),arguments,NULL);
|
||||
}
|
||||
return status;
|
||||
#else
|
||||
return CUDA_ERROR_NOT_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
CUresult WINAPI d4rComposeNativeMotionPrepared(const D4rNativeMotionPreparedParams* parameters)
|
||||
{
|
||||
if(!d4r_native_motion_prepared_parameters_valid(parameters)) return CUDA_ERROR_INVALID_VALUE;
|
||||
#ifdef D4R_MOTION_KERNELS
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_LOAD)(void**,const void*);
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_FUNCTION)(void**,void*,const char*);
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_LAUNCH)(void*,unsigned,unsigned,unsigned,
|
||||
unsigned,unsigned,unsigned,unsigned,void*,void**,void**);
|
||||
static pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
|
||||
static void *module,*function,*apply;
|
||||
static HIP_LAUNCH launch;
|
||||
static int initialized,result=CUDA_ERROR_NOT_SUPPORTED;
|
||||
int status;
|
||||
pthread_mutex_lock(&lock);
|
||||
if(!initialized)
|
||||
{
|
||||
void* hip;
|
||||
const void* image;
|
||||
HIP_LOAD load;
|
||||
HIP_FUNCTION get;
|
||||
char architecture[32];
|
||||
initialized=1;pthread_once(&load_once,load_zluda);
|
||||
hip=dlopen("libamdhip64.so.7",RTLD_NOW|RTLD_LOCAL);
|
||||
gpu_architecture(architecture,sizeof(architecture));image=d4r_motion_image(architecture);
|
||||
load=hip ? (HIP_LOAD)dlsym(hip,"hipModuleLoadData") : NULL;
|
||||
get=hip ? (HIP_FUNCTION)dlsym(hip,"hipModuleGetFunction") : NULL;
|
||||
launch=hip ? (HIP_LAUNCH)dlsym(hip,"hipModuleLaunchKernel") : NULL;
|
||||
if(image && load && get && launch && load(&module,image)==0 &&
|
||||
get(&function,module,"d4r_native_motion_prepare")==0 &&
|
||||
get(&apply,module,"d4r_native_motion_prepared")==0) result=CUDA_SUCCESS;
|
||||
tracef("prepared native motion: %s (%s)",result==0 ? "GPU kernel ready" : "unavailable",architecture);
|
||||
}
|
||||
status=result;pthread_mutex_unlock(&lock);
|
||||
if(status==CUDA_SUCCESS)
|
||||
{
|
||||
D4rNativeMotionPreparedParams copy=*parameters;
|
||||
void* arguments[]={©};
|
||||
status=launch(function,(copy.native.base.width+31)/32,(copy.native.base.height+31)/32,1,256,1,1,0,
|
||||
d4r_ngx_stream(NULL),arguments,NULL);
|
||||
if(status!=0) return status;
|
||||
return launch(apply,(copy.native.base.width+15)/16,(copy.native.base.height+15)/16,1,256,1,1,0,
|
||||
d4r_ngx_stream(NULL),arguments,NULL);
|
||||
}
|
||||
return status;
|
||||
#else
|
||||
return CUDA_ERROR_NOT_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
CUresult WINAPI d4rStabilizeThinWiresPrepared(const D4rWireHistoryPreparedParams* parameters)
|
||||
{
|
||||
if(!d4r_wire_history_prepared_parameters_valid(parameters)) return CUDA_ERROR_INVALID_VALUE;
|
||||
#ifdef D4R_MOTION_KERNELS
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_LOAD)(void**,const void*);
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_FUNCTION)(void**,void*,const char*);
|
||||
typedef int(__attribute__((sysv_abi)) * HIP_LAUNCH)(void*,unsigned,unsigned,unsigned,
|
||||
unsigned,unsigned,unsigned,unsigned,void*,void**,void**);
|
||||
static pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
|
||||
static void *module,*function,*apply;
|
||||
static HIP_LAUNCH launch;
|
||||
static int initialized,result=CUDA_ERROR_NOT_SUPPORTED;
|
||||
int status;
|
||||
pthread_mutex_lock(&lock);
|
||||
if(!initialized)
|
||||
{
|
||||
void* hip;
|
||||
const void* image;
|
||||
HIP_LOAD load;
|
||||
HIP_FUNCTION get;
|
||||
char architecture[32];
|
||||
initialized=1;pthread_once(&load_once,load_zluda);
|
||||
hip=dlopen("libamdhip64.so.7",RTLD_NOW|RTLD_LOCAL);
|
||||
gpu_architecture(architecture,sizeof(architecture));image=d4r_motion_image(architecture);
|
||||
load=hip ? (HIP_LOAD)dlsym(hip,"hipModuleLoadData") : NULL;
|
||||
get=hip ? (HIP_FUNCTION)dlsym(hip,"hipModuleGetFunction") : NULL;
|
||||
launch=hip ? (HIP_LAUNCH)dlsym(hip,"hipModuleLaunchKernel") : NULL;
|
||||
if(image && load && get && launch && load(&module,image)==0 &&
|
||||
get(&function,module,"d4r_wire_history_preclassified")==0 &&
|
||||
get(&apply,module,"d4r_wire_history_apply")==0) result=CUDA_SUCCESS;
|
||||
tracef("prepared thin wire history: %s (%s)",result==0 ? "GPU kernel ready" : "unavailable",architecture);
|
||||
}
|
||||
status=result;pthread_mutex_unlock(&lock);
|
||||
if(status==CUDA_SUCCESS)
|
||||
{
|
||||
D4rWireHistoryPreparedParams copy=*parameters;
|
||||
void* arguments[]={©};
|
||||
status=launch(function,(copy.sparse.base.width+15)/16,(copy.sparse.base.height+15)/16,1,256,1,1,0,
|
||||
d4r_ngx_stream(NULL),arguments,NULL);
|
||||
if(status!=0) return status;
|
||||
return launch(apply,(copy.sparse.base.width+15)/16,(copy.sparse.base.height+15)/16,1,256,1,1,0,
|
||||
d4r_ngx_stream(NULL),arguments,NULL);
|
||||
}
|
||||
return status;
|
||||
#else
|
||||
return CUDA_ERROR_NOT_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void tracef(const char* format, ...)
|
||||
{
|
||||
char line[2048];
|
||||
|
||||
@@ -125,3 +125,19 @@
|
||||
@ stdcall d4rNgxTimelineDestroy(ptr) d4rNgxTimelineDestroy
|
||||
@ stdcall d4rNgxTimelineWait(ptr) d4rNgxTimelineWait
|
||||
@ stdcall d4rDilateMotionVectors(ptr) d4rDilateMotionVectors
|
||||
@ stdcall d4rDilateMotionVectorsPadded(ptr long) d4rDilateMotionVectorsPadded
|
||||
|
||||
@ stdcall d4rDilateMotionVectorsThin(ptr long) d4rDilateMotionVectorsThin
|
||||
|
||||
@ stdcall d4rComposeNativeMotion(ptr) d4rComposeNativeMotion
|
||||
@ stdcall d4rComposeNativeMotionThin(ptr) d4rComposeNativeMotionThin
|
||||
@ stdcall d4rStabilizeThinWires(ptr) d4rStabilizeThinWires
|
||||
|
||||
@ stdcall d4rStabilizeThinWiresSparse(ptr) d4rStabilizeThinWiresSparse
|
||||
|
||||
@ stdcall d4rComposeNativeMotionAligned(ptr) d4rComposeNativeMotionAligned
|
||||
|
||||
@ stdcall d4rComposeNativeMotionHybrid(ptr) d4rComposeNativeMotionHybrid
|
||||
|
||||
@ stdcall d4rComposeNativeMotionPrepared(ptr) d4rComposeNativeMotionPrepared
|
||||
@ stdcall d4rStabilizeThinWiresPrepared(ptr) d4rStabilizeThinWiresPrepared
|
||||
Reference in new issue
Block a user