d4r: official NVIDIA DLSS on AMD GPUs under Linux and Proton

This commit is contained in:
ayo committed 2026-09-27 15:26:56 -04:00
commit 118173ecd7
128 files changed
+31333

No files matched your search

+222
View File
@@ -0,0 +1,222 @@
// Measures host<->array and host<->linear copy bandwidth through the CUDA
// driver API (ZLUDA), for the staging sizes the NGX shim moves per frame.
// usage: bench_cuda_copies [WIDTH HEIGHT CHANNELS] (half-float texels)
#include <dlfcn.h>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using CUresult = int;
using CUdeviceptr = uint64_t;
struct Memcpy2D
{
size_t srcXInBytes, srcY;
uint32_t srcMemoryType, srcAlignment;
const void* srcHost;
CUdeviceptr srcDevice;
void* srcArray;
size_t srcPitch;
size_t dstXInBytes, dstY;
uint32_t dstMemoryType, dstAlignment;
void* dstHost;
CUdeviceptr dstDevice;
void* dstArray;
size_t dstPitch;
size_t WidthInBytes, Height;
};
struct ArrayDescriptor
{
size_t Width, Height;
int32_t Format;
uint32_t NumChannels;
};
template <typename Function> static Function load(void* library, const char* name)
{
void* raw = dlsym(library, name);
if (raw == nullptr)
{
std::fprintf(stderr, "missing %s\n", name);
std::exit(1);
}
Function function{};
std::memcpy(&function, &raw, sizeof(function));
return function;
}
int main(int argc, char** argv)
{
const size_t width = argc > 3 ? std::strtoul(argv[1], nullptr, 0) : 2560;
const size_t height = argc > 3 ? std::strtoul(argv[2], nullptr, 0) : 1440;
const unsigned channels = argc > 3 ? static_cast<unsigned>(std::strtoul(argv[3], nullptr, 0)) : 4;
const size_t rowBytes = width * 2 * channels, bytes = rowBytes * height;
void* library = dlopen("libcuda.so", RTLD_NOW | RTLD_LOCAL);
if (library == nullptr)
return 1;
auto init = load<CUresult (*)(unsigned)>(library, "cuInit");
auto device_get = load<CUresult (*)(int*, int)>(library, "cuDeviceGet");
auto context_create = load<CUresult (*)(void**, unsigned, int)>(library, "cuCtxCreate_v2");
auto alloc_host = load<CUresult (*)(void**, size_t, unsigned)>(library, "cuMemHostAlloc");
auto alloc = load<CUresult (*)(CUdeviceptr*, size_t)>(library, "cuMemAlloc_v2");
auto array_create = load<CUresult (*)(void**, const ArrayDescriptor*)>(library, "cuArrayCreate_v2");
auto copy = load<CUresult (*)(const Memcpy2D*)>(library, "cuMemcpy2D_v2");
auto synchronize = load<CUresult (*)()>(library, "cuCtxSynchronize");
int device = 0;
void* context = nullptr;
void *pinned = nullptr, *array = nullptr;
CUdeviceptr linear = 0;
CUresult result = 0;
#define STEP(call) \
if ((result = (call)) != 0) \
{ \
std::fprintf(stderr, "%s failed: %d\n", #call, result); \
return 1; \
}
STEP(init(0));
STEP(device_get(&device, 0));
STEP(context_create(&context, 0, device));
STEP(alloc_host(&pinned, bytes, 0));
STEP(alloc(&linear, bytes));
const ArrayDescriptor descriptor{width, height, 0x10 /* HALF */, channels};
STEP(array_create(&array, &descriptor));
void* pageable = std::malloc(bytes);
std::memset(pageable, 1, bytes);
std::memset(pinned, 1, bytes);
enum { HOST = 1, DEVICE = 2, ARRAY = 3 };
auto run = [&](const char* label, uint32_t srcType, uint32_t dstType, void* host) {
Memcpy2D request{};
request.srcMemoryType = srcType;
request.dstMemoryType = dstType;
request.srcHost = host;
request.dstHost = host;
request.srcDevice = linear;
request.dstDevice = linear;
request.srcArray = array;
request.dstArray = array;
request.srcPitch = request.dstPitch = rowBytes;
request.WidthInBytes = rowBytes;
request.Height = height;
double best = 1e9;
for (int iteration = 0; iteration < 10; ++iteration)
{
synchronize();
const auto start = std::chrono::steady_clock::now();
if (copy(&request) || synchronize())
{
std::printf("%-26s failed\n", label);
return;
}
const double ms =
std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
best = ms < best ? ms : best;
}
std::printf("%-26s %7.3f ms %6.2f GB/s\n", label, best, bytes / best / 1e6);
};
if (argc > 1 && std::strcmp(argv[1], "overlap") == 0)
{
// The shim's per-frame transfers: color 856x480 RGBA16F, depth 856x480
// R32F and motion 2560x1440 RG16F up; the 2560x1440 RGBA16F result down.
// Compares issuing them back to back with overlapping the upload and
// the download on two streams (pinned host memory, async copies).
auto create_stream = load<CUresult (*)(void**, unsigned)>(library, "cuStreamCreate");
auto stream_sync = load<CUresult (*)(void*)>(library, "cuStreamSynchronize");
struct Plane { size_t w, h; unsigned ch; int32_t fmt; size_t texel; void* host; void* array; };
Plane planes[4] = {{856, 480, 4, 0x10, 8}, {856, 480, 1, 0x20, 4}, {2560, 1440, 2, 0x10, 4},
{2560, 1440, 4, 0x10, 8}};
for (Plane& plane : planes)
{
STEP(alloc_host(&plane.host, plane.w * plane.h * plane.texel, 0));
std::memset(plane.host, 1, plane.w * plane.h * plane.texel);
const ArrayDescriptor d{plane.w, plane.h, plane.fmt, plane.ch};
STEP(array_create(&plane.array, &d));
}
void *up = nullptr, *down = nullptr;
STEP(create_stream(&up, 1)); // CU_STREAM_NON_BLOCKING
STEP(create_stream(&down, 1));
auto request = [&](Plane& plane, bool upload) {
Memcpy2D r{};
r.srcMemoryType = upload ? HOST : ARRAY;
r.dstMemoryType = upload ? ARRAY : HOST;
r.srcHost = plane.host;
r.dstHost = plane.host;
r.srcArray = r.dstArray = plane.array;
r.srcPitch = r.dstPitch = plane.w * plane.texel;
r.WidthInBytes = plane.w * plane.texel;
r.Height = plane.h;
return r;
};
// Linear device staging per plane: the async DMA runs host<->linear on
// two non-blocking streams; arrays are filled/read with fast D2D copies.
auto copy_h2d_async = load<CUresult (*)(CUdeviceptr, const void*, size_t, void*)>(library, "cuMemcpyHtoDAsync_v2");
auto copy_d2h_async = load<CUresult (*)(void*, CUdeviceptr, size_t, void*)>(library, "cuMemcpyDtoHAsync_v2");
CUdeviceptr staging[4] = {};
for (int i = 0; i < 4; ++i)
STEP(alloc(&staging[i], planes[i].w * planes[i].h * planes[i].texel));
auto d2d = [&](Plane& plane, CUdeviceptr linear, bool toArray) {
Memcpy2D r{};
r.srcMemoryType = toArray ? DEVICE : ARRAY;
r.dstMemoryType = toArray ? ARRAY : DEVICE;
r.srcDevice = r.dstDevice = linear;
r.srcArray = r.dstArray = plane.array;
r.srcPitch = r.dstPitch = plane.w * plane.texel;
r.WidthInBytes = plane.w * plane.texel;
r.Height = plane.h;
return copy(&r);
};
for (int mode = 0; mode < 3; ++mode)
{
double best = 1e9;
for (int iteration = 0; iteration < 20; ++iteration)
{
synchronize();
const auto start = std::chrono::steady_clock::now();
CUresult status = 0;
if (mode == 0) // current shim: synchronous 2D copies, one after another
{
for (int i = 0; i < 3; ++i) { Memcpy2D r = request(planes[i], true); status |= copy(&r); }
Memcpy2D r = request(planes[3], false);
status |= copy(&r);
}
else
{
const size_t outBytes = planes[3].w * planes[3].h * planes[3].texel;
status |= d2d(planes[3], staging[3], false);
status |= copy_d2h_async(planes[3].host, staging[3], outBytes, down);
if (mode == 2) // serialized control: wait for the download first
status |= stream_sync(down);
for (int i = 0; i < 3; ++i)
status |= copy_h2d_async(staging[i], planes[i].host, planes[i].w * planes[i].h * planes[i].texel, up);
status |= stream_sync(up);
for (int i = 0; i < 3; ++i)
status |= d2d(planes[i], staging[i], true);
status |= stream_sync(down);
}
synchronize();
if (status != 0)
{
std::printf("mode %d failed\n", mode);
break;
}
const double ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
best = ms < best ? ms : best;
}
std::printf("%-46s %7.3f ms\n", mode == 0 ? "sequential sync 2D (current shim path)" : mode == 1 ? "linear staging, down || up overlapped" : "linear staging, down then up (no overlap)", best);
}
return 0;
}
std::printf("%zux%zu x%u half = %.1f MB\n", width, height, channels, bytes / 1e6);
run("pageable -> array", HOST, ARRAY, pageable);
run("pinned -> array", HOST, ARRAY, pinned);
run("pinned -> linear", HOST, DEVICE, pinned);
run("linear -> array", DEVICE, ARRAY, nullptr);
run("array -> pageable", ARRAY, HOST, pageable);
run("array -> pinned", ARRAY, HOST, pinned);
run("array -> linear", ARRAY, DEVICE, nullptr);
run("linear -> pinned", DEVICE, HOST, pinned);
return 0;
}
+222
View File
@@ -0,0 +1,222 @@
// Replays one captured CUDA launch (see D4R_CUDA_REPLAY_DUMP_DIR in
// tools/wine_nvcuda_bridge.c) through ZLUDA in isolation, to time a single
// DLSS kernel and to compare its output across ZLUDA builds.
//
// usage: bench_dlss_kernel FATBIN REPLAY_DIR [ITERATIONS [OUTPUT_PREFIX]]
//
// Every allocation the launch points into is restored from the capture
// before each launch. After the first launch each allocation is written to
// OUTPUT_PREFIX-alloc-N.bin (if given) and its FNV-1a hash is printed.
#include <dlfcn.h>
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>
using CUresult = int;
using CUdeviceptr = uint64_t;
template <typename Function> static Function load(void* library, const char* name)
{
void* raw = dlsym(library, name);
if (raw == nullptr)
{
std::fprintf(stderr, "missing %s\n", name);
std::exit(1);
}
Function function{};
std::memcpy(&function, &raw, sizeof(function));
return function;
}
static std::vector<unsigned char> read_file(const std::string& path)
{
std::ifstream input(path, std::ios::binary);
if (!input)
{
std::fprintf(stderr, "cannot read %s\n", path.c_str());
std::exit(2);
}
return std::vector<unsigned char>((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
}
#define CHECK(call) \
do \
{ \
const CUresult check_result = (call); \
if (check_result != 0) \
{ \
std::fprintf(stderr, "%s failed: %d\n", #call, check_result); \
return 1; \
} \
} while (0)
struct Allocation
{
size_t bytes = 0;
std::vector<unsigned char> data;
CUdeviceptr device = 0;
};
int main(int argc, char** argv)
{
if (argc < 3)
{
std::fprintf(stderr, "usage: bench_dlss_kernel FATBIN REPLAY_DIR [ITERATIONS [OUTPUT_PREFIX]]\n");
return 2;
}
const std::string directory = argv[2];
const int iterations = argc > 3 ? std::atoi(argv[3]) : 20;
const char* output_prefix = argc > 4 ? argv[4] : nullptr;
std::string kernel;
unsigned int launch[7] = {};
std::vector<Allocation> allocations;
struct Pointer { size_t offset, allocation; uint64_t delta; };
std::vector<Pointer> pointers;
{
std::ifstream manifest(directory + "/manifest.txt");
std::string line;
while (std::getline(manifest, line))
{
std::istringstream fields(line);
std::string tag;
fields >> tag;
if (tag == "kernel")
fields >> kernel;
else if (tag == "launch")
for (unsigned int& value : launch)
fields >> value;
else if (tag == "alloc")
{
size_t index = 0, bytes = 0;
std::string base;
fields >> index >> base >> bytes;
if (allocations.size() <= index)
allocations.resize(index + 1);
allocations[index].bytes = bytes;
allocations[index].data = read_file(directory + "/alloc-" + std::to_string(index) + ".bin");
}
else if (tag == "pointer")
{
Pointer pointer{};
fields >> pointer.offset >> pointer.allocation >> pointer.delta;
pointers.push_back(pointer);
}
else if (tag == "surface" || tag == "texture")
{
std::fprintf(stderr, "replay of %s arguments is not supported\n", tag.c_str());
return 2;
}
}
}
std::vector<unsigned char> arguments = read_file(directory + "/args.bin");
std::vector<unsigned char> image = read_file(argv[1]);
image.resize(image.size() + 16, 0);
void* library = dlopen("libcuda.so", RTLD_NOW | RTLD_LOCAL);
if (library == nullptr)
{
std::fprintf(stderr, "dlopen(libcuda.so): %s\n", dlerror());
return 1;
}
auto init = load<CUresult (*)(unsigned int)>(library, "cuInit");
auto device_get = load<CUresult (*)(int*, int)>(library, "cuDeviceGet");
auto context_create = load<CUresult (*)(void**, unsigned int, int)>(library, "cuCtxCreate_v2");
auto module_load = load<CUresult (*)(void**, const void*)>(library, "cuModuleLoadData");
auto get_function = load<CUresult (*)(void**, void*, const char*)>(library, "cuModuleGetFunction");
auto mem_alloc = load<CUresult (*)(CUdeviceptr*, size_t)>(library, "cuMemAlloc_v2");
auto copy_h2d = load<CUresult (*)(CUdeviceptr, const void*, size_t)>(library, "cuMemcpyHtoD_v2");
auto copy_d2h = load<CUresult (*)(void*, CUdeviceptr, size_t)>(library, "cuMemcpyDtoH_v2");
auto synchronize = load<CUresult (*)()>(library, "cuCtxSynchronize");
auto event_create = load<CUresult (*)(void**, unsigned int)>(library, "cuEventCreate");
auto event_record = load<CUresult (*)(void*, void*)>(library, "cuEventRecord");
auto event_elapsed = load<CUresult (*)(float*, void*, void*)>(library, "cuEventElapsedTime");
auto launch_kernel = load<CUresult (*)(void*, unsigned int, unsigned int, unsigned int, unsigned int,
unsigned int, unsigned int, unsigned int, void*, void**, void**)>(
library, "cuLaunchKernel");
int device = 0;
void* context = nullptr;
void* module = nullptr;
void* function = nullptr;
CHECK(init(0));
CHECK(device_get(&device, 0));
CHECK(context_create(&context, 0, device));
CHECK(module_load(&module, image.data()));
CHECK(get_function(&function, module, kernel.c_str()));
for (Allocation& allocation : allocations)
CHECK(mem_alloc(&allocation.device, allocation.bytes));
for (const Pointer& pointer : pointers)
{
const uint64_t value = allocations.at(pointer.allocation).device + pointer.delta;
std::memcpy(arguments.data() + pointer.offset, &value, sizeof(value));
}
size_t argument_bytes = arguments.size();
void* extra[] = {reinterpret_cast<void*>(1), arguments.data(), reinterpret_cast<void*>(2), &argument_bytes,
nullptr};
auto restore = [&]() -> CUresult {
for (Allocation& allocation : allocations)
if (CUresult result = copy_h2d(allocation.device, allocation.data.data(), allocation.bytes))
return result;
return 0;
};
auto run = [&]() {
return launch_kernel(function, launch[0], launch[1], launch[2], launch[3], launch[4], launch[5],
launch[6], nullptr, nullptr, extra);
};
CHECK(restore());
CHECK(run());
CHECK(synchronize());
for (size_t index = 0; index < allocations.size(); ++index)
{
std::vector<unsigned char> result(allocations[index].bytes);
CHECK(copy_d2h(result.data(), allocations[index].device, result.size()));
uint64_t hash = 1469598103934665603ull;
size_t changed = 0;
for (size_t byte = 0; byte < result.size(); ++byte)
{
hash = (hash ^ result[byte]) * 1099511628211ull;
changed += result[byte] != allocations[index].data[byte];
}
std::printf("alloc %zu bytes=%zu changed=%zu hash=%016llx\n", index, result.size(), changed,
static_cast<unsigned long long>(hash));
if (output_prefix != nullptr)
{
std::ofstream output(std::string(output_prefix) + "-alloc-" + std::to_string(index) + ".bin",
std::ios::binary);
output.write(reinterpret_cast<const char*>(result.data()), static_cast<std::streamsize>(result.size()));
}
}
void* start = nullptr;
void* end = nullptr;
CHECK(event_create(&start, 0));
CHECK(event_create(&end, 0));
std::vector<float> times;
for (int iteration = 0; iteration < iterations; ++iteration)
{
CHECK(restore());
CHECK(synchronize());
CHECK(event_record(start, nullptr));
CHECK(run());
CHECK(event_record(end, nullptr));
CHECK(synchronize());
float milliseconds = 0;
CHECK(event_elapsed(&milliseconds, start, end));
times.push_back(milliseconds);
}
std::sort(times.begin(), times.end());
if (!times.empty())
std::printf("kernel %s grid=%u,%u,%u block=%u,%u,%u iterations=%d min=%.4f median=%.4f max=%.4f ms\n",
kernel.c_str(), launch[0], launch[1], launch[2], launch[3], launch[4], launch[5], iterations,
times.front(), times[times.size() / 2], times.back());
return 0;
}
+79
View File
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""Compare PE section metadata and aligned byte blocks for local NGX DLLs."""
from __future__ import annotations
import argparse
import hashlib
from pathlib import Path
from inspect_ngx_dll import PEImage
def version_of(image: PEImage) -> str:
for resource in image.resources():
info = resource.get("version_info", {}).get("strings", {})
if "FileVersion" in info:
return info["FileVersion"]
return "unknown"
def compare_sections(a: PEImage, b: PEImage, block_size: int) -> None:
by_name_a = {section["name"]: section for section in a.sections}
by_name_b = {section["name"]: section for section in b.sections}
common = [name for name in by_name_a if name in by_name_b]
hash_a = hashlib.sha256(a.data).hexdigest()
hash_b = hashlib.sha256(b.data).hexdigest()
print(f"A: {a.path.name} version={version_of(a)} size={len(a.data)} sha256={hash_a}")
print(f"B: {b.path.name} version={version_of(b)} size={len(b.data)} sha256={hash_b}")
print(f"Aligned comparison uses {block_size}-byte blocks at matching section offsets.")
for name in common:
sa = by_name_a[name]
sb = by_name_b[name]
start_a, start_b = sa["raw_offset"], sb["raw_offset"]
size_a, size_b = sa["raw_size"], sb["raw_size"]
overlap = min(size_a, size_b)
prefix = 0
ba = a.data[start_a : start_a + overlap]
bb = b.data[start_b : start_b + overlap]
for x, y in zip(ba, bb):
if x != y:
break
prefix += 1
blocks = overlap // block_size
equal_blocks = 0
for index in range(blocks):
lo = index * block_size
hi = lo + block_size
if ba[lo:hi] == bb[lo:hi]:
equal_blocks += 1
aligned_percent = 100.0 * equal_blocks / blocks if blocks else 0.0
print(
f"{name:8} A={size_a:>10} B={size_b:>10} "
f"aligned-identical={equal_blocks}/{blocks} ({aligned_percent:.2f}%) "
f"common-prefix={prefix} bytes entropy={sa['entropy']:.3f}/{sb['entropy']:.3f} "
f"section-sha256={sa['sha256'][:12]}/{sb['sha256'][:12]}"
)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("dll_a", type=Path)
parser.add_argument("dll_b", type=Path)
parser.add_argument("--block-size", type=int, default=4096)
args = parser.parse_args()
if args.block_size < 64 or args.block_size & (args.block_size - 1):
parser.error("--block-size must be a power of two and at least 64")
image_a = PEImage(args.dll_a, string_limit=0)
image_b = PEImage(args.dll_b, string_limit=0)
compare_sections(image_a, image_b, args.block_size)
return 0
if __name__ == "__main__":
raise SystemExit(main())
+814
View File
@@ -0,0 +1,814 @@
// Drives d4r_nvngx.dll through the NGX D3D12 API the way a game (or
// OptiScaler's DLSS backend) does: a D3D12 device and command queue, input
// textures in NON_PIXEL_SHADER_RESOURCE, an output UAV, and one
// EvaluateFeature per recorded command list. The inputs reproduce the CUDA
// probe's synthetic frame (ngx_cuda_probe.cpp) so the D3D12 result can be
// compared with the direct CUDA result.
//
// usage: d3d12_dlss_harness.exe SHIM_DLL OUTPUT_RAW [FRAMES [IN_W IN_H OUT_W OUT_H]]
#define WIDL_EXPLICIT_AGGREGATE_RETURNS
#include <windows.h>
#include <d3d12.h>
#include <algorithm>
#include <array>
#include <bit>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
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);
NgxResult d4r_ngx_get_int(void* parameters, const char* name, int* value);
}
using PFN_Init_Ext = NgxResult (*)(unsigned long long, const wchar_t*, ID3D12Device*, unsigned int, const void*);
using PFN_Parameters = NgxResult (*)(void**);
using PFN_CreateFeature = NgxResult (*)(ID3D12GraphicsCommandList*, unsigned int, void*, NgxHandle**);
using PFN_Evaluate = NgxResult (*)(ID3D12GraphicsCommandList*, const NgxHandle*, void*, void*);
using PFN_Release = NgxResult (*)(NgxHandle*);
using PFN_Shutdown = NgxResult (*)();
static ID3D12Device* g_device;
static ID3D12CommandQueue* g_queue;
static ID3D12CommandAllocator* g_allocator;
static ID3D12GraphicsCommandList* g_list;
static ID3D12Fence* g_fence;
static HANDLE g_event;
static UINT64 g_fenceValue;
static bool check(HRESULT hr, const char* what)
{
if (FAILED(hr))
std::printf("%s failed: 0x%08lx\n", what, hr);
return SUCCEEDED(hr);
}
static void submit_and_wait()
{
g_list->Close();
ID3D12CommandList* lists[] = {g_list};
g_queue->ExecuteCommandLists(1, lists);
g_queue->Signal(g_fence, ++g_fenceValue);
if (g_fence->GetCompletedValue() < g_fenceValue)
{
g_fence->SetEventOnCompletion(g_fenceValue, g_event);
WaitForSingleObject(g_event, INFINITE);
}
g_allocator->Reset();
g_list->Reset(g_allocator, nullptr);
}
static ID3D12Resource* create_buffer(D3D12_HEAP_TYPE type, UINT64 size, D3D12_RESOURCE_STATES state)
{
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = type;
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Width = size;
desc.Height = 1;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
ID3D12Resource* buffer = nullptr;
check(g_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, state, nullptr,
__uuidof(ID3D12Resource), reinterpret_cast<void**>(&buffer)),
"CreateCommittedResource(buffer)");
return buffer;
}
static ID3D12Resource* create_texture(UINT width, UINT height, DXGI_FORMAT format, D3D12_RESOURCE_FLAGS flags)
{
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = D3D12_HEAP_TYPE_DEFAULT;
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
desc.Width = width;
desc.Height = height;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.Format = format;
desc.SampleDesc.Count = 1;
desc.Flags = flags;
ID3D12Resource* texture = nullptr;
check(g_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COPY_DEST,
nullptr, __uuidof(ID3D12Resource), reinterpret_cast<void**>(&texture)),
"CreateCommittedResource(texture)");
return texture;
}
static void barrier(ID3D12Resource* resource, D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after)
{
D3D12_RESOURCE_BARRIER b = {};
b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
b.Transition.pResource = resource;
b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
b.Transition.StateBefore = before;
b.Transition.StateAfter = after;
g_list->ResourceBarrier(1, &b);
}
// Uploads tightly packed rows into a texture, leaving it in finalState.
static void upload(ID3D12Resource* texture, const void* data, UINT rowBytes, D3D12_RESOURCE_STATES finalState)
{
D3D12_RESOURCE_DESC desc;
texture->GetDesc(&desc);
D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout;
UINT rows;
UINT64 rowSize, total;
g_device->GetCopyableFootprints(&desc, 0, 1, 0, &layout, &rows, &rowSize, &total);
ID3D12Resource* staging = create_buffer(D3D12_HEAP_TYPE_UPLOAD, total, D3D12_RESOURCE_STATE_GENERIC_READ);
uint8_t* mapped = nullptr;
D3D12_RANGE none = {0, 0};
staging->Map(0, &none, reinterpret_cast<void**>(&mapped));
for (UINT y = 0; y < rows; ++y)
std::memcpy(mapped + layout.Offset + static_cast<size_t>(y) * layout.Footprint.RowPitch,
static_cast<const uint8_t*>(data) + static_cast<size_t>(y) * rowBytes, rowBytes);
staging->Unmap(0, nullptr);
D3D12_TEXTURE_COPY_LOCATION destination = {};
destination.pResource = texture;
destination.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
D3D12_TEXTURE_COPY_LOCATION source = {};
source.pResource = staging;
source.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
source.PlacedFootprint = layout;
g_list->CopyTextureRegion(&destination, 0, 0, 0, &source, nullptr);
barrier(texture, D3D12_RESOURCE_STATE_COPY_DEST, finalState);
submit_and_wait();
staging->Release();
}
static void update_texture(ID3D12Resource* texture, const void* data, UINT rowBytes,
D3D12_RESOURCE_STATES currentState)
{
barrier(texture, currentState, D3D12_RESOURCE_STATE_COPY_DEST);
submit_and_wait();
upload(texture, data, rowBytes, currentState);
}
static std::vector<uint8_t> read_back(ID3D12Resource* texture, D3D12_RESOURCE_STATES state, UINT rowBytes)
{
D3D12_RESOURCE_DESC desc;
texture->GetDesc(&desc);
D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout;
UINT rows;
UINT64 rowSize, total;
g_device->GetCopyableFootprints(&desc, 0, 1, 0, &layout, &rows, &rowSize, &total);
ID3D12Resource* staging = create_buffer(D3D12_HEAP_TYPE_READBACK, total, D3D12_RESOURCE_STATE_COPY_DEST);
barrier(texture, state, D3D12_RESOURCE_STATE_COPY_SOURCE);
D3D12_TEXTURE_COPY_LOCATION destination = {};
destination.pResource = staging;
destination.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
destination.PlacedFootprint = layout;
D3D12_TEXTURE_COPY_LOCATION source = {};
source.pResource = texture;
source.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
g_list->CopyTextureRegion(&destination, 0, 0, 0, &source, nullptr);
barrier(texture, D3D12_RESOURCE_STATE_COPY_SOURCE, state);
submit_and_wait();
std::vector<uint8_t> result(static_cast<size_t>(rowBytes) * rows);
uint8_t* mapped = nullptr;
D3D12_RANGE range = {0, static_cast<SIZE_T>(total)};
staging->Map(0, &range, reinterpret_cast<void**>(&mapped));
for (UINT y = 0; y < rows; ++y)
std::memcpy(result.data() + static_cast<size_t>(y) * rowBytes,
mapped + layout.Offset + static_cast<size_t>(y) * layout.Footprint.RowPitch, rowBytes);
staging->Unmap(0, nullptr);
staging->Release();
return result;
}
static void trace_stage(const char* stage)
{
const char* path = std::getenv("D4R_HARNESS_TRACE");
if (path == nullptr || path[0] == '\0')
return;
if (FILE* file = std::fopen(path, "a"))
{
std::fprintf(file, "%s\n", stage);
std::fclose(file);
}
}
// A CPU-rasterized, subpixel-shifted version of the synthetic fence scene.
// Positive jitter moves the image right/down in screen space, so a render
// pixel samples the scene at (pixel - jitter). Four-by-four coverage samples
// make thin-line and checkerboard changes visible to temporal DLSS.
static void render_jittered_background(std::vector<uint16_t>& color, UINT width, UINT height, UINT stride,
float jitterX, float jitterY)
{
for (UINT y = 0; y < height; ++y)
for (UINT x = 0; x < width; ++x)
{
float sum[3] = {};
for (int sy = 0; sy < 4; ++sy)
for (int sx = 0; sx < 4; ++sx)
{
const float sampleX = float(x) + (float(sx) + 0.5f) * 0.25f - 0.5f - jitterX;
const float sampleY = float(y) + (float(sy) + 0.5f) * 0.25f - 0.5f - jitterY;
const int ix = std::clamp(int(std::floor(sampleX)), 0, int(width) - 1);
const int iy = std::clamp(int(std::floor(sampleY)), 0, int(height) - 1);
const bool fence = (ix % 24 == 0) || (iy % 24 == 0);
const bool checker = (((ix / 4) + (iy / 4)) & 1) != 0;
sum[0] += fence ? 1.0f : (checker ? 0.25f : 0.05f);
sum[1] += ((ix + iy) % 32 < 2) ? 0.8f : (checker ? 0.05f : 0.25f);
sum[2] += (((ix / 8) ^ (iy / 8)) & 1) ? 0.8f : 0.05f;
}
const size_t pixel = static_cast<size_t>(y) * stride + x;
for (size_t channel = 0; channel < 3; ++channel)
color[pixel * 4 + channel] = std::bit_cast<uint16_t>(static_cast<_Float16>(sum[channel] / 16.0f));
color[pixel * 4 + 3] = 0x3c00u;
}
}
// Quality scene (D4R_HARNESS_QUALITY_SCENE=<report file, Windows path>): a static analytic scene with
// detail near and below the render pixel pitch (blinds, thin poles, fine
// diagonals), rendered like a game feeds DLSS: one jittered point sample per
// render pixel. Coordinates are in output pixels.
static float quality_scene(float x, float y, UINT outWidth, UINT outHeight)
{
const float u = x / outWidth, v = y / outHeight;
auto frac = [](float value) { return value - std::floor(value); };
if (u > 0.05f && u < 0.45f && v > 0.1f && v < 0.9f) // blinds: 5 px period, 35% slat
return frac(y / 5.0f) < 0.35f ? 0.9f : 0.08f;
if (u > 0.55f && u < 0.95f && v > 0.1f && v < 0.45f) // thin poles: 7 px period, 1.4 px wide
return frac(x / 7.0f) < 0.2f ? 0.85f : 0.1f;
if (u > 0.55f && u < 0.95f && v > 0.55f && v < 0.9f) // fine diagonals: 6 px period
return frac((x + y) / 6.0f) < 0.25f ? 0.8f : 0.12f;
return 0.3f + 0.1f * std::sin(u * 6.0f) * std::cos(v * 5.0f);
}
// Pole and cable geometry resembling the reported Townfall failure. All
// positions are in output pixels, so both resource-width cases get identical
// active samples and only their three padding columns differ.
static float pole_scene(float x, float y, UINT outWidth, UINT outHeight)
{
const float u = x / outWidth, v = y / outHeight;
auto line = [](float px, float py, float ax, float ay, float bx, float by, float width) {
const float dx = bx - ax, dy = by - ay;
const float t = std::clamp(((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy), 0.0f, 1.0f);
const float ex = px - ax - t * dx, ey = py - ay - t * dy;
return ex * ex + ey * ey < width * width;
};
if (std::abs(u - 0.52f) < 0.004f && v > 0.15f)
return 0.035f;
for (float height : {0.24f, 0.36f, 0.52f, 0.68f})
if (line(u, v, 0.49f, height, 0.55f, height - 0.004f, 0.00045f))
return 0.045f;
if (line(u, v, 0.0f, 0.37f, 1.0f, 0.21f, 0.00036f) ||
line(u, v, 0.1f, 0.02f, 0.93f, 0.85f, 0.00030f) ||
line(u, v, 0.02f, 0.7f, 0.95f, 0.44f, 0.00025f))
return 0.04f;
return 0.46f + 0.03f * v;
}
// D4R_HARNESS_REPLAY_DIR=<dir> replays frames captured in a game by the shim
// (D4R_SHIM_INPUT_DUMP_DIR + D4R_SHIM_CAPTURE_COUNT): frame-NNNNNN-color.rgba16f,
// -depth.r32f, -motion.rg16f and -params.txt, starting at D4R_HARNESS_REPLAY_START.
struct ReplayFrame
{
std::vector<uint8_t> color, depth, motion;
float jitterX = 0, jitterY = 0, mvScaleX = 1, mvScaleY = 1, sharpness = 0;
float preExposure = 1, exposureScale = 1, frameTime = 16.666667f;
int reset = 0, hasExposure = 0;
unsigned renderWidth = 0, renderHeight = 0;
unsigned colorBaseX = 0, colorBaseY = 0, depthBaseX = 0, depthBaseY = 0, mvBaseX = 0, mvBaseY = 0;
};
static bool read_file(const std::string& path, std::vector<uint8_t>& bytes, size_t expected)
{
FILE* file = std::fopen(path.c_str(), "rb");
if (file == nullptr)
return false;
bytes.resize(expected);
const size_t got = std::fread(bytes.data(), 1, expected, file);
const bool extra = std::fgetc(file) != EOF;
std::fclose(file);
return got == expected && !extra;
}
static bool load_replay_frame(const char* directory, uint32_t frame, size_t colorBytes, size_t depthBytes,
size_t motionBytes, ReplayFrame& out)
{
char prefix[MAX_PATH];
std::snprintf(prefix, sizeof(prefix), "%s\\frame-%06u-", directory, frame);
const std::string base = prefix;
if (!read_file(base + "color.rgba16f", out.color, colorBytes) ||
!read_file(base + "depth.r32f", out.depth, depthBytes) ||
!read_file(base + "motion.rg16f", out.motion, motionBytes))
{
std::fprintf(stderr, "replay frame %u: missing or mis-sized plane under %s\n", frame, prefix);
return false;
}
FILE* file = std::fopen((base + "params.txt").c_str(), "r");
if (file == nullptr)
{
std::fprintf(stderr, "replay frame %u: no params\n", frame);
return false;
}
char key[64];
while (std::fscanf(file, "%63s", key) == 1)
{
const std::string name = key;
if (name == "jitter") std::fscanf(file, "%f %f", &out.jitterX, &out.jitterY);
else if (name == "mv_scale") std::fscanf(file, "%f %f", &out.mvScaleX, &out.mvScaleY);
else if (name == "sharpness") std::fscanf(file, "%f", &out.sharpness);
else if (name == "pre_exposure") std::fscanf(file, "%f", &out.preExposure);
else if (name == "exposure_scale") std::fscanf(file, "%f", &out.exposureScale);
else if (name == "frame_time") std::fscanf(file, "%f", &out.frameTime);
else if (name == "reset") std::fscanf(file, "%d", &out.reset);
else if (name == "has_exposure") std::fscanf(file, "%d", &out.hasExposure);
else if (name == "render") std::fscanf(file, "%u %u", &out.renderWidth, &out.renderHeight);
else if (name == "color_base") std::fscanf(file, "%u %u", &out.colorBaseX, &out.colorBaseY);
else if (name == "depth_base") std::fscanf(file, "%u %u", &out.depthBaseX, &out.depthBaseY);
else if (name == "mv_base") std::fscanf(file, "%u %u", &out.mvBaseX, &out.mvBaseY);
else std::fscanf(file, "%*[^\n]");
}
std::fclose(file);
return true;
}
static float halton(uint32_t index, uint32_t base)
{
float result = 0.0f, fraction = 1.0f;
for (; index > 0; index /= base)
{
fraction /= static_cast<float>(base);
result += fraction * static_cast<float>(index % base);
}
return result;
}
int main(int argc, char** argv)
{
trace_stage("entered main");
if (argc < 3)
{
std::fprintf(stderr, "usage: %s SHIM_DLL OUTPUT_RAW [FRAMES]\n", argv[0]);
return 2;
}
const int frames = argc > 3 ? std::atoi(argv[3]) : 6;
const UINT inWidth = argc > 7 ? static_cast<UINT>(std::atoi(argv[4])) : 640;
const UINT inHeight = argc > 7 ? static_cast<UINT>(std::atoi(argv[5])) : 360;
const UINT outWidth = argc > 7 ? static_cast<UINT>(std::atoi(argv[6])) : 1280;
const UINT outHeight = argc > 7 ? static_cast<UINT>(std::atoi(argv[7])) : 720;
const char* resourceWidthSetting = std::getenv("D4R_HARNESS_RESOURCE_WIDTH");
const UINT resourceWidth = resourceWidthSetting != nullptr
? static_cast<UINT>(std::atoi(resourceWidthSetting)) : inWidth;
if (resourceWidth < inWidth || resourceWidth > inWidth + 256)
{
std::fprintf(stderr, "Resource width must be between active width and active width + 256\n");
return 2;
}
const bool motionScene = std::getenv("D4R_HARNESS_MOTION_SCENE") != nullptr;
const bool jitterScene = std::getenv("D4R_HARNESS_JITTER_SCENE") != nullptr;
if (motionScene && jitterScene)
{
std::fprintf(stderr, "Choose one synthetic temporal scene at a time\n");
return 2;
}
const bool qualityScene = std::getenv("D4R_HARNESS_QUALITY_SCENE") != nullptr;
const bool poleScene = std::getenv("D4R_HARNESS_POLE_SCENE") != nullptr;
const char* replayDir = std::getenv("D4R_HARNESS_REPLAY_DIR");
const uint32_t replayStart = std::getenv("D4R_HARNESS_REPLAY_START") != nullptr
? static_cast<uint32_t>(std::atoi(std::getenv("D4R_HARNESS_REPLAY_START"))) : 1;
const bool temporal = std::getenv("D4R_HARNESS_TEMPORAL") != nullptr || motionScene || jitterScene || qualityScene ||
replayDir != nullptr;
const bool forceReset = std::getenv("D4R_HARNESS_FORCE_RESET") != nullptr;
const bool saveFrames = std::getenv("D4R_HARNESS_SAVE_FRAMES") != nullptr;
const char* waitSetting = std::getenv("D4R_HARNESS_FRAME_WAIT_MS");
const int requestedWait = waitSetting != nullptr ? std::atoi(waitSetting) : 200;
const DWORD frameWaitMs = requestedWait >= 0 && requestedWait <= 10000 ? requestedWait : 200;
trace_stage(temporal ? "temporal history enabled" : "reset each frame");
HMODULE shim = nullptr;
if (std::getenv("D4R_HARNESS_LOAD_NGX_EARLY") != nullptr)
{
trace_stage("loading NGX DLL before D3D12 setup");
shim = LoadLibraryA(argv[1]);
trace_stage(shim != nullptr ? "early NGX DLL load succeeded" : "early NGX DLL load failed");
if (shim == nullptr)
return 1;
}
HMODULE d3d12 = LoadLibraryA("d3d12.dll");
auto createDevice = reinterpret_cast<HRESULT(WINAPI*)(IUnknown*, D3D_FEATURE_LEVEL, REFIID, void**)>(
reinterpret_cast<void*>(GetProcAddress(d3d12, "D3D12CreateDevice")));
if (createDevice == nullptr ||
!check(createDevice(nullptr, D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device), reinterpret_cast<void**>(&g_device)),
"D3D12CreateDevice"))
return 1;
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
check(g_device->CreateCommandQueue(&queueDesc, __uuidof(ID3D12CommandQueue), reinterpret_cast<void**>(&g_queue)),
"CreateCommandQueue");
check(g_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, __uuidof(ID3D12CommandAllocator),
reinterpret_cast<void**>(&g_allocator)),
"CreateCommandAllocator");
check(g_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_allocator, nullptr,
__uuidof(ID3D12GraphicsCommandList), reinterpret_cast<void**>(&g_list)),
"CreateCommandList");
check(g_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence), reinterpret_cast<void**>(&g_fence)),
"CreateFence");
g_event = CreateEventA(nullptr, FALSE, FALSE, nullptr);
std::printf("D3D12 device ready\n");
trace_stage("D3D12 device ready");
// Synthetic inputs, identical to ngx_cuda_probe.cpp.
std::vector<uint16_t> color(static_cast<size_t>(resourceWidth) * inHeight * 4);
constexpr uint16_t dark = 0x2a66u, mid = 0x3400u, bright = 0x3a66u, white = 0x3c00u;
for (UINT y = 0; y < inHeight; ++y)
for (UINT x = 0; x < inWidth; ++x)
{
const size_t pixel = static_cast<size_t>(y) * resourceWidth + x;
const bool fence = (x % 24u == 0u) || (y % 24u == 0u);
const bool checker = (((x / 4u) + (y / 4u)) & 1u) != 0;
color[pixel * 4 + 0] = fence ? white : (checker ? mid : dark);
color[pixel * 4 + 1] = ((x + y) % 32u < 2u) ? bright : (checker ? dark : mid);
color[pixel * 4 + 2] = (((x / 8u) ^ (y / 8u)) & 1u) ? bright : dark;
color[pixel * 4 + 3] = white;
}
for (UINT y = 0; y < inHeight; ++y)
for (UINT x = inWidth; x < resourceWidth; ++x)
{
const size_t pixel = (static_cast<size_t>(y) * resourceWidth + x) * 4;
color[pixel] = white;
color[pixel + 1] = color[pixel + 2] = 0;
color[pixel + 3] = white;
}
const std::vector<uint16_t> backgroundColor = color;
std::vector<float> depth(static_cast<size_t>(resourceWidth) * inHeight, 0.5f);
for (UINT y = 0; y < inHeight; ++y)
for (UINT x = inWidth; x < resourceWidth; ++x)
depth[static_cast<size_t>(y) * resourceWidth + x] = 0.0f;
// D4R_HARNESS_MV_HIRES=1 gives DLSS display-resolution motion vectors,
// what it expects without the MVLowRes create flag (as games pass them).
const bool motionHighRes = std::getenv("D4R_HARNESS_MV_HIRES") != nullptr;
const UINT motionWidth = motionHighRes ? outWidth : resourceWidth, motionHeight = motionHighRes ? outHeight : inHeight;
std::vector<uint16_t> motion(static_cast<size_t>(motionWidth) * motionHeight * 2, 0);
const float exposureValue = 1.0f;
std::vector<uint16_t> outputInit(static_cast<size_t>(outWidth) * outHeight * 4, 0);
const auto srv = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
ID3D12Resource* colorTexture = create_texture(resourceWidth, inHeight, DXGI_FORMAT_R16G16B16A16_FLOAT, D3D12_RESOURCE_FLAG_NONE);
ID3D12Resource* depthTexture = create_texture(resourceWidth, inHeight, DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE);
ID3D12Resource* motionTexture = create_texture(motionWidth, motionHeight, DXGI_FORMAT_R16G16_FLOAT, D3D12_RESOURCE_FLAG_NONE);
ID3D12Resource* exposureTexture = create_texture(1, 1, DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE);
ID3D12Resource* outputTexture = create_texture(outWidth, outHeight, DXGI_FORMAT_R16G16B16A16_FLOAT,
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
upload(colorTexture, color.data(), resourceWidth * 8, srv);
upload(depthTexture, depth.data(), resourceWidth * 4, srv);
upload(motionTexture, motion.data(), motionWidth * 4, srv);
upload(exposureTexture, &exposureValue, 4, srv);
upload(outputTexture, outputInit.data(), outWidth * 8, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
std::printf("synthetic inputs uploaded\n");
trace_stage("synthetic inputs uploaded");
if (shim == nullptr)
{
trace_stage("loading NGX DLL");
shim = LoadLibraryA(argv[1]);
trace_stage(shim != nullptr ? "NGX DLL loaded" : "NGX DLL load failed");
}
if (shim == nullptr)
{
std::printf("LoadLibraryA(%s) failed: %lu\n", argv[1], GetLastError());
return 1;
}
auto init = reinterpret_cast<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")));
wchar_t dataPath[MAX_PATH];
GetTempPathW(MAX_PATH, dataPath);
NgxResult result = init(241534723ULL, dataPath, g_device, 0x15, nullptr);
trace_stage(result == NGX_SUCCESS ? "NGX init succeeded" : "NGX init failed");
std::printf("NVSDK_NGX_D3D12_Init_Ext -> 0x%08x\n", result);
if (result != NGX_SUCCESS)
return 1;
void* capabilities = nullptr;
result = capability(&capabilities);
int available = -1;
if (result == NGX_SUCCESS)
d4r_ngx_get_int(capabilities, "SuperSampling.Available", &available);
std::printf("GetCapabilityParameters -> 0x%08x, SuperSampling.Available=%d\n", result, available);
void* parameters = nullptr;
result = allocate(&parameters);
std::printf("AllocateParameters -> 0x%08x\n", result);
d4r_ngx_set_uint(parameters, "Width", inWidth);
d4r_ngx_set_uint(parameters, "Height", inHeight);
d4r_ngx_set_uint(parameters, "OutWidth", outWidth);
d4r_ngx_set_uint(parameters, "OutHeight", outHeight);
// D4R_HARNESS_QUALITY selects NVSDK_NGX_PerfQuality_Value (default 2,
// MaxQuality; 0 MaxPerf, 1 Balanced, 3 UltraPerformance, 5 DLAA).
const char* quality = std::getenv("D4R_HARNESS_QUALITY");
d4r_ngx_set_int(parameters, "PerfQualityValue", quality != nullptr && *quality != '\0' ? std::atoi(quality) : 2);
// D4R_HARNESS_CREATE_FLAGS mirrors a game's NVSDK_NGX_DLSS_Feature_Flags
// (e.g. 0x49 = HDR | DepthInverted | AutoExposure).
const char* createFlags = std::getenv("D4R_HARNESS_CREATE_FLAGS");
d4r_ngx_set_int(parameters, "DLSS.Feature.Create.Flags",
createFlags != nullptr ? static_cast<int>(std::strtol(createFlags, nullptr, 0)) : 0);
NgxHandle* feature = nullptr;
result = createFeature(g_list, 1, parameters, &feature);
std::printf("CreateFeature -> 0x%08x handle=%p\n", result, static_cast<void*>(feature));
if (result != NGX_SUCCESS)
return 1;
d4r_ngx_set_d3d12_resource(parameters, "Color", colorTexture);
d4r_ngx_set_d3d12_resource(parameters, "Depth", depthTexture);
d4r_ngx_set_d3d12_resource(parameters, "MotionVectors", motionTexture);
d4r_ngx_set_d3d12_resource(parameters, "Output", outputTexture);
d4r_ngx_set_d3d12_resource(parameters, "ExposureTexture", exposureTexture);
d4r_ngx_set_float(parameters, "Jitter.Offset.X", 0.0f);
d4r_ngx_set_float(parameters, "Jitter.Offset.Y", 0.0f);
d4r_ngx_set_float(parameters, "MV.Scale.X", 1.0f);
d4r_ngx_set_float(parameters, "MV.Scale.Y", 1.0f);
d4r_ngx_set_float(parameters, "Sharpness", 0.0f);
d4r_ngx_set_float(parameters, "DLSS.Pre.Exposure", 1.0f);
d4r_ngx_set_float(parameters, "DLSS.Exposure.Scale", 1.0f);
d4r_ngx_set_float(parameters, "FrameTimeDeltaInMsec", 16.666667f);
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Width", inWidth);
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Height", inHeight);
// Default to the proven single-frame reference. Temporal probes reset only
// the first frame and let the same official NGX feature accumulate history.
d4r_ngx_set_int(parameters, "Reset", 1);
// Quality scene ground truth (8x8 supersampled output pixels) and metrics.
// D4R_HARNESS_QUALITY_PAN="vx,vy" scrolls the scene content by whole
// output pixels per frame (motion vectors report it), so every frame's
// truth is an exact shift of one canvas that covers the whole scroll.
int panX = 0, panY = 0;
if (const char* pan = std::getenv("D4R_HARNESS_QUALITY_PAN"))
std::sscanf(pan, "%d,%d", &panX, &panY);
const int canvasX0 = std::min(0, -frames * panX), canvasY0 = std::min(0, -frames * panY);
const int canvasWidth = static_cast<int>(outWidth) + std::abs(frames * panX);
const int canvasHeight = static_cast<int>(outHeight) + std::abs(frames * panY);
std::vector<float> truth;
std::vector<float> previousError;
double flickerSum = 0.0;
int flickerFrames = 0;
if (qualityScene && !poleScene)
{
truth.resize(static_cast<size_t>(canvasWidth) * canvasHeight);
const int truthSamples = poleScene ? 2 : 8;
for (int y = 0; y < canvasHeight; ++y)
for (int x = 0; x < canvasWidth; ++x)
{
float sum = 0.0f;
for (int sy = 0; sy < truthSamples; ++sy)
for (int sx = 0; sx < truthSamples; ++sx)
sum += poleScene
? pole_scene(x + canvasX0 + (sx + 0.5f) / truthSamples,
y + canvasY0 + (sy + 0.5f) / truthSamples, outWidth, outHeight)
: quality_scene(x + canvasX0 + (sx + 0.5f) / truthSamples,
y + canvasY0 + (sy + 0.5f) / truthSamples, outWidth, outHeight);
truth[static_cast<size_t>(y) * canvasWidth + x] = sum / (truthSamples * truthSamples);
}
}
const float scaleX = static_cast<float>(outWidth) / inWidth, scaleY = static_cast<float>(outHeight) / inHeight;
const uint32_t phases = static_cast<uint32_t>(std::lround(8.0f * scaleX * scaleY));
for (int frame = 1; frame <= frames; ++frame)
{
if (qualityScene)
{
// Halton(2,3) jitter in render pixels, [-0.5, 0.5); a render pixel
// samples the scene at its centre plus the jitter.
const uint32_t index = static_cast<uint32_t>(frame - 1) % phases + 1;
const float jx = halton(index, 2) - 0.5f, jy = halton(index, 3) - 0.5f;
// DLSS's Jitter.Offset is minus the sample offset (verified: the other
// signs lose ~9 dB). D4R_HARNESS_JITTER_SIGNS="sx,sy" overrides it.
float signX = -1.0f, signY = -1.0f;
if (const char* signs = std::getenv("D4R_HARNESS_JITTER_SIGNS"))
std::sscanf(signs, "%f,%f", &signX, &signY);
// Scene content at frame f sits f * pan output pixels further along.
const float shiftX = static_cast<float>(frame * panX), shiftY = static_cast<float>(frame * panY);
for (UINT y = 0; y < inHeight; ++y)
for (UINT x = 0; x < inWidth; ++x)
{
const float sampleX = (x + 0.5f + jx) * scaleX - shiftX;
const float sampleY = (y + 0.5f + jy) * scaleY - shiftY;
const float value = poleScene
? pole_scene(sampleX, sampleY, outWidth, outHeight)
: quality_scene(sampleX, sampleY, outWidth, outHeight);
const size_t pixel = static_cast<size_t>(y) * resourceWidth + x;
const uint16_t half = std::bit_cast<uint16_t>(static_cast<_Float16>(value));
color[pixel * 4 + 0] = color[pixel * 4 + 1] = color[pixel * 4 + 2] = half;
color[pixel * 4 + 3] = 0x3c00u;
}
update_texture(colorTexture, color.data(), resourceWidth * 8, srv);
if (frame == 1 && (panX != 0 || panY != 0))
{
// Current position -> previous position. Without MVLowRes DLSS
// reads display-resolution vectors in output pixels (its input
// kernel adds them to the output pixel position); render-size
// vectors are in render pixels. D4R_HARNESS_MV_MULT scales them
// (convention checks).
const char* multiplier = std::getenv("D4R_HARNESS_MV_MULT");
const float mvScale = multiplier != nullptr ? std::strtof(multiplier, nullptr) : 1.0f;
const float unitX = motionHighRes ? 1.0f : 1.0f / scaleX, unitY = motionHighRes ? 1.0f : 1.0f / scaleY;
const uint16_t mvX = std::bit_cast<uint16_t>(static_cast<_Float16>(-mvScale * panX * unitX));
const uint16_t mvY = std::bit_cast<uint16_t>(static_cast<_Float16>(-mvScale * panY * unitY));
for (size_t pixel = 0; pixel < motion.size() / 2; ++pixel)
{
motion[pixel * 2 + 0] = mvX;
motion[pixel * 2 + 1] = mvY;
}
update_texture(motionTexture, motion.data(), motionWidth * 4, srv);
}
d4r_ngx_set_float(parameters, "Jitter.Offset.X", signX * jx);
d4r_ngx_set_float(parameters, "Jitter.Offset.Y", signY * jy);
}
if (jitterScene)
{
constexpr std::array<float, 8> jitterX =
{0.0f, -0.25f, 0.25f, -0.375f, 0.125f, 0.375f, -0.125f, 0.0f};
constexpr std::array<float, 8> jitterY =
{0.0f, 0.25f, -0.25f, -0.125f, 0.375f, 0.125f, -0.375f, 0.0f};
const size_t phase = static_cast<size_t>(frame - 1) % jitterX.size();
render_jittered_background(color, inWidth, inHeight, resourceWidth, jitterX[phase], jitterY[phase]);
update_texture(colorTexture, color.data(), resourceWidth * 8, srv);
d4r_ngx_set_float(parameters, "Jitter.Offset.X", jitterX[phase]);
d4r_ngx_set_float(parameters, "Jitter.Offset.Y", jitterY[phase]);
}
if (motionScene)
{
color = backgroundColor;
std::fill(depth.begin(), depth.end(), 0.5f);
std::fill(motion.begin(), motion.end(), 0);
const UINT left = 220u + 4u * static_cast<UINT>(frame - 1);
for (UINT py = 100; py < 180 && py < inHeight; ++py)
for (UINT px = left; px < left + 80 && px < inWidth; ++px)
{
const size_t pixel = static_cast<size_t>(py) * resourceWidth + px;
color[pixel * 4 + 0] = white;
color[pixel * 4 + 1] = dark;
color[pixel * 4 + 2] = dark;
color[pixel * 4 + 3] = white;
depth[pixel] = 0.2f;
const size_t motionPixel = static_cast<size_t>(py) * motionWidth + px;
motion[motionPixel * 2 + 0] = 0xc400u; // -4 render pixels: current position -> previous
motion[motionPixel * 2 + 1] = 0;
}
update_texture(colorTexture, color.data(), resourceWidth * 8, srv);
update_texture(depthTexture, depth.data(), resourceWidth * 4, srv);
update_texture(motionTexture, motion.data(), motionWidth * 4, srv);
}
if (replayDir != nullptr)
{
ReplayFrame replay;
if (!load_replay_frame(replayDir, replayStart + static_cast<uint32_t>(frame - 1),
color.size() * sizeof(uint16_t), depth.size() * sizeof(float),
motion.size() * sizeof(uint16_t), replay))
return 1;
update_texture(colorTexture, replay.color.data(), resourceWidth * 8, srv);
update_texture(depthTexture, replay.depth.data(), resourceWidth * 4, srv);
update_texture(motionTexture, replay.motion.data(), motionWidth * 4, srv);
d4r_ngx_set_float(parameters, "Jitter.Offset.X", replay.jitterX);
d4r_ngx_set_float(parameters, "Jitter.Offset.Y", replay.jitterY);
d4r_ngx_set_float(parameters, "MV.Scale.X", replay.mvScaleX);
d4r_ngx_set_float(parameters, "MV.Scale.Y", replay.mvScaleY);
d4r_ngx_set_float(parameters, "Sharpness", replay.sharpness);
d4r_ngx_set_float(parameters, "DLSS.Pre.Exposure", replay.preExposure);
d4r_ngx_set_float(parameters, "DLSS.Exposure.Scale", replay.exposureScale);
d4r_ngx_set_float(parameters, "FrameTimeDeltaInMsec", replay.frameTime);
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Width", replay.renderWidth);
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Height", replay.renderHeight);
d4r_ngx_set_uint(parameters, "DLSS.Input.Color.Subrect.Base.X", replay.colorBaseX);
d4r_ngx_set_uint(parameters, "DLSS.Input.Color.Subrect.Base.Y", replay.colorBaseY);
d4r_ngx_set_uint(parameters, "DLSS.Input.Depth.Subrect.Base.X", replay.depthBaseX);
d4r_ngx_set_uint(parameters, "DLSS.Input.Depth.Subrect.Base.Y", replay.depthBaseY);
d4r_ngx_set_uint(parameters, "DLSS.Input.MV.Subrect.Base.X", replay.mvBaseX);
d4r_ngx_set_uint(parameters, "DLSS.Input.MV.Subrect.Base.Y", replay.mvBaseY);
d4r_ngx_set_int(parameters, "Reset", frame == 1 || replay.reset != 0 ? 1 : 0);
// Games with AutoExposure pass no exposure texture; the synthetic scenes pass 1.0.
d4r_ngx_set_d3d12_resource(parameters, "ExposureTexture", replay.hasExposure ? exposureTexture : nullptr);
}
else if (temporal)
d4r_ngx_set_int(parameters, "Reset", forceReset || frame == 1 ? 1 : 0);
const DWORD start = GetTickCount();
result = evaluate(g_list, feature, parameters, nullptr);
submit_and_wait();
std::printf("frame %d: EvaluateFeature -> 0x%08x (%lu ms incl. submit)\n", frame, result, GetTickCount() - start);
Sleep(frameWaitMs); // let the CUDA worker finish before the next presentation
if (qualityScene && !poleScene && frame > frames - 17)
{
// Luma of the presented result vs the ground truth at this frame's
// scroll position, and the frame-to-frame change of that error
// (ideally 0; for a static scene it is the change of the output).
const std::vector<uint8_t> raw = read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, outWidth * 8);
const uint16_t* halves = reinterpret_cast<const uint16_t*>(raw.data());
const size_t pixels = static_cast<size_t>(outWidth) * outHeight;
// The shim presents the previous evaluation's result (one frame of
// pipeline latency), so compare with the truth of that frame.
static const int presentedAge = std::getenv("D4R_HARNESS_QUALITY_AGE") != nullptr
? std::atoi(std::getenv("D4R_HARNESS_QUALITY_AGE")) : 1;
const int shownFrame = frame - presentedAge;
std::vector<float> error(pixels);
double squared = 0.0, blindsSquared = 0.0;
size_t blindsPixels = 0;
for (size_t pixel = 0; pixel < pixels; ++pixel)
{
float value = 0.0f;
for (int channel = 0; channel < 3; ++channel)
value += static_cast<float>(std::bit_cast<_Float16>(halves[pixel * 4 + channel]));
const float luma = std::clamp(value / 3.0f, 0.0f, 1.0f);
// Content coordinates: the screen pixel minus the scroll.
const int cx = static_cast<int>(pixel % outWidth) - shownFrame * panX;
const int cy = static_cast<int>(pixel / outWidth) - shownFrame * panY;
const float target = truth[static_cast<size_t>(cy - canvasY0) * canvasWidth + (cx - canvasX0)];
error[pixel] = luma - target;
squared += static_cast<double>(error[pixel]) * error[pixel];
const float u = static_cast<float>(cx) / outWidth;
const float v = static_cast<float>(cy) / outHeight;
if (u > 0.05f && u < 0.45f && v > 0.1f && v < 0.9f)
{
blindsSquared += static_cast<double>(error[pixel]) * error[pixel];
++blindsPixels;
}
}
double change = 0.0;
if (!previousError.empty())
{
for (size_t pixel = 0; pixel < pixels; ++pixel)
change += std::fabs(error[pixel] - previousError[pixel]);
change /= static_cast<double>(pixels);
flickerSum += change;
++flickerFrames;
}
previousError = error;
if (const char* dump = std::getenv("D4R_HARNESS_QUALITY_DUMP"))
{
// Every measured frame's raw RGBA16F output, for crops and videos.
char path[MAX_PATH];
std::snprintf(path, sizeof(path), "%s.%03d", dump, frame);
if (FILE* file = std::fopen(path, "wb"))
{
std::fwrite(raw.data(), 1, raw.size(), file);
std::fclose(file);
}
}
FILE* report = std::fopen(std::getenv("D4R_HARNESS_QUALITY_SCENE"), "a");
if (report == nullptr)
report = stdout;
std::fprintf(report, "QUALITY frame=%d psnr=%.2f blinds_psnr=%.2f flicker=%.5f\n", frame,
10.0 * std::log10(1.0 / (squared / pixels)),
10.0 * std::log10(1.0 / (blindsSquared / blindsPixels)), change);
if (frame == frames)
std::fprintf(report, "QUALITY summary psnr=%.2f blinds_psnr=%.2f mean_flicker=%.5f\n",
10.0 * std::log10(1.0 / (squared / pixels)),
10.0 * std::log10(1.0 / (blindsSquared / blindsPixels)),
flickerFrames != 0 ? flickerSum / flickerFrames : 0.0);
if (report != stdout)
std::fclose(report);
}
if (saveFrames)
{
const std::vector<uint8_t> frameOutput =
read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, outWidth * 8);
const std::string framePath = std::string(argv[2]) + ".frame" + std::to_string(frame);
if (FILE* frameFile = std::fopen(framePath.c_str(), "wb"))
{
std::fwrite(frameOutput.data(), 1, frameOutput.size(), frameFile);
std::fclose(frameFile);
}
}
}
const std::vector<uint8_t> output = read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, outWidth * 8);
FILE* file = std::fopen(argv[2], "wb");
if (file != nullptr)
{
std::fwrite(output.data(), 1, output.size(), file);
std::fclose(file);
}
size_t nonzero = 0;
for (size_t index = 0; index < output.size(); index += 2)
nonzero += (output[index] | output[index + 1]) != 0;
std::printf("output read back: %zu of %zu halves nonzero, written to %s\n", nonzero, output.size() / 2, argv[2]);
release(feature);
shutdown();
return 0;
}
+446
View File
@@ -0,0 +1,446 @@
// Probe for zero-copy D3D12 <-> CUDA/HIP sharing under Proton.
//
// D3D12 shared NT handles are a dead end under Wine (no unix fd behind them;
// vkd3d-proton exports only textures), so this goes through vkd3d-proton's
// Vulkan interop instead:
// - a VkBuffer in device-local memory, allocated on vkd3d-proton's own
// VkDevice with VkExportMemoryAllocateInfo (winevulkan makes it a host
// OPAQUE_FD export),
// - imported into HIP by the d4r nvcuda bridge (d4rImportVulkanMemory),
// - filled from / copied into a D3D12 texture by raw Vulkan copy commands
// recorded inside a D3D12 command list (ID3D12DXVKInteropDevice1::
// BeginVkCommandBufferInterop), which is where the NGX shim records its
// staging copies today.
//
// usage: d3d12_hip_interop_probe.exe NVCUDA_BRIDGE_DLL REPORT_FILE [WIDTH HEIGHT]
// (Proton does not connect the console, so results go to REPORT_FILE.)
#define WIDL_EXPLICIT_AGGREGATE_RETURNS
#include <windows.h>
#include <d3d12.h>
#include <vulkan/vulkan_core.h>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
using CUresult = int;
using CUdeviceptr = unsigned long long;
// vkd3d-proton's interop interface (include/vkd3d_device_vkd3d_ext.idl).
MIDL_INTERFACE("39da4e09-bd1c-4198-9fae-86bbe3be41fd")
ID3D12DXVKInteropDevice : public IUnknown
{
virtual HRESULT STDMETHODCALLTYPE GetDXGIAdapter(REFIID iid, void** object) = 0;
virtual HRESULT STDMETHODCALLTYPE GetInstanceExtensions(UINT* count, const char** extensions) = 0;
virtual HRESULT STDMETHODCALLTYPE GetDeviceExtensions(UINT* count, const char** extensions) = 0;
virtual HRESULT STDMETHODCALLTYPE GetDeviceFeatures(const VkPhysicalDeviceFeatures2** features) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVulkanHandles(VkInstance* instance, VkPhysicalDevice* physical, VkDevice* device) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVulkanQueueInfo(ID3D12CommandQueue* queue, VkQueue* vk_queue, UINT32* family) = 0;
virtual void STDMETHODCALLTYPE GetVulkanImageLayout(ID3D12Resource* resource, D3D12_RESOURCE_STATES state,
VkImageLayout* layout) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVulkanResourceInfo(ID3D12Resource* resource, UINT64* handle, UINT64* offset) = 0;
virtual HRESULT STDMETHODCALLTYPE LockCommandQueue(ID3D12CommandQueue* queue) = 0;
virtual HRESULT STDMETHODCALLTYPE UnlockCommandQueue(ID3D12CommandQueue* queue) = 0;
};
MIDL_INTERFACE("902d8115-59eb-4406-9518-fe00f991ee65")
ID3D12DXVKInteropDevice1 : public ID3D12DXVKInteropDevice
{
virtual HRESULT STDMETHODCALLTYPE GetVulkanResourceInfo1(ID3D12Resource* resource, UINT64* handle, UINT64* offset,
VkFormat* format) = 0;
virtual HRESULT STDMETHODCALLTYPE CreateInteropCommandQueue(const D3D12_COMMAND_QUEUE_DESC* desc, UINT32 family,
ID3D12CommandQueue** queue) = 0;
virtual HRESULT STDMETHODCALLTYPE CreateInteropCommandAllocator(D3D12_COMMAND_LIST_TYPE type, UINT32 family,
ID3D12CommandAllocator** allocator) = 0;
virtual HRESULT STDMETHODCALLTYPE BeginVkCommandBufferInterop(ID3D12CommandList* list, VkCommandBuffer* buffer) = 0;
virtual HRESULT STDMETHODCALLTYPE EndVkCommandBufferInterop(ID3D12CommandList* list) = 0;
};
__CRT_UUID_DECL(ID3D12DXVKInteropDevice1, 0x902d8115, 0x59eb, 0x4406, 0x95, 0x18, 0xfe, 0x00, 0xf9, 0x91, 0xee, 0x65)
static ID3D12Device* g_device;
static ID3D12CommandQueue* g_queue;
static ID3D12CommandAllocator* g_allocator;
static ID3D12GraphicsCommandList* g_list;
static ID3D12Fence* g_fence;
static HANDLE g_event;
static UINT64 g_fenceValue;
static bool check(HRESULT hr, const char* what)
{
if (FAILED(hr))
std::printf("%s failed: 0x%08lx\n", what, hr);
return SUCCEEDED(hr);
}
static bool flush()
{
if (!check(g_list->Close(), "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);
WaitForSingleObject(g_event, INFINITE);
return check(g_allocator->Reset(), "allocator Reset") && check(g_list->Reset(g_allocator, nullptr), "list Reset");
}
static double elapsed_ms(std::chrono::steady_clock::time_point start)
{
return std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
}
static ID3D12Resource* create_buffer(D3D12_HEAP_TYPE type, UINT64 size, D3D12_RESOURCE_STATES state)
{
D3D12_HEAP_PROPERTIES heap = {};
heap.Type = type;
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Width = size;
desc.Height = 1;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
ID3D12Resource* resource = nullptr;
if (!check(g_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, state, nullptr,
__uuidof(ID3D12Resource), reinterpret_cast<void**>(&resource)),
"CreateCommittedResource(buffer)"))
return nullptr;
return resource;
}
static void transition(ID3D12Resource* resource, D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after)
{
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);
}
static uint64_t pattern(uint64_t i)
{
return (i * 0x9E3779B97F4A7C15ull) ^ (i >> 7);
}
int main(int argc, char** argv)
{
if (argc < 3)
{
std::printf("usage: %s NVCUDA_BRIDGE_DLL REPORT_FILE [WIDTH HEIGHT]\n", argv[0]);
return 2;
}
if (std::freopen(argv[2], "w", stdout) == nullptr)
return 2;
std::setvbuf(stdout, nullptr, _IONBF, 0);
const UINT width = argc > 4 ? static_cast<UINT>(std::strtoul(argv[3], nullptr, 0)) : 2560;
const UINT height = argc > 4 ? static_cast<UINT>(std::strtoul(argv[4], nullptr, 0)) : 1440;
const UINT64 texel = 8; // R16G16B16A16_FLOAT, the DLSS output format
const UINT64 pitch = width * texel;
const UINT64 bytes = pitch * height;
const UINT64 words = bytes / 8;
if (pitch % D3D12_TEXTURE_DATA_PITCH_ALIGNMENT != 0)
{
std::printf("width must make a 256-byte aligned row pitch\n");
return 2;
}
if (!check(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_0, __uuidof(ID3D12Device),
reinterpret_cast<void**>(&g_device)),
"D3D12CreateDevice"))
return 1;
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
if (!check(g_device->CreateCommandQueue(&queueDesc, __uuidof(ID3D12CommandQueue), reinterpret_cast<void**>(&g_queue)),
"CreateCommandQueue") ||
!check(g_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, __uuidof(ID3D12CommandAllocator),
reinterpret_cast<void**>(&g_allocator)),
"CreateCommandAllocator") ||
!check(g_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_allocator, nullptr,
__uuidof(ID3D12GraphicsCommandList), reinterpret_cast<void**>(&g_list)),
"CreateCommandList") ||
!check(g_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence), reinterpret_cast<void**>(&g_fence)),
"CreateFence"))
return 1;
g_event = CreateEventA(nullptr, FALSE, FALSE, nullptr);
ID3D12DXVKInteropDevice1* interop = nullptr;
if (!check(g_device->QueryInterface(__uuidof(ID3D12DXVKInteropDevice1), reinterpret_cast<void**>(&interop)),
"QueryInterface(ID3D12DXVKInteropDevice1)"))
return 1;
VkInstance vkInstance = VK_NULL_HANDLE;
VkPhysicalDevice vkPhysical = VK_NULL_HANDLE;
VkDevice vkDevice = VK_NULL_HANDLE;
if (!check(interop->GetVulkanHandles(&vkInstance, &vkPhysical, &vkDevice), "GetVulkanHandles"))
return 1;
UINT extensionCount = 0;
interop->GetDeviceExtensions(&extensionCount, nullptr);
std::vector<const char*> extensions(extensionCount);
interop->GetDeviceExtensions(&extensionCount, extensions.data());
bool win32Memory = false;
for (const char* name : extensions)
win32Memory |= std::strcmp(name, "VK_KHR_external_memory_win32") == 0;
std::printf("vkd3d-proton VkDevice %p, %u extensions, VK_KHR_external_memory_win32 %s\n",
static_cast<void*>(vkDevice), extensionCount, win32Memory ? "enabled" : "MISSING");
HMODULE vulkan = LoadLibraryA("vulkan-1.dll");
auto getInstanceProc = vulkan != nullptr ? reinterpret_cast<PFN_vkGetInstanceProcAddr>(
reinterpret_cast<void*>(GetProcAddress(vulkan, "vkGetInstanceProcAddr")))
: nullptr;
if (getInstanceProc == nullptr)
{
std::printf("vulkan-1.dll unavailable\n");
return 1;
}
auto getDeviceProc = reinterpret_cast<PFN_vkGetDeviceProcAddr>(getInstanceProc(vkInstance, "vkGetDeviceProcAddr"));
auto getMemoryProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceMemoryProperties>(
getInstanceProc(vkInstance, "vkGetPhysicalDeviceMemoryProperties"));
#define DEVICE_PROC(name) auto name = reinterpret_cast<PFN_##name>(getDeviceProc(vkDevice, #name))
DEVICE_PROC(vkCreateBuffer);
DEVICE_PROC(vkGetBufferMemoryRequirements);
DEVICE_PROC(vkAllocateMemory);
DEVICE_PROC(vkBindBufferMemory);
DEVICE_PROC(vkCmdPipelineBarrier);
DEVICE_PROC(vkCmdCopyImageToBuffer);
DEVICE_PROC(vkCmdCopyBufferToImage);
#undef DEVICE_PROC
// Exportable device-local buffer on vkd3d-proton's device.
VkExternalMemoryBufferCreateInfo externalBuffer = {VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO};
externalBuffer.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
VkBufferCreateInfo bufferInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, &externalBuffer};
bufferInfo.size = bytes;
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkBuffer vkBuffer = VK_NULL_HANDLE;
VkResult vr = vkCreateBuffer(vkDevice, &bufferInfo, nullptr, &vkBuffer);
VkMemoryRequirements requirements = {};
vkGetBufferMemoryRequirements(vkDevice, vkBuffer, &requirements);
VkPhysicalDeviceMemoryProperties memoryProperties = {};
getMemoryProperties(vkPhysical, &memoryProperties);
int memoryType = -1;
for (int pass = 0; pass < 2 && memoryType < 0; ++pass)
for (uint32_t i = 0; i < memoryProperties.memoryTypeCount && memoryType < 0; ++i)
{
const VkMemoryPropertyFlags flags = memoryProperties.memoryTypes[i].propertyFlags;
if ((requirements.memoryTypeBits & (1u << i)) && (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) &&
(pass == 1 || !(flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)))
memoryType = static_cast<int>(i);
}
VkMemoryDedicatedAllocateInfo dedicated = {VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO};
dedicated.buffer = vkBuffer;
VkExportMemoryAllocateInfo exportInfo = {VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, &dedicated};
exportInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
VkMemoryAllocateInfo allocateInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &exportInfo};
allocateInfo.allocationSize = requirements.size;
allocateInfo.memoryTypeIndex = static_cast<uint32_t>(memoryType);
VkDeviceMemory vkMemory = VK_NULL_HANDLE;
if (vr == VK_SUCCESS && memoryType >= 0)
vr = vkAllocateMemory(vkDevice, &allocateInfo, nullptr, &vkMemory);
if (vr == VK_SUCCESS)
vr = vkBindBufferMemory(vkDevice, vkBuffer, vkMemory, 0);
std::printf("exportable VkBuffer: %llu bytes (allocation %llu, memory type %d): VkResult %d\n",
static_cast<unsigned long long>(bytes), static_cast<unsigned long long>(requirements.size), memoryType,
vr);
if (vr != VK_SUCCESS || memoryType < 0)
return 1;
HMODULE bridge = LoadLibraryA(argv[1]);
if (bridge == nullptr)
{
std::printf("LoadLibrary(%s) failed: %lu\n", argv[1], GetLastError());
return 1;
}
using ImportFn = CUresult(WINAPI*)(void*, uint64_t, uint64_t, CUdeviceptr*, void**);
using DtoHFn = CUresult(WINAPI*)(void*, CUdeviceptr, size_t);
using MemsetFn = CUresult(WINAPI*)(CUdeviceptr, unsigned int, size_t);
using SyncFn = CUresult(WINAPI*)();
using AllocFn = CUresult(WINAPI*)(CUdeviceptr*, size_t);
using DtoDFn = CUresult(WINAPI*)(CUdeviceptr, CUdeviceptr, size_t);
auto import = reinterpret_cast<ImportFn>(reinterpret_cast<void*>(GetProcAddress(bridge, "d4rImportVulkanMemory")));
auto dtoh = reinterpret_cast<DtoHFn>(reinterpret_cast<void*>(GetProcAddress(bridge, "cuMemcpyDtoH")));
auto memset32 = reinterpret_cast<MemsetFn>(reinterpret_cast<void*>(GetProcAddress(bridge, "cuMemsetD32")));
auto synchronize = reinterpret_cast<SyncFn>(reinterpret_cast<void*>(GetProcAddress(bridge, "cuCtxSynchronize")));
auto alloc = reinterpret_cast<AllocFn>(reinterpret_cast<void*>(GetProcAddress(bridge, "cuMemAlloc")));
auto dtod = reinterpret_cast<DtoDFn>(reinterpret_cast<void*>(GetProcAddress(bridge, "cuMemcpyDtoD")));
if (import == nullptr || dtoh == nullptr || memset32 == nullptr || synchronize == nullptr || alloc == nullptr ||
dtod == nullptr)
{
std::printf("bridge exports missing\n");
return 1;
}
CUdeviceptr device = 0;
void* external = nullptr;
CUresult result = import(vkDevice, reinterpret_cast<uint64_t>(vkMemory), requirements.size, &device, &external);
std::printf("d4rImportVulkanMemory -> %d, device 0x%llx\n", result, device);
if (result != 0)
return 1;
// A game-like R16G16B16A16_FLOAT texture, filled through an upload buffer.
D3D12_HEAP_PROPERTIES defaultHeap = {};
defaultHeap.Type = D3D12_HEAP_TYPE_DEFAULT;
D3D12_RESOURCE_DESC textureDesc = {};
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
textureDesc.Width = width;
textureDesc.Height = height;
textureDesc.DepthOrArraySize = 1;
textureDesc.MipLevels = 1;
textureDesc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT;
textureDesc.SampleDesc.Count = 1;
textureDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
ID3D12Resource* texture = nullptr;
ID3D12Resource* upload = create_buffer(D3D12_HEAP_TYPE_UPLOAD, bytes, D3D12_RESOURCE_STATE_GENERIC_READ);
ID3D12Resource* readback = create_buffer(D3D12_HEAP_TYPE_READBACK, bytes, D3D12_RESOURCE_STATE_COPY_DEST);
if (!check(g_device->CreateCommittedResource(&defaultHeap, D3D12_HEAP_FLAG_NONE, &textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST, nullptr, __uuidof(ID3D12Resource),
reinterpret_cast<void**>(&texture)),
"CreateCommittedResource(texture)") ||
upload == nullptr || readback == nullptr)
return 1;
UINT64 vkImageHandle = 0, unusedOffset = 0;
interop->GetVulkanResourceInfo(texture, &vkImageHandle, &unusedOffset);
const VkImage vkImage = reinterpret_cast<VkImage>(vkImageHandle);
uint64_t* mapped = nullptr;
D3D12_RANGE none = {0, 0};
if (!check(upload->Map(0, &none, reinterpret_cast<void**>(&mapped)), "Map upload"))
return 1;
for (UINT64 i = 0; i < words; ++i)
mapped[i] = pattern(i) & 0x3BFF3BFF3BFF3BFFull; // finite halves: copies must not canonicalize NaNs
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {};
footprint.Footprint.Format = textureDesc.Format;
footprint.Footprint.Width = width;
footprint.Footprint.Height = height;
footprint.Footprint.Depth = 1;
footprint.Footprint.RowPitch = static_cast<UINT>(pitch);
D3D12_TEXTURE_COPY_LOCATION textureLocation = {};
textureLocation.pResource = texture;
textureLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
D3D12_TEXTURE_COPY_LOCATION uploadLocation = {};
uploadLocation.pResource = upload;
uploadLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
uploadLocation.PlacedFootprint = footprint;
D3D12_TEXTURE_COPY_LOCATION readbackLocation = uploadLocation;
readbackLocation.pResource = readback;
g_list->CopyTextureRegion(&textureLocation, 0, 0, 0, &uploadLocation, nullptr);
transition(texture, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COPY_SOURCE);
if (!flush())
return 1;
VkBufferImageCopy region = {};
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
region.imageExtent = {width, height, 1};
VkMemoryBarrier toTransfer = {VK_STRUCTURE_TYPE_MEMORY_BARRIER};
toTransfer.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
toTransfer.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
VkMemoryBarrier fromTransfer = {VK_STRUCTURE_TYPE_MEMORY_BARRIER};
fromTransfer.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
fromTransfer.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
// Texture -> exportable buffer, recorded inside the D3D12 command list.
auto recordTextureToBuffer = [&]() {
VkCommandBuffer cmd = VK_NULL_HANDLE;
if (!check(interop->BeginVkCommandBufferInterop(g_list, &cmd), "BeginVkCommandBufferInterop"))
return false;
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
interop->GetVulkanImageLayout(texture, D3D12_RESOURCE_STATE_COPY_SOURCE, &layout);
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &toTransfer,
0, nullptr, 0, nullptr);
vkCmdCopyImageToBuffer(cmd, vkImage, layout, vkBuffer, 1, &region);
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1,
&fromTransfer, 0, nullptr, 0, nullptr);
return check(interop->EndVkCommandBufferInterop(g_list), "EndVkCommandBufferInterop");
};
if (!recordTextureToBuffer() || !flush())
return 1;
std::vector<uint64_t> host(words);
result = dtoh(host.data(), device, bytes);
size_t mismatches = 0;
for (UINT64 i = 0; i < words; ++i)
mismatches += host[i] != (pattern(i) & 0x3BFF3BFF3BFF3BFFull);
std::printf("D3D12 texture -> Vulkan buffer -> CUDA: result %d, %zu of %llu texels mismatched\n", result,
mismatches, static_cast<unsigned long long>(words));
// CUDA write -> buffer -> D3D12 texture (the DLSS output direction).
result = memset32(device, 0x3C003800u, bytes / 4);
if (result == 0)
result = synchronize();
transition(texture, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COPY_DEST);
auto recordBufferToTexture = [&]() {
VkCommandBuffer cmd = VK_NULL_HANDLE;
if (!check(interop->BeginVkCommandBufferInterop(g_list, &cmd), "BeginVkCommandBufferInterop"))
return false;
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
interop->GetVulkanImageLayout(texture, D3D12_RESOURCE_STATE_COPY_DEST, &layout);
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &toTransfer,
0, nullptr, 0, nullptr);
vkCmdCopyBufferToImage(cmd, vkBuffer, vkImage, layout, 1, &region);
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1,
&fromTransfer, 0, nullptr, 0, nullptr);
return check(interop->EndVkCommandBufferInterop(g_list), "EndVkCommandBufferInterop");
};
if (!recordBufferToTexture())
return 1;
transition(texture, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COPY_SOURCE);
g_list->CopyTextureRegion(&readbackLocation, 0, 0, 0, &textureLocation, nullptr);
if (!flush())
return 1;
uint64_t* back = nullptr;
D3D12_RANGE all = {0, static_cast<SIZE_T>(bytes)};
readback->Map(0, &all, reinterpret_cast<void**>(&back));
size_t reverse = 0;
for (UINT64 i = 0; i < words; ++i)
reverse += back[i] != 0x3C0038003C003800ull;
readback->Unmap(0, nullptr);
std::printf("CUDA -> Vulkan buffer -> D3D12 texture: result %d, %zu of %llu texels mismatched\n", result, reverse,
static_cast<unsigned long long>(words));
// Timings: interop copies vs today's texture -> readback copy, and a CUDA
// device copy out of the imported buffer.
double toBuffer = 1e9, toTexture = 1e9, toReadback = 1e9, cudaCopy = 1e9;
for (int i = 0; i < 10; ++i)
{
auto start = std::chrono::steady_clock::now();
recordTextureToBuffer();
flush();
toBuffer = std::min(toBuffer, elapsed_ms(start));
transition(texture, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COPY_DEST);
flush();
start = std::chrono::steady_clock::now();
recordBufferToTexture();
flush();
toTexture = std::min(toTexture, elapsed_ms(start));
transition(texture, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COPY_SOURCE);
flush();
start = std::chrono::steady_clock::now();
g_list->CopyTextureRegion(&readbackLocation, 0, 0, 0, &textureLocation, nullptr);
flush();
toReadback = std::min(toReadback, elapsed_ms(start));
}
CUdeviceptr local = 0;
if (alloc(&local, bytes) == 0)
for (int i = 0; i < 10; ++i)
{
synchronize();
const auto start = std::chrono::steady_clock::now();
dtod(local, device, bytes);
synchronize();
cudaCopy = std::min(cudaCopy, elapsed_ms(start));
}
std::printf("%ux%u RGBA16F (%.1f MB), best of 10 incl. submit+wait:\n", width, height, bytes / 1048576.0);
std::printf(" interop texture -> VRAM buffer: %.3f ms\n", toBuffer);
std::printf(" interop VRAM buffer -> texture: %.3f ms\n", toTexture);
std::printf(" D3D12 texture -> readback (today): %.3f ms\n", toReadback);
std::printf(" CUDA device copy from imported: %.3f ms\n", cudaCopy);
const bool ok = mismatches == 0 && reverse == 0;
std::printf("zero-copy interop probe: %s\n", ok ? "PASS" : "FAIL");
return ok ? 0 : 1;
}
+18
View File
@@ -0,0 +1,18 @@
// MinGW-side support for d4r_ngx_param_msvc.cpp: allocation hooks for the
// parameter objects and the MSVC floating-point marker symbol that clang-cl
// objects reference.
#include <cstdlib>
extern "C" {
int _fltused = 0;
}
extern "C" void* d4r_host_alloc(unsigned long long size)
{
return std::calloc(1, static_cast<size_t>(size));
}
extern "C" void d4r_host_free(void* pointer)
{
std::free(pointer);
}
+191
View File
@@ -0,0 +1,191 @@
// Microsoft-ABI access to NVSDK_NGX_Parameter objects for MinGW-built code.
// NVSDK_NGX_Parameter is a C++ interface with overloaded virtual Set/Get
// methods; MSVC orders overloaded virtuals differently from the Itanium ABI,
// so calls must be compiled with clang-cl. Build freestanding (/Zl /GS-).
using D4rNgxResult = unsigned int;
struct ID3D11Resource;
struct ID3D12Resource;
struct D4rNgxParameterAbi
{
virtual void Set(const char* name, unsigned long long value) = 0;
virtual void Set(const char* name, float value) = 0;
virtual void Set(const char* name, double value) = 0;
virtual void Set(const char* name, unsigned int value) = 0;
virtual void Set(const char* name, int value) = 0;
virtual void Set(const char* name, ID3D11Resource* value) = 0;
virtual void Set(const char* name, ID3D12Resource* value) = 0;
virtual void Set(const char* name, void* value) = 0;
virtual D4rNgxResult Get(const char* name, unsigned long long* value) const = 0;
virtual D4rNgxResult Get(const char* name, float* value) const = 0;
virtual D4rNgxResult Get(const char* name, double* value) const = 0;
virtual D4rNgxResult Get(const char* name, unsigned int* value) const = 0;
virtual D4rNgxResult Get(const char* name, int* value) const = 0;
virtual D4rNgxResult Get(const char* name, ID3D11Resource** value) const = 0;
virtual D4rNgxResult Get(const char* name, ID3D12Resource** value) const = 0;
virtual D4rNgxResult Get(const char* name, void** value) const = 0;
virtual void Reset() = 0;
};
static D4rNgxParameterAbi* parameters(void* raw)
{
return static_cast<D4rNgxParameterAbi*>(raw);
}
extern "C" void d4r_ngx_set_ull(void* raw, const char* name, unsigned long long value) { parameters(raw)->Set(name, value); }
extern "C" void d4r_ngx_set_float(void* raw, const char* name, float value) { parameters(raw)->Set(name, value); }
extern "C" void d4r_ngx_set_uint(void* raw, const char* name, unsigned int value) { parameters(raw)->Set(name, value); }
extern "C" void d4r_ngx_set_int(void* raw, const char* name, int value) { parameters(raw)->Set(name, value); }
extern "C" void d4r_ngx_set_void(void* raw, const char* name, void* value) { parameters(raw)->Set(name, value); }
extern "C" D4rNgxResult d4r_ngx_get_ull(void* raw, const char* name, unsigned long long* value) { return parameters(raw)->Get(name, value); }
extern "C" D4rNgxResult d4r_ngx_get_float(void* raw, const char* name, float* value) { return parameters(raw)->Get(name, value); }
extern "C" D4rNgxResult d4r_ngx_get_uint(void* raw, const char* name, unsigned int* value) { return parameters(raw)->Get(name, value); }
extern "C" D4rNgxResult d4r_ngx_get_int(void* raw, const char* name, int* value) { return parameters(raw)->Get(name, value); }
extern "C" D4rNgxResult d4r_ngx_get_void(void* raw, const char* name, void** value) { return parameters(raw)->Get(name, value); }
extern "C" D4rNgxResult d4r_ngx_get_d3d12_resource(void* raw, const char* name, ID3D12Resource** value) { return parameters(raw)->Get(name, value); }
extern "C" void d4r_ngx_set_d3d12_resource(void* raw, const char* name, ID3D12Resource* value) { parameters(raw)->Set(name, value); }
// A complete NVSDK_NGX_Parameter implementation handed out by the shim's
// Allocate/Get/GetCapabilityParameters. It stores D3D12 resources (which the
// CUDA-path parameter objects of the official core do not) and converts
// between numeric types on Get, like the official implementations.
inline void* operator new(decltype(sizeof(0)), void* place) noexcept { return place; }
extern "C" void* d4r_host_alloc(unsigned long long size); // zero-filled, provided by the shim
extern "C" void d4r_host_free(void* pointer);
namespace
{
constexpr D4rNgxResult kSuccess = 0x1;
constexpr D4rNgxResult kUnsupportedParameter = 0xBAD00010;
enum ValueType : unsigned
{
TypeULL = 1,
TypeFloat,
TypeDouble,
TypeUInt,
TypeInt,
TypePointer
};
struct Entry
{
char name[96];
unsigned type;
union
{
unsigned long long u;
long long i;
double d;
void* p;
} value;
};
bool same_name(const char* a, const char* b)
{
for (; *a != 0 && *a == *b; ++a, ++b)
{
}
return *a == *b;
}
class Parameters final : public D4rNgxParameterAbi
{
public:
void Set(const char* name, unsigned long long value) override { store(name, TypeULL)->value.u = value; }
void Set(const char* name, float value) override { store(name, TypeFloat)->value.d = value; }
void Set(const char* name, double value) override { store(name, TypeDouble)->value.d = value; }
void Set(const char* name, unsigned int value) override { store(name, TypeUInt)->value.u = value; }
void Set(const char* name, int value) override { store(name, TypeInt)->value.i = value; }
void Set(const char* name, ID3D11Resource* value) override { store(name, TypePointer)->value.p = value; }
void Set(const char* name, ID3D12Resource* value) override { store(name, TypePointer)->value.p = value; }
void Set(const char* name, void* value) override { store(name, TypePointer)->value.p = value; }
D4rNgxResult Get(const char* name, unsigned long long* value) const override { return number(name, value); }
D4rNgxResult Get(const char* name, float* value) const override { return number(name, value); }
D4rNgxResult Get(const char* name, double* value) const override { return number(name, value); }
D4rNgxResult Get(const char* name, unsigned int* value) const override { return number(name, value); }
D4rNgxResult Get(const char* name, int* value) const override { return number(name, value); }
D4rNgxResult Get(const char* name, ID3D11Resource** value) const override { return pointer(name, value); }
D4rNgxResult Get(const char* name, ID3D12Resource** value) const override { return pointer(name, value); }
D4rNgxResult Get(const char* name, void** value) const override { return pointer(name, value); }
void Reset() override { count_ = 0; }
private:
const Entry* find(const char* name) const
{
for (unsigned index = 0; index < count_; ++index)
if (same_name(entries_[index].name, name))
return &entries_[index];
return nullptr;
}
Entry* store(const char* name, unsigned type)
{
Entry* entry = const_cast<Entry*>(find(name));
if (entry == nullptr)
{
// Past capacity the last slot is reused rather than failing.
entry = &entries_[count_ < kCapacity ? count_++ : kCapacity - 1];
unsigned length = 0;
for (; name[length] != 0 && length + 1 < sizeof(entry->name); ++length)
entry->name[length] = name[length];
entry->name[length] = 0;
}
entry->type = type;
entry->value.u = 0;
return entry;
}
template <typename T> D4rNgxResult number(const char* name, T* value) const
{
const Entry* entry = find(name);
if (entry == nullptr || value == nullptr)
return kUnsupportedParameter;
switch (entry->type)
{
case TypeFloat:
case TypeDouble:
*value = static_cast<T>(entry->value.d);
break;
case TypeInt:
*value = static_cast<T>(entry->value.i);
break;
case TypePointer:
*value = static_cast<T>(reinterpret_cast<unsigned long long>(entry->value.p));
break;
default:
*value = static_cast<T>(entry->value.u);
break;
}
return kSuccess;
}
template <typename T> D4rNgxResult pointer(const char* name, T** value) const
{
const Entry* entry = find(name);
if (entry == nullptr || value == nullptr)
return kUnsupportedParameter;
*value = entry->type == TypePointer ? static_cast<T*>(entry->value.p)
: reinterpret_cast<T*>(entry->value.u);
return kSuccess;
}
static constexpr unsigned kCapacity = 256;
unsigned count_ = 0;
Entry entries_[kCapacity];
};
} // namespace
extern "C" void* d4r_ngx_parameters_create()
{
void* memory = d4r_host_alloc(sizeof(Parameters));
return memory != nullptr ? static_cast<D4rNgxParameterAbi*>(new (memory) Parameters()) : nullptr;
}
extern "C" void d4r_ngx_parameters_destroy(void* parameters)
{
d4r_host_free(parameters);
}
+81
View File
@@ -0,0 +1,81 @@
// Opt-in NVAPI identity bridge for OptiScaler's NVIDIA-only DLSS backend.
// All NVAPI interfaces except the version string are forwarded to the
// prefix-local DXVK-NVAPI DLL. The underlying GPU and DLSS execution remain
// AMD/ZLUDA; this only lets OptiScaler select its official-DLSS route.
#include <windows.h>
#include <string.h>
#define NVAPI_GET_INTERFACE_VERSION_STRING 0x01053fa5u
#define NVAPI_INITIALIZE 0x0150e828u
#define NVAPI_UNLOAD 0xd22bdd7eu
typedef void *(__cdecl *NvapiQueryInterface)(unsigned int);
static NvapiQueryInterface real_query;
static volatile LONG spoof_probe_armed;
static volatile LONG spoof_probe_used;
static volatile LONG spoof_unload_armed;
static NvapiQueryInterface load_real_query(void)
{
if (real_query != NULL)
return real_query;
wchar_t path[MAX_PATH];
UINT length = GetSystemDirectoryW(path, MAX_PATH);
static const wchar_t suffix[] = L"\\d4r_nvapi64_real.dll";
if (length == 0 || length + (sizeof(suffix) / sizeof(suffix[0])) > MAX_PATH)
return NULL;
memcpy(path + length, suffix, sizeof(suffix));
HMODULE module = LoadLibraryW(path);
if (module == NULL)
return NULL;
FARPROC symbol = GetProcAddress(module, "nvapi_QueryInterface");
memcpy(&real_query, &symbol, sizeof(real_query));
return real_query;
}
static int __cdecl d4r_get_interface_version_string(char *description)
{
if (description == NULL)
return -5; // NVAPI_INVALID_ARGUMENT
// OptiScaler takes the real-NVIDIA branch for any non-DXVK version string.
// NVAPI's short string has 64 bytes.
static const char identity[] = "NVIDIA NVAPI (d4r compatibility)";
memcpy(description, identity, sizeof(identity));
if (InterlockedCompareExchange(&spoof_probe_used, 0, 0) == 0)
InterlockedExchange(&spoof_probe_armed, 1);
return 0; // NVAPI_OK
}
static int __cdecl d4r_initialize_identity_probe(void)
{
// OptiScaler calls this inside DLL_PROCESS_ATTACH when it believes it is
// loading NVIDIA's own NVAPI. DXVK-NVAPI initializes DXGI here, which is
// unsafe under the loader lock. The later, ordinary init calls are passed
// through to DXVK-NVAPI.
InterlockedExchange(&spoof_probe_used, 1);
InterlockedExchange(&spoof_unload_armed, 1);
return 0;
}
static int __cdecl d4r_unload_identity_probe(void)
{
return 0;
}
__declspec(dllexport) void *__cdecl nvapi_QueryInterface(unsigned int interface_id)
{
NvapiQueryInterface query = load_real_query();
if (query == NULL)
return NULL;
if (interface_id == NVAPI_GET_INTERFACE_VERSION_STRING)
return (void *)&d4r_get_interface_version_string;
if (interface_id == NVAPI_INITIALIZE &&
InterlockedCompareExchange(&spoof_probe_armed, 0, 1) == 1)
return (void *)&d4r_initialize_identity_probe;
if (interface_id == NVAPI_UNLOAD &&
InterlockedCompareExchange(&spoof_unload_armed, 0, 1) == 1)
return (void *)&d4r_unload_identity_probe;
return query(interface_id);
}
File diff suppressed because it is too large. Load diff
+319
View File
@@ -0,0 +1,319 @@
#include <hip/hip_runtime_api.h>
#include <vulkan/vulkan.h>
#include <algorithm>
#include <array>
#include <cstdio>
#include <cstring>
#include <dlfcn.h>
#include <vector>
template <typename T> static T vk_proc(VkDevice device, const char* name)
{
return reinterpret_cast<T>(vkGetDeviceProcAddr(device, name));
}
static void print_hip(const char* stage, hipError_t result)
{
std::printf("%s: %s (%d)\n", stage, hipGetErrorName(result), static_cast<int>(result));
}
static uint32_t choose_memory_type(VkPhysicalDevice physicalDevice, uint32_t typeBits)
{
VkPhysicalDeviceMemoryProperties properties{};
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &properties);
for (uint32_t i = 0; i < properties.memoryTypeCount; ++i)
{
if ((typeBits & (1u << i)) != 0 &&
(properties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0)
return i;
}
for (uint32_t i = 0; i < properties.memoryTypeCount; ++i)
{
if ((typeBits & (1u << i)) != 0)
return i;
}
return UINT32_MAX;
}
int main()
{
constexpr VkDeviceSize testSize = 4096;
constexpr unsigned char testByte = 0x5a;
hipError_t hipResult = hipInit(0);
print_hip("hipInit", hipResult);
if (hipResult != hipSuccess)
return 1;
int hipDeviceCount = 0;
hipResult = hipGetDeviceCount(&hipDeviceCount);
print_hip("hipGetDeviceCount", hipResult);
if (hipResult != hipSuccess || hipDeviceCount < 1)
{
std::fprintf(stderr, "No HIP device is available\n");
return 1;
}
int hipDevice = 0;
hipResult = hipSetDevice(hipDevice);
print_hip("hipSetDevice(0)", hipResult);
if (hipResult != hipSuccess)
return 1;
hipDeviceProp_t hipProperties{};
hipResult = hipGetDeviceProperties(&hipProperties, hipDevice);
print_hip("hipGetDeviceProperties", hipResult);
if (hipResult != hipSuccess)
return 1;
std::printf("HIP device: %s (%s)\n", hipProperties.name, hipProperties.gcnArchName);
VkApplicationInfo application{};
application.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
application.pApplicationName = "d4r HIP/Vulkan external-memory probe";
application.apiVersion = VK_API_VERSION_1_2;
VkInstanceCreateInfo instanceInfo{};
instanceInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceInfo.pApplicationInfo = &application;
VkInstance instance = VK_NULL_HANDLE;
VkResult vkResult = vkCreateInstance(&instanceInfo, nullptr, &instance);
std::printf("vkCreateInstance: %d\n", static_cast<int>(vkResult));
if (vkResult != VK_SUCCESS)
return 1;
uint32_t physicalDeviceCount = 0;
vkResult = vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, nullptr);
std::vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount);
if (vkResult == VK_SUCCESS && physicalDeviceCount > 0)
vkResult = vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, physicalDevices.data());
if (vkResult != VK_SUCCESS)
{
std::fprintf(stderr, "vkEnumeratePhysicalDevices failed: %d\n", static_cast<int>(vkResult));
vkDestroyInstance(instance, nullptr);
return 1;
}
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
VkPhysicalDeviceProperties physicalProperties{};
for (VkPhysicalDevice candidate : physicalDevices)
{
VkPhysicalDeviceProperties properties{};
vkGetPhysicalDeviceProperties(candidate, &properties);
std::printf("Vulkan device: vendor=0x%04x device=0x%04x name=%s\n", properties.vendorID,
properties.deviceID, properties.deviceName);
if (properties.vendorID == 0x1002 && physicalDevice == VK_NULL_HANDLE)
{
physicalDevice = candidate;
physicalProperties = properties;
}
}
if (physicalDevice == VK_NULL_HANDLE)
{
std::fprintf(stderr, "No AMD Vulkan device was found\n");
vkDestroyInstance(instance, nullptr);
return 1;
}
std::printf("Target: HIP=%s, Vulkan=%s (device 0x%04x)\n", hipProperties.name,
physicalProperties.deviceName, physicalProperties.deviceID);
VkPhysicalDeviceExternalBufferInfo externalBufferInfo{};
externalBufferInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO;
externalBufferInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT;
externalBufferInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
VkExternalBufferProperties externalBufferProperties{};
externalBufferProperties.sType = VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES;
vkGetPhysicalDeviceExternalBufferProperties(physicalDevice, &externalBufferInfo, &externalBufferProperties);
const VkExternalMemoryProperties& externalMemory = externalBufferProperties.externalMemoryProperties;
std::printf("RADV OPAQUE_FD buffer: features=0x%x compatible=0x%x\n",
externalMemory.externalMemoryFeatures, externalMemory.compatibleHandleTypes);
if ((externalMemory.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT) == 0 ||
(externalMemory.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT) == 0)
{
std::fprintf(stderr, "RADV does not support importing and exporting OPAQUE_FD buffers\n");
vkDestroyInstance(instance, nullptr);
return 1;
}
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies.data());
uint32_t queueFamilyIndex = UINT32_MAX;
for (uint32_t i = 0; i < queueFamilyCount; ++i)
{
if ((queueFamilies[i].queueFlags & VK_QUEUE_COMPUTE_BIT) != 0 && queueFamilies[i].queueCount > 0)
{
queueFamilyIndex = i;
break;
}
}
if (queueFamilyIndex == UINT32_MAX)
{
std::fprintf(stderr, "No Vulkan compute queue is available\n");
vkDestroyInstance(instance, nullptr);
return 1;
}
const float queuePriority = 1.0f;
VkDeviceQueueCreateInfo queueInfo{};
queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueInfo.queueFamilyIndex = queueFamilyIndex;
queueInfo.queueCount = 1;
queueInfo.pQueuePriorities = &queuePriority;
const char* deviceExtensions[] = {VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME};
VkDeviceCreateInfo deviceInfo{};
deviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
deviceInfo.queueCreateInfoCount = 1;
deviceInfo.pQueueCreateInfos = &queueInfo;
deviceInfo.enabledExtensionCount = 1;
deviceInfo.ppEnabledExtensionNames = deviceExtensions;
VkDevice device = VK_NULL_HANDLE;
vkResult = vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device);
std::printf("vkCreateDevice: %d\n", static_cast<int>(vkResult));
if (vkResult != VK_SUCCESS)
{
vkDestroyInstance(instance, nullptr);
return 1;
}
auto getMemoryFd = vk_proc<PFN_vkGetMemoryFdKHR>(device, "vkGetMemoryFdKHR");
if (getMemoryFd == nullptr)
{
std::fprintf(stderr, "vkGetMemoryFdKHR is unavailable\n");
vkDestroyDevice(device, nullptr);
vkDestroyInstance(instance, nullptr);
return 1;
}
VkExternalMemoryBufferCreateInfo externalCreateInfo{};
externalCreateInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO;
externalCreateInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.pNext = &externalCreateInfo;
bufferInfo.size = testSize;
bufferInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkBuffer buffer = VK_NULL_HANDLE;
vkResult = vkCreateBuffer(device, &bufferInfo, nullptr, &buffer);
std::printf("vkCreateBuffer(exportable): %d\n", static_cast<int>(vkResult));
if (vkResult != VK_SUCCESS)
{
vkDestroyDevice(device, nullptr);
vkDestroyInstance(instance, nullptr);
return 1;
}
VkMemoryRequirements memoryRequirements{};
vkGetBufferMemoryRequirements(device, buffer, &memoryRequirements);
const uint32_t memoryTypeIndex = choose_memory_type(physicalDevice, memoryRequirements.memoryTypeBits);
if (memoryTypeIndex == UINT32_MAX)
{
std::fprintf(stderr, "No compatible Vulkan memory type was found\n");
vkDestroyBuffer(device, buffer, nullptr);
vkDestroyDevice(device, nullptr);
vkDestroyInstance(instance, nullptr);
return 1;
}
VkExportMemoryAllocateInfo exportInfo{};
exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
exportInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
VkMemoryAllocateInfo allocationInfo{};
allocationInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocationInfo.pNext = &exportInfo;
allocationInfo.allocationSize = memoryRequirements.size;
allocationInfo.memoryTypeIndex = memoryTypeIndex;
VkDeviceMemory memory = VK_NULL_HANDLE;
vkResult = vkAllocateMemory(device, &allocationInfo, nullptr, &memory);
std::printf("vkAllocateMemory(exportable): %d size=%llu\n", static_cast<int>(vkResult),
static_cast<unsigned long long>(allocationInfo.allocationSize));
if (vkResult == VK_SUCCESS)
vkResult = vkBindBufferMemory(device, buffer, memory, 0);
if (vkResult != VK_SUCCESS)
{
std::fprintf(stderr, "Vulkan memory allocation/bind failed: %d\n", static_cast<int>(vkResult));
if (memory != VK_NULL_HANDLE)
vkFreeMemory(device, memory, nullptr);
vkDestroyBuffer(device, buffer, nullptr);
vkDestroyDevice(device, nullptr);
vkDestroyInstance(instance, nullptr);
return 1;
}
VkMemoryGetFdInfoKHR fdInfo{};
fdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
fdInfo.memory = memory;
fdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
int fd = -1;
vkResult = getMemoryFd(device, &fdInfo, &fd);
std::printf("vkGetMemoryFdKHR: %d fd=%d\n", static_cast<int>(vkResult), fd);
if (vkResult != VK_SUCCESS)
{
vkFreeMemory(device, memory, nullptr);
vkDestroyBuffer(device, buffer, nullptr);
vkDestroyDevice(device, nullptr);
vkDestroyInstance(instance, nullptr);
return 1;
}
hipExternalMemoryHandleDesc hipHandle{};
hipHandle.type = hipExternalMemoryHandleTypeOpaqueFd;
hipHandle.handle.fd = fd;
hipHandle.size = allocationInfo.allocationSize;
hipExternalMemory_t hipMemory = nullptr;
hipResult = hipImportExternalMemory(&hipMemory, &hipHandle);
print_hip("hipImportExternalMemory(Vulkan OPAQUE_FD)", hipResult);
if (hipResult != hipSuccess)
{
std::fprintf(stderr, "HIP could not import RADV's exported Vulkan memory\n");
vkFreeMemory(device, memory, nullptr);
vkDestroyBuffer(device, buffer, nullptr);
vkDestroyDevice(device, nullptr);
vkDestroyInstance(instance, nullptr);
return 1;
}
hipExternalMemoryBufferDesc mapInfo{};
mapInfo.size = testSize;
void* hipPointer = nullptr;
hipResult = hipExternalMemoryGetMappedBuffer(&hipPointer, hipMemory, &mapInfo);
print_hip("hipExternalMemoryGetMappedBuffer", hipResult);
if (hipResult == hipSuccess)
{
hipResult = hipMemset(hipPointer, testByte, testSize);
print_hip("hipMemset(imported Vulkan buffer)", hipResult);
}
if (hipResult == hipSuccess)
hipResult = hipDeviceSynchronize();
print_hip("hipDeviceSynchronize", hipResult);
std::array<unsigned char, 64> readback{};
if (hipResult == hipSuccess)
{
hipResult = hipMemcpy(readback.data(), hipPointer, readback.size(), hipMemcpyDeviceToHost);
print_hip("hipMemcpy(imported buffer to host)", hipResult);
}
const bool patternMatches = hipResult == hipSuccess &&
std::all_of(readback.begin(), readback.end(), [](unsigned char value) { return value == testByte; });
std::printf("HIP readback pattern: %s\n", patternMatches ? "PASS" : "FAIL");
const bool hipImageMapAvailable = dlsym(RTLD_DEFAULT, "hipExternalMemoryGetMappedMipmappedArray") != nullptr;
const bool hipSemaphoreImportAvailable = dlsym(RTLD_DEFAULT, "hipImportExternalSemaphore") != nullptr;
std::printf("HIP external-image mapping export: %s\n", hipImageMapAvailable ? "present" : "absent");
std::printf("HIP external-semaphore import export: %s\n", hipSemaphoreImportAvailable ? "present" : "absent");
hipError_t destroyResult = hipDestroyExternalMemory(hipMemory);
print_hip("hipDestroyExternalMemory(buffer)", destroyResult);
vkFreeMemory(device, memory, nullptr);
vkDestroyBuffer(device, buffer, nullptr);
vkDestroyDevice(device, nullptr);
vkDestroyInstance(instance, nullptr);
return patternMatches ? 0 : 1;
}
+505
View File
@@ -0,0 +1,505 @@
#!/usr/bin/env python3
"""Read-only PE inventory for locally supplied NGX/DLSS DLLs.
This reports metadata, imports/exports, resource metadata, section sizes/entropy,
and targeted strings. It never writes or extracts DLL payloads.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import math
import re
import struct
import sys
from collections import Counter
from pathlib import Path
from typing import Any
class PEError(ValueError):
pass
def unpack(fmt: str, data: bytes, offset: int) -> tuple[Any, ...]:
size = struct.calcsize(fmt)
if offset < 0 or offset + size > len(data):
raise PEError(f"truncated PE data at file offset 0x{offset:x}")
return struct.unpack_from(fmt, data, offset)
def c_string(data: bytes, offset: int, limit: int = 4096) -> str:
if offset < 0 or offset >= len(data):
return ""
end = data.find(b"\0", offset, min(len(data), offset + limit))
if end < 0:
end = min(len(data), offset + limit)
return data[offset:end].decode("ascii", errors="replace")
def entropy(block: bytes) -> float:
if not block:
return 0.0
counts = Counter(block)
size = len(block)
return -sum((n / size) * math.log2(n / size) for n in counts.values())
def aligned(value: int, alignment: int = 4) -> int:
return (value + alignment - 1) & ~(alignment - 1)
def read_utf16z(data: bytes, offset: int, limit: int) -> tuple[str, int]:
end = offset
max_end = min(len(data), offset + limit)
chars = bytearray()
while end + 1 < max_end:
word = struct.unpack_from("<H", data, end)[0]
end += 2
if word == 0:
break
chars.extend(struct.pack("<H", word))
return chars.decode("utf-16le", errors="replace"), end
def parse_version_info(blob: bytes) -> dict[str, Any]:
result: dict[str, Any] = {"strings": {}}
def visit(start: int, parent_end: int) -> None:
if start + 6 > parent_end or start + 6 > len(blob):
return
length, value_length, value_type = unpack("<HHH", blob, start)
if length < 6:
return
end = min(start + length, parent_end, len(blob))
key, after_key = read_utf16z(blob, start + 6, max(2, end - (start + 6)))
value_start = aligned(after_key)
if value_type == 1:
value_bytes = value_length * 2
else:
value_bytes = value_length
value_end = min(value_start + value_bytes, end)
if key == "VS_VERSION_INFO" and value_bytes >= 52 and value_start + 52 <= end:
fixed = unpack("<13I", blob, value_start)
if fixed[0] == 0xFEEF04BD:
file_ms, file_ls, prod_ms, prod_ls = fixed[2:6]
result["fixed_file_version"] = [file_ms >> 16, file_ms & 0xFFFF, file_ls >> 16, file_ls & 0xFFFF]
result["fixed_product_version"] = [prod_ms >> 16, prod_ms & 0xFFFF, prod_ls >> 16, prod_ls & 0xFFFF]
if value_type == 1 and value_length and key not in {"StringFileInfo", "VarFileInfo"}:
value = blob[value_start:value_end].decode("utf-16le", errors="replace").rstrip("\0")
result["strings"].setdefault(key, value)
child = aligned(value_end)
while child + 6 <= end:
child_len = unpack("<H", blob, child)[0]
if child_len < 6 or child + child_len > end:
break
visit(child, end)
child = aligned(child + child_len)
visit(0, len(blob))
if not result["strings"]:
result.pop("strings")
return result
class PEImage:
DIRECTORY_NAMES = {
0: "export",
1: "import",
2: "resource",
3: "exception",
4: "security_certificate_file_offset",
5: "base_relocation",
6: "debug",
9: "tls",
10: "load_config",
12: "iat",
13: "delay_import",
}
RESOURCE_TYPES = {
1: "CURSOR",
2: "BITMAP",
3: "ICON",
4: "MENU",
5: "DIALOG",
6: "STRING",
9: "ACCELERATOR",
10: "RCDATA",
14: "GROUP_ICON",
16: "VERSIONINFO",
24: "MANIFEST",
}
STRING_PATTERN = re.compile(
r"(?:ngx|dlss|cuda|nvapi|tensor|weight|shader|cubin|fatbin|\.pth|d3d12|dxgi|vulkan|nvml|driver|nvsdk)",
re.IGNORECASE,
)
MAGIC_MARKERS = {
"ELF magic candidate": b"\x7fELF",
"SPIR-V little-endian magic": b"\x03\x02\x23\x07",
"DXBC container marker": b"DXBC",
"DXIL marker": b"DXIL",
"Zstandard frame magic": b"\x28\xb5\x2f\xfd",
"LZ4 frame magic": b"\x04\x22\x4d\x18",
"gzip header": b"\x1f\x8b",
"ZIP local-file header": b"PK\x03\x04",
"CUDA fatbin symbol": b"__nv_relfatbin",
"CUDA fatbin wrapper symbol": b"__cudaFatCubin",
}
def __init__(self, path: Path, string_limit: int = 100) -> None:
self.path = path
self.data = path.read_bytes()
self.string_limit = string_limit
self.sections: list[dict[str, Any]] = []
self.directories: list[tuple[int, int]] = []
self.pointer_size = 8
self.machine = 0
self._parse_headers()
def _parse_headers(self) -> None:
if self.data[:2] != b"MZ":
raise PEError("missing MZ header")
pe_offset = unpack("<I", self.data, 0x3C)[0]
if self.data[pe_offset : pe_offset + 4] != b"PE\0\0":
raise PEError("missing PE signature")
coff = pe_offset + 4
self.machine, section_count, timestamp, _, _, optional_size, characteristics = unpack("<HHIIIHH", self.data, coff)
optional = coff + 20
magic = unpack("<H", self.data, optional)[0]
if magic == 0x20B:
self.pointer_size = 8
directory_offset = optional + 112
image_base = unpack("<Q", self.data, optional + 24)[0]
elif magic == 0x10B:
self.pointer_size = 4
directory_offset = optional + 96
image_base = unpack("<I", self.data, optional + 28)[0]
else:
raise PEError(f"unsupported optional-header magic 0x{magic:x}")
directory_count = unpack("<I", self.data, directory_offset - 4)[0]
for index in range(min(directory_count, 16)):
self.directories.append(unpack("<II", self.data, directory_offset + index * 8))
section_table = optional + optional_size
for index in range(section_count):
off = section_table + index * 40
name, virtual_size, virtual_address, raw_size, raw_offset = unpack("<8sIIII", self.data, off)
_, _, _, _, flags = unpack("<IIIII", self.data, off + 20)
section_name = name.split(b"\0", 1)[0].decode("ascii", errors="replace")
raw = self.data[raw_offset : min(raw_offset + raw_size, len(self.data))]
self.sections.append(
{
"name": section_name,
"virtual_address": virtual_address,
"virtual_size": virtual_size,
"raw_offset": raw_offset,
"raw_size": raw_size,
"characteristics": f"0x{flags:08x}",
"entropy": round(entropy(raw), 4),
"sha256": hashlib.sha256(raw).hexdigest(),
}
)
self.pe_offset = pe_offset
self.timestamp = timestamp
self.characteristics = characteristics
self.image_base = image_base
def rva_to_offset(self, rva: int) -> int:
if rva < 0x1000:
return rva
for section in self.sections:
start = section["virtual_address"]
size = max(section["virtual_size"], section["raw_size"])
if start <= rva < start + size:
offset = section["raw_offset"] + rva - start
if offset < len(self.data):
return offset
raise PEError(f"RVA 0x{rva:x} is not backed by a file section")
def directory(self, index: int) -> tuple[int, int]:
return self.directories[index] if index < len(self.directories) else (0, 0)
def imports(self) -> list[dict[str, Any]]:
rva, size = self.directory(1)
if not rva or not size:
return []
off = self.rva_to_offset(rva)
item_size = 20
result = []
for pos in range(off, min(off + size, len(self.data)) - item_size + 1, item_size):
original, stamp, chain, name_rva, first_thunk = unpack("<IIIII", self.data, pos)
if not (original or stamp or chain or name_rva or first_thunk):
break
name = c_string(self.data, self.rva_to_offset(name_rva))
thunk_rva = original or first_thunk
thunk_off = self.rva_to_offset(thunk_rva)
imports: list[str] = []
max_entries = min((len(self.data) - thunk_off) // self.pointer_size, 65536)
for index in range(max_entries):
thunk = unpack("<Q" if self.pointer_size == 8 else "<I", self.data, thunk_off + index * self.pointer_size)[0]
if thunk == 0:
break
ordinal_mask = 1 << (self.pointer_size * 8 - 1)
if thunk & ordinal_mask:
imports.append(f"#{thunk & 0xFFFF}")
else:
name_off = self.rva_to_offset(thunk)
imports.append(c_string(self.data, name_off + 2))
result.append({"library": name, "symbols": imports})
return result
def exports(self) -> list[dict[str, Any]]:
rva, size = self.directory(0)
if not rva or size < 40:
return []
off = self.rva_to_offset(rva)
fields = unpack("<IIHHIIIIIII", self.data, off)
_, ordinal_base, function_count, name_count, funcs_rva, names_rva, ordinals_rva = fields[4:11]
funcs_off = self.rva_to_offset(funcs_rva) if function_count else 0
names_off = self.rva_to_offset(names_rva) if name_count else 0
ords_off = self.rva_to_offset(ordinals_rva) if name_count else 0
functions: list[dict[str, Any]] = []
for i in range(min(name_count, 100000)):
name_rva = unpack("<I", self.data, names_off + 4 * i)[0]
ordinal_index = unpack("<H", self.data, ords_off + 2 * i)[0]
name = c_string(self.data, self.rva_to_offset(name_rva))
ordinal = ordinal_base + ordinal_index
target_rva = unpack("<I", self.data, funcs_off + 4 * ordinal_index)[0] if ordinal_index < function_count else 0
functions.append({"name": name, "ordinal": ordinal, "rva": f"0x{target_rva:x}"})
return functions
def resources(self) -> list[dict[str, Any]]:
rva, size = self.directory(2)
if not rva or not size:
return []
root = self.rva_to_offset(rva)
items: list[dict[str, Any]] = []
visited: set[int] = set()
def parse_name(name_field: int, is_type: bool) -> str | int:
if not (name_field & 0x80000000):
resource_id = name_field & 0xFFFF
return self.RESOURCE_TYPES.get(resource_id, resource_id) if is_type else resource_id
rel = name_field & 0x7FFFFFFF
name_offset = root + rel
units = unpack("<H", self.data, name_offset)[0]
return self.data[name_offset + 2 : name_offset + 2 + units * 2].decode("utf-16le", errors="replace")
def walk(rel: int, path: list[str | int], depth: int = 0) -> None:
if depth > 8 or rel in visited:
return
visited.add(rel)
directory_off = root + rel
named, ids = unpack("<HH", self.data, directory_off + 12)
count = named + ids
if count > 10000:
return
for index in range(count):
name_field, target = unpack("<II", self.data, directory_off + 16 + index * 8)
name = parse_name(name_field, not path)
new_path = path + [name]
if target & 0x80000000:
walk(target & 0x7FFFFFFF, new_path, depth + 1)
continue
data_entry = root + target
data_rva, data_size, codepage, _ = unpack("<IIII", self.data, data_entry)
try:
data_off = self.rva_to_offset(data_rva)
except PEError:
data_off = -1
item: dict[str, Any] = {
"path": new_path,
"rva": f"0x{data_rva:x}",
"file_offset": f"0x{data_off:x}" if data_off >= 0 else None,
"size": data_size,
"codepage": codepage,
}
if (16 in new_path or "VERSIONINFO" in new_path) and data_off >= 0 and data_size <= 4 * 1024 * 1024:
item["version_info"] = parse_version_info(self.data[data_off : data_off + data_size])
items.append(item)
walk(0, [])
return items
def targeted_strings(self) -> list[dict[str, Any]]:
hits: list[dict[str, Any]] = []
seen: set[str] = set()
patterns = (
(re.compile(rb"[\x20-\x7e]{6,}"), "ascii"),
(re.compile(rb"(?:[\x20-\x7e]\x00){6,}"), "utf16le"),
)
for pattern, encoding in patterns:
for match in pattern.finditer(self.data):
raw = match.group(0)
value = raw.decode("ascii", errors="replace") if encoding == "ascii" else raw[::2].decode("ascii", errors="replace")
value = value.strip()
if value and value not in seen and self.STRING_PATTERN.search(value):
seen.add(value)
hits.append({"offset": f"0x{match.start():x}", "encoding": encoding, "value": value[:1000]})
if len(hits) >= self.string_limit:
return hits
return hits
def section_for_file_offset(self, offset: int) -> str | None:
for section in self.sections:
start = section["raw_offset"]
if start <= offset < start + section["raw_size"]:
return section["name"]
return None
def binary_markers(self) -> list[dict[str, Any]]:
findings = []
for label, marker in self.MAGIC_MARKERS.items():
offsets = []
cursor = 0
count = 0
valid_elf_count = 0
elf_types: Counter[tuple[int, int, int, int]] = Counter()
elf_candidates: Counter[tuple[int, int, int, int, int, int]] = Counter()
while True:
offset = self.data.find(marker, cursor)
if offset < 0:
break
count += 1
if len(offsets) < 20:
offsets.append({"file_offset": f"0x{offset:x}", "section": self.section_for_file_offset(offset)})
if label.startswith("ELF image") and offset + 64 <= len(self.data):
header = self.data[offset : offset + 64]
elf_class, elf_data = header[4], header[5]
if header[6] == 1 and elf_class in (1, 2) and elf_data in (1, 2):
endian = "<" if elf_data == 1 else ">"
elf_type = struct.unpack_from(endian + "H", header, 16)[0]
machine = struct.unpack_from(endian + "H", header, 18)[0]
elf_version = struct.unpack_from(endian + "I", header, 20)[0]
expected_header_size = 64 if elf_class == 2 else 52
header_size = struct.unpack_from(endian + "H", header, 52 if elf_class == 2 else 40)[0]
elf_candidates[(elf_class, elf_data, elf_type, machine, elf_version, header_size)] += 1
if elf_version == 1 and header_size == expected_header_size:
valid_elf_count += 1
elf_types[(elf_class, elf_data, elf_type, machine)] += 1
cursor = offset + 1
if count:
finding: dict[str, Any] = {"marker": label, "count": count, "sample_offsets": offsets}
if label.startswith("ELF"):
finding["valid_elf_header_count"] = valid_elf_count
finding["elf_header_types"] = [
{
"elf_class": "ELF64" if key[0] == 2 else "ELF32",
"endianness": "little" if key[1] == 1 else "big",
"type": key[2],
"machine": key[3],
"machine_name": "EM_CUDA" if key[3] == 190 else "unknown",
"count": value,
}
for key, value in elf_types.items()
]
finding["candidate_header_fields"] = [
{
"elf_class": "ELF64" if key[0] == 2 else "ELF32",
"endianness": "little" if key[1] == 1 else "big",
"type": key[2],
"machine": key[3],
"machine_name": "EM_CUDA" if key[3] == 190 else "unknown",
"e_version": key[4],
"e_ehsize": key[5],
"count": value,
}
for key, value in elf_candidates.most_common(10)
]
findings.append(finding)
for label, pattern in (
("PTX .version directive candidate", re.compile(rb"(?<![A-Za-z0-9_.])\.version\s+[0-9]+(?:\.[0-9]+)?")),
("PTX .target sm_ directive candidate", re.compile(rb"(?<![A-Za-z0-9_.])\.target\s+sm_[0-9]+")),
(
"PTX .entry directive candidate",
re.compile(rb"(?<![A-Za-z0-9_.])\.entry\s+([A-Za-z_$][A-Za-z0-9_.$]*)\s*\("),
),
):
matches = list(pattern.finditer(self.data))
if matches:
directives = {
(
m.group(1)
if label.startswith("PTX .entry")
else m.group(0).lstrip(b"\x00\r\n \t")
).decode("ascii")
for m in matches
}
findings.append(
{
"marker": label,
"count": len(matches),
"unique_directives": sorted(directives),
"sample_offsets": [
{"file_offset": f"0x{m.start():x}", "section": self.section_for_file_offset(m.start())}
for m in matches[:20]
],
}
)
return findings
def report(self) -> dict[str, Any]:
directories = {}
for index, (rva, size) in enumerate(self.directories):
if rva or size:
directories[self.DIRECTORY_NAMES.get(index, str(index))] = {"rva_or_file_offset": f"0x{rva:x}", "size": size}
hash_digest = hashlib.sha256(self.data).hexdigest()
return {
"path": str(self.path),
"size_bytes": len(self.data),
"sha256": hash_digest,
"format": {
"machine": f"0x{self.machine:04x}",
"pe_type": "PE32+" if self.pointer_size == 8 else "PE32",
"image_base": f"0x{self.image_base:x}",
"coff_timestamp": self.timestamp,
"coff_characteristics": f"0x{self.characteristics:04x}",
"data_directories": directories,
},
"sections": self.sections,
"imports": self.imports(),
"exports": self.exports(),
"resources": self.resources(),
"targeted_strings": self.targeted_strings(),
"candidate_binary_markers": self.binary_markers(),
}
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, help="write JSON report to this local path")
parser.add_argument("--strings-limit", type=int, default=100, help="maximum targeted strings per DLL")
parser.add_argument("dll", nargs="+", type=Path)
args = parser.parse_args()
reports = []
failed = False
for path in args.dll:
try:
reports.append(PEImage(path, max(0, args.strings_limit)).report())
except (OSError, PEError, struct.error) as exc:
failed = True
reports.append({"path": str(path), "error": str(exc)})
encoded = json.dumps(reports, indent=2, ensure_ascii=False) + "\n"
if args.output:
args.output.write_text(encoded, encoding="utf-8")
print(f"Wrote read-only metadata report for {len(reports)} input(s) to {args.output}")
else:
sys.stdout.write(encoded)
return 1 if failed else 0
if __name__ == "__main__":
raise SystemExit(main())
+210
View File
@@ -0,0 +1,210 @@
#!/usr/bin/env python3
"""Instrument a PTX kernel to report where non-finite values first appear.
Adds a trailing `.param .u64 d4r_dbg` to the entry and, after every
floating-point result written to a %-register (MMA outputs, f16/f16x2
arithmetic, conversions, rsqrt/rcp, ...), ORs a flag into
d4r_dbg[source_line] when the value is Inf or NaN (all-ones exponent):
bit 0 for an f16/f16x2 result, bit 1 for an f32 result.
With --max, each slot instead records the largest magnitude seen on that
line (f16 results as f16 bits, f32 results as f32 bits with bit 31 set to
tell them apart), which shows where values grow out of range.
Replay the instrumented kernel with replay_dlss_layer, passing a zero-filled
buffer of 4 * (line_count + 1) bytes as the extra argument, then list the
flagged lines with --report.
usage: instrument_ptx_nonfinite.py [--max] IN.ptx OUT.ptx KERNEL
instrument_ptx_nonfinite.py --report [--max] IN.ptx DEBUG_BUFFER
"""
import re
import struct
import sys
# Instructions whose first operand (or brace list) is a floating-point result.
F16X2_OPS = re.compile(r"^(add|sub|mul|fma\.rn|min|max|neg|abs)\.f16x2$|^cvt\.rn\.f16x2\.e[45]m[23]x2$")
F16_OPS = re.compile(r"^(add|sub|mul|fma\.rn|min|max|neg|abs)\.f16$|^cvt\.rn\.f16\.f(32|64)$")
F32_OPS = re.compile(r"^(add|sub|mul|fma\.rn|div\.approx|rsqrt\.approx|rcp\.approx|sqrt\.approx|lg2\.approx|ex2\.approx|cvt\.f32\.f16)(\.ftz)?(\.f32)?$")
MMA_F16 = re.compile(r"^mma\.sync\.aligned\.m16n8k(16|32)\.row\.col\.f16\.")
INSTRUCTION = re.compile(r"^(\s*\{?\s*)(@!?%p\d+\s+)?([a-z][\w.:]*)\s+(.*)$")
MAX_MODE = False
def max_checks(offset, register, kind):
"""Record the largest magnitude written by one instruction."""
if kind == "f16x2":
return [
f"and.b32 %d4rdbg_r0, {register}, 32767;",
f"shr.u32 %d4rdbg_r1, {register}, 16;",
"and.b32 %d4rdbg_r1, %d4rdbg_r1, 32767;",
"max.u32 %d4rdbg_r0, %d4rdbg_r0, %d4rdbg_r1;",
f"atom.global.max.u32 %d4rdbg_r1, [%d4rdbg_rd0+{offset}], %d4rdbg_r0;",
]
if kind == "f16":
return [
f"cvt.u32.u16 %d4rdbg_r0, {register};",
"and.b32 %d4rdbg_r0, %d4rdbg_r0, 32767;",
f"atom.global.max.u32 %d4rdbg_r1, [%d4rdbg_rd0+{offset}], %d4rdbg_r0;",
]
return [
f"mov.b32 %d4rdbg_r0, {register};",
"and.b32 %d4rdbg_r0, %d4rdbg_r0, 2147483647;",
"shr.u32 %d4rdbg_r0, %d4rdbg_r0, 1;",
"or.b32 %d4rdbg_r0, %d4rdbg_r0, 2147483648;",
f"atom.global.max.u32 %d4rdbg_r1, [%d4rdbg_rd0+{offset}], %d4rdbg_r0;",
]
def checks_for(line_number, opcode, operands):
"""Return PTX lines checking the destination of one instruction."""
offset = 4 * line_number
if MMA_F16.match(opcode):
destination = re.match(r"\{([^}]*)\}", operands)
registers = [r.strip() for r in destination.group(1).split(",")] if destination else []
kind = "f16x2"
else:
first = operands.split(",")[0].strip().rstrip(";")
registers = [first]
if F16X2_OPS.match(opcode):
kind = "f16x2"
elif F16_OPS.match(opcode):
kind = "f16"
elif F32_OPS.match(opcode) and opcode.endswith(("f32", "f16")):
kind = "f32"
else:
return []
lines = []
for register in registers:
if not register.startswith("%"):
continue # registers declared in nested inline-asm scopes
if MAX_MODE:
lines += max_checks(offset, register, kind)
continue
if kind == "f16x2":
lines += [
f"and.b32 %d4rdbg_r0, {register}, 2080406528;", # 0x7c007c00
"and.b32 %d4rdbg_r1, %d4rdbg_r0, 31744;", # 0x7c00
"setp.eq.u32 %d4rdbg_p0, %d4rdbg_r1, 31744;",
"and.b32 %d4rdbg_r1, %d4rdbg_r0, 2080374784;", # 0x7c000000
"setp.eq.or.u32 %d4rdbg_p0, %d4rdbg_r1, 2080374784, %d4rdbg_p0;",
f"@%d4rdbg_p0 atom.global.or.b32 %d4rdbg_r1, [%d4rdbg_rd0+{offset}], 1;",
]
elif kind == "f16":
lines += [
f"cvt.u32.u16 %d4rdbg_r0, {register};",
"and.b32 %d4rdbg_r0, %d4rdbg_r0, 31744;",
"setp.eq.u32 %d4rdbg_p0, %d4rdbg_r0, 31744;",
f"@%d4rdbg_p0 atom.global.or.b32 %d4rdbg_r1, [%d4rdbg_rd0+{offset}], 1;",
]
else:
lines += [
f"mov.b32 %d4rdbg_r0, {register};",
"and.b32 %d4rdbg_r0, %d4rdbg_r0, 2139095040;", # 0x7f800000
"setp.eq.u32 %d4rdbg_p0, %d4rdbg_r0, 2139095040;",
f"@%d4rdbg_p0 atom.global.or.b32 %d4rdbg_r1, [%d4rdbg_rd0+{offset}], 2;",
]
return lines
def instrument(source, kernel):
lines = source.split("\n")
output = []
in_kernel = False
entry_params_open = False
body_started = False
pending_statement = None # multi-line mma statements
for number, line in enumerate(lines, start=1):
if re.match(rf"^\.visible \.entry {re.escape(kernel)}\(", line):
in_kernel = True
entry_params_open = True
output.append(line)
continue
if in_kernel and entry_params_open and line.strip() == ")":
output[-1] = output[-1] + ","
output.append(".param .u64 d4r_dbg")
output.append(line)
entry_params_open = False
continue
if in_kernel and not body_started and line.strip() == "{":
output.append(line)
output.append(".reg .pred %d4rdbg_p0;")
output.append(".reg .b32 %d4rdbg_r<2>;")
output.append(".reg .b64 %d4rdbg_rd0;")
output.append("ld.param.u64 %d4rdbg_rd0, [d4r_dbg];")
output.append("cvta.to.global.u64 %d4rdbg_rd0, %d4rdbg_rd0;")
body_started = True
continue
output.append(line)
if not body_started:
continue
if line.strip() == "}" and pending_statement is None and number == len(lines):
in_kernel = False
statement = line if pending_statement is None else pending_statement[1] + " " + line
start = number if pending_statement is None else pending_statement[0]
if ";" not in line:
match = INSTRUCTION.match(statement)
if match and MMA_F16.match(match.group(3)):
pending_statement = (start, statement)
continue
pending_statement = None
match = INSTRUCTION.match(statement)
if not match or match.group(2):
continue # skip predicated results: the check would run unconditionally
checks = checks_for(start, match.group(3), match.group(4))
if checks:
output.extend(checks)
return "\n".join(output), len(lines)
def half_value(bits):
return struct.unpack("<e", struct.pack("<H", bits & 0xFFFF))[0]
def report_max(lines, values, limit):
rows = [(index, value) for index, value in enumerate(values) if value]
print(f"{len(rows)} source lines recorded magnitudes")
for index, value in rows[:limit]:
if value & 0x80000000:
magnitude = struct.unpack("<f", struct.pack("<I", (value & 0x7FFFFFFF) << 1))[0]
kind = "f32"
else:
magnitude = half_value(value)
kind = "f16"
text = lines[index - 1].strip() if 0 < index <= len(lines) else "?"
print(f" line {index:6d} [{kind}] max|x|={magnitude:<12.6g} {text[:90]}")
def report(ptx_path, debug_path, max_mode=False, limit=40):
lines = open(ptx_path).read().split("\n")
data = open(debug_path, "rb").read()
flags = struct.unpack(f"<{len(data) // 4}I", data)
if max_mode:
report_max(lines, flags, limit)
return
hits = [(index, flag) for index, flag in enumerate(flags) if flag]
print(f"{len(hits)} source lines produced non-finite values")
for index, flag in hits[:40]:
kind = "+".join(k for bit, k in ((1, "f16"), (2, "f32")) if flag & bit)
text = lines[index - 1].strip() if 0 < index <= len(lines) else "?"
print(f" line {index:6d} [{kind}] {text[:110]}")
if __name__ == "__main__":
arguments = sys.argv[1:]
reporting = "--report" in arguments
MAX_MODE = "--max" in arguments
arguments = [a for a in arguments if a not in ("--report", "--max")]
if reporting and len(arguments) == 2:
report(arguments[0], arguments[1], MAX_MODE, limit=400)
elif len(arguments) == 3:
sys.argv = [sys.argv[0]] + arguments
text, count = instrument(open(sys.argv[1]).read(), sys.argv[3])
open(sys.argv[2], "w").write(text)
print(f"instrumented {sys.argv[3]}: {count} source lines; debug buffer needs {4 * (count + 1)} bytes")
else:
print(__doc__)
sys.exit(2)
File diff suppressed because it is too large. Load diff
+363
View File
@@ -0,0 +1,363 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <dxgi1_4.h>
#include <d3d12.h>
#include "nvsdk_ngx.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
template <typename Target, typename Source> Target copy_function_pointer(Source source)
{
static_assert(sizeof(Target) == sizeof(Source), "function pointer sizes differ");
Target target{};
std::memcpy(&target, &source, sizeof(target));
return target;
}
template <typename Function> Function load_export(HMODULE module, const char* name)
{
FARPROC raw = GetProcAddress(module, name);
return raw ? copy_function_pointer<Function>(raw) : nullptr;
}
static std::wstring utf8_to_wide(const char* input)
{
const int required = MultiByteToWideChar(CP_UTF8, 0, input, -1, nullptr, 0);
if (required <= 1)
return {};
std::wstring output(static_cast<size_t>(required), L'\0');
MultiByteToWideChar(CP_UTF8, 0, input, -1, output.data(), required);
output.resize(static_cast<size_t>(required - 1));
return output;
}
static const char* ngx_result_name(NVSDK_NGX_Result result)
{
switch (result)
{
case NVSDK_NGX_Result_Success:
return "Success";
case NVSDK_NGX_Result_FAIL_FeatureNotSupported:
return "FeatureNotSupported";
case NVSDK_NGX_Result_FAIL_PlatformError:
return "PlatformError";
case NVSDK_NGX_Result_FAIL_InvalidParameter:
return "InvalidParameter";
case NVSDK_NGX_Result_FAIL_NotInitialized:
return "NotInitialized";
case NVSDK_NGX_Result_FAIL_UnableToInitializeFeature:
return "UnableToInitializeFeature";
case NVSDK_NGX_Result_FAIL_OutOfDate:
return "OutOfDate";
default:
return "Other";
}
}
static void print_ngx_result(const char* stage, NVSDK_NGX_Result result)
{
std::printf("%s: result=0x%08x (%s)\n", stage, static_cast<unsigned int>(result), ngx_result_name(result));
}
int main(int argc, char** argv)
{
if (argc != 4)
{
std::fprintf(stderr,
"usage: ngx_d3d12_probe.exe PATH_TO_NGX_CORE_DLL PATH_TO_NVNGX_DLSS_DLL WRITABLE_APPDATA_DIRECTORY\n");
return 2;
}
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
const char* ngxCorePath = argv[1];
const char* featureDllPath = argv[2];
std::wstring appData = utf8_to_wide(argv[3]);
std::wstring featureDirectory = utf8_to_wide(featureDllPath);
if (appData.empty() || featureDirectory.empty())
{
std::fprintf(stderr, "Invalid UTF-8 feature-DLL path or appdata path argument\n");
return 2;
}
const size_t separator = featureDirectory.find_last_of(L"\\/");
if (separator == std::wstring::npos)
featureDirectory = L".";
else
featureDirectory.resize(separator);
const wchar_t* featurePaths[] = {featureDirectory.c_str()};
NVSDK_NGX_FeatureCommonInfo featureCommonInfo{};
featureCommonInfo.PathListInfo.Path = featurePaths;
featureCommonInfo.PathListInfo.Length = 1;
HMODULE ngxCore = LoadLibraryA(ngxCorePath);
if (ngxCore == nullptr)
{
std::fprintf(stderr, "LoadLibraryA(%s) failed: GetLastError=%lu\n", ngxCorePath, GetLastError());
return 1;
}
std::printf("Loaded NGX core: %s\n", ngxCorePath);
std::printf("DLSS SR feature search directory: %ls\n", featureDirectory.c_str());
HMODULE dxgiModule = LoadLibraryA("dxgi.dll");
HMODULE d3d12Module = LoadLibraryA("d3d12.dll");
if (dxgiModule == nullptr || d3d12Module == nullptr)
{
std::fprintf(stderr, "Could not load dxgi.dll or d3d12.dll: GetLastError=%lu\n", GetLastError());
if (d3d12Module != nullptr)
FreeLibrary(d3d12Module);
if (dxgiModule != nullptr)
FreeLibrary(dxgiModule);
FreeLibrary(ngxCore);
return 1;
}
using CreateFactoryFn = HRESULT(WINAPI*)(REFIID, void**);
using D3D12CreateDeviceFn = HRESULT(WINAPI*)(IUnknown*, D3D_FEATURE_LEVEL, REFIID, void**);
auto createFactory = load_export<CreateFactoryFn>(dxgiModule, "CreateDXGIFactory1");
auto createD3D12Device = load_export<D3D12CreateDeviceFn>(d3d12Module, "D3D12CreateDevice");
auto getRequirements = load_export<decltype(&NVSDK_NGX_D3D12_GetFeatureRequirements)>(
ngxCore, "NVSDK_NGX_D3D12_GetFeatureRequirements");
auto init = load_export<decltype(&NVSDK_NGX_D3D12_Init)>(ngxCore, "NVSDK_NGX_D3D12_Init");
auto shutdown = load_export<decltype(&NVSDK_NGX_D3D12_Shutdown1)>(ngxCore, "NVSDK_NGX_D3D12_Shutdown1");
auto getCapabilityParameters = load_export<decltype(&NVSDK_NGX_D3D12_GetCapabilityParameters)>(
ngxCore, "NVSDK_NGX_D3D12_GetCapabilityParameters");
auto destroyParameters = load_export<decltype(&NVSDK_NGX_D3D12_DestroyParameters)>(
ngxCore, "NVSDK_NGX_D3D12_DestroyParameters");
auto getScratchBufferSize = load_export<decltype(&NVSDK_NGX_D3D12_GetScratchBufferSize)>(
ngxCore, "NVSDK_NGX_D3D12_GetScratchBufferSize");
auto createFeature = load_export<decltype(&NVSDK_NGX_D3D12_CreateFeature)>(
ngxCore, "NVSDK_NGX_D3D12_CreateFeature");
auto releaseFeature = load_export<decltype(&NVSDK_NGX_D3D12_ReleaseFeature)>(
ngxCore, "NVSDK_NGX_D3D12_ReleaseFeature");
if (createFactory == nullptr || createD3D12Device == nullptr || getRequirements == nullptr || init == nullptr ||
getCapabilityParameters == nullptr || destroyParameters == nullptr || getScratchBufferSize == nullptr ||
createFeature == nullptr || releaseFeature == nullptr)
{
std::fprintf(stderr, "Missing DXGI, D3D12, or NGX exports\n");
FreeLibrary(d3d12Module);
FreeLibrary(dxgiModule);
FreeLibrary(ngxCore);
return 1;
}
constexpr GUID iidFactory1 = {0x770aae78, 0xf26f, 0x4dba, {0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, 0x87}};
constexpr GUID iidD3D12Device = {0x189819f1, 0x1db6, 0x4b57, {0xbe, 0x54, 0x18, 0x21, 0x33, 0x9b, 0x85, 0xf7}};
constexpr GUID iidD3D12CommandAllocator = {0x6102dee4, 0xaf59, 0x4b09, {0xb9, 0x99, 0xb4, 0x4d, 0x73, 0xf0, 0x9b, 0x24}};
constexpr GUID iidD3D12GraphicsCommandList = {0x5b160d0f, 0xac1b, 0x4185, {0x8b, 0xa8, 0xb3, 0xae, 0x42, 0xa5, 0xa4, 0x55}};
constexpr GUID iidD3D12Resource = {0x696442be, 0xa72e, 0x4059, {0xbc, 0x79, 0x5b, 0x5c, 0x98, 0x04, 0x0f, 0xad}};
IDXGIFactory1* factory = nullptr;
HRESULT hr = createFactory(iidFactory1, reinterpret_cast<void**>(&factory));
std::printf("CreateDXGIFactory1: HRESULT=0x%08lx\n", static_cast<unsigned long>(hr));
if (FAILED(hr) || factory == nullptr)
{
FreeLibrary(d3d12Module);
FreeLibrary(dxgiModule);
FreeLibrary(ngxCore);
return 1;
}
NVSDK_NGX_FeatureDiscoveryInfo discovery{};
discovery.SDKVersion = static_cast<NVSDK_NGX_Version>(0x13);
if (const char* apiVersion = std::getenv("D4R_NGX_API_VERSION"))
discovery.SDKVersion = static_cast<NVSDK_NGX_Version>(std::strtoul(apiVersion, nullptr, 0));
discovery.FeatureID = NVSDK_NGX_Feature_SuperSampling;
discovery.Identifier.IdentifierType = NVSDK_NGX_Application_Identifier_Type_Application_Id;
discovery.Identifier.v.ApplicationId = 241534723ULL;
discovery.ApplicationDataPath = appData.c_str();
discovery.FeatureInfo = &featureCommonInfo;
IDXGIAdapter1* adapter = nullptr;
IDXGIAdapter1* amdAdapter = nullptr;
const char* selectFirstAdapterSetting = std::getenv("D4R_SELECT_FIRST_DXGI_ADAPTER");
const bool selectFirstAdapter = selectFirstAdapterSetting != nullptr && std::strcmp(selectFirstAdapterSetting, "1") == 0;
for (UINT index = 0; factory->EnumAdapters1(index, &adapter) != DXGI_ERROR_NOT_FOUND; ++index)
{
DXGI_ADAPTER_DESC1 description{};
if (SUCCEEDED(adapter->GetDesc1(&description)))
{
std::printf("DXGI adapter: vendor=0x%04x device=0x%04x flags=0x%08x luid=%08lx:%08lx name=%ls\n",
description.VendorId, description.DeviceId, description.Flags,
static_cast<unsigned long>(description.AdapterLuid.HighPart),
static_cast<unsigned long>(description.AdapterLuid.LowPart), description.Description);
if ((description.VendorId == 0x1002 || selectFirstAdapter) && amdAdapter == nullptr)
{
amdAdapter = adapter;
adapter = nullptr;
}
}
if (adapter != nullptr)
{
adapter->Release();
adapter = nullptr;
}
}
if (amdAdapter == nullptr)
{
std::fprintf(stderr, "No AMD DXGI adapter was enumerated\n");
factory->Release();
FreeLibrary(d3d12Module);
FreeLibrary(dxgiModule);
FreeLibrary(ngxCore);
return 1;
}
NVSDK_NGX_FeatureRequirement requirement{};
NVSDK_NGX_Result ngxResult = getRequirements(amdAdapter, &discovery, &requirement);
print_ngx_result("D3D12_GetFeatureRequirements(DLSS SR)", ngxResult);
if (ngxResult == NVSDK_NGX_Result_Success)
{
std::printf("feature support flags: 0x%08x\n", static_cast<unsigned int>(requirement.FeatureSupported));
std::printf("minimum hardware architecture: %u\n", requirement.MinHWArchitecture);
std::printf("minimum OS version: %s\n", requirement.MinOSVersion);
}
ID3D12Device* device = nullptr;
hr = createD3D12Device(amdAdapter, D3D_FEATURE_LEVEL_12_0, iidD3D12Device,
reinterpret_cast<void**>(&device));
std::printf("D3D12CreateDevice: HRESULT=0x%08lx\n", static_cast<unsigned long>(hr));
if (FAILED(hr) || device == nullptr)
{
amdAdapter->Release();
factory->Release();
FreeLibrary(d3d12Module);
FreeLibrary(dxgiModule);
FreeLibrary(ngxCore);
return 1;
}
std::printf("NGX API version passed to core: 0x%08x\n", static_cast<unsigned int>(discovery.SDKVersion));
ngxResult = init(discovery.Identifier.v.ApplicationId, discovery.ApplicationDataPath, device,
&featureCommonInfo, discovery.SDKVersion);
print_ngx_result("D3D12_Init(DLSS SR)", ngxResult);
if (ngxResult == NVSDK_NGX_Result_Success)
{
NVSDK_NGX_Parameter* parameters = nullptr;
NVSDK_NGX_Result parameterResult = getCapabilityParameters(&parameters);
print_ngx_result("D3D12_GetCapabilityParameters", parameterResult);
if (parameterResult == NVSDK_NGX_Result_Success && parameters != nullptr)
{
int available = -1;
NVSDK_NGX_Result availabilityResult = parameters->Get(
NVSDK_NGX_Parameter_SuperSampling_Available, &available);
print_ngx_result("Parameters.Get(SuperSampling.Available)", availabilityResult);
std::printf("DLSS SR availability value: %d\n", available);
int featureInitResult = -1;
NVSDK_NGX_Result initStateResult = parameters->Get(
NVSDK_NGX_Parameter_SuperSampling_FeatureInitResult, &featureInitResult);
print_ngx_result("Parameters.Get(SuperSampling.FeatureInitResult)", initStateResult);
std::printf("DLSS SR recorded feature-init result: 0x%08x\n",
static_cast<unsigned int>(featureInitResult));
int needsUpdatedDriver = -1;
NVSDK_NGX_Result driverStateResult = parameters->Get(
NVSDK_NGX_Parameter_SuperSampling_NeedsUpdatedDriver, &needsUpdatedDriver);
print_ngx_result("Parameters.Get(SuperSampling.NeedsUpdatedDriver)", driverStateResult);
std::printf("DLSS SR needs updated driver: %d\n", needsUpdatedDriver);
constexpr unsigned int renderWidth = 640;
constexpr unsigned int renderHeight = 360;
constexpr unsigned int outputWidth = 1280;
constexpr unsigned int outputHeight = 720;
parameters->Set(NVSDK_NGX_Parameter_Width, renderWidth);
parameters->Set(NVSDK_NGX_Parameter_Height, renderHeight);
parameters->Set(NVSDK_NGX_Parameter_OutWidth, outputWidth);
parameters->Set(NVSDK_NGX_Parameter_OutHeight, outputHeight);
parameters->Set(NVSDK_NGX_Parameter_PerfQualityValue,
static_cast<int>(NVSDK_NGX_PerfQuality_Value_MaxQuality));
parameters->Set(NVSDK_NGX_Parameter_DLSS_Feature_Create_Flags,
static_cast<int>(NVSDK_NGX_DLSS_Feature_Flags_None));
size_t scratchSize = 0;
NVSDK_NGX_Result scratchResult = getScratchBufferSize(
NVSDK_NGX_Feature_SuperSampling, parameters, &scratchSize);
print_ngx_result("D3D12_GetScratchBufferSize(DLSS SR)", scratchResult);
std::printf("DLSS SR scratch size: %llu bytes\n", static_cast<unsigned long long>(scratchSize));
ID3D12CommandAllocator* commandAllocator = nullptr;
ID3D12GraphicsCommandList* commandList = nullptr;
HRESULT commandResult = device->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT, iidD3D12CommandAllocator,
reinterpret_cast<void**>(&commandAllocator));
std::printf("CreateCommandAllocator: HRESULT=0x%08lx\n", static_cast<unsigned long>(commandResult));
if (SUCCEEDED(commandResult) && commandAllocator != nullptr)
{
commandResult = device->CreateCommandList(
0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator, nullptr,
iidD3D12GraphicsCommandList, reinterpret_cast<void**>(&commandList));
std::printf("CreateCommandList: HRESULT=0x%08lx\n", static_cast<unsigned long>(commandResult));
}
ID3D12Resource* scratchBuffer = nullptr;
if (scratchResult == NVSDK_NGX_Result_Success && scratchSize > 0 && commandList != nullptr)
{
D3D12_HEAP_PROPERTIES heap{};
heap.Type = D3D12_HEAP_TYPE_DEFAULT;
D3D12_RESOURCE_DESC buffer{};
buffer.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
buffer.Width = scratchSize;
buffer.Height = 1;
buffer.DepthOrArraySize = 1;
buffer.MipLevels = 1;
buffer.SampleDesc.Count = 1;
buffer.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
HRESULT scratchCreateResult = device->CreateCommittedResource(
&heap, D3D12_HEAP_FLAG_NONE, &buffer, D3D12_RESOURCE_STATE_COMMON, nullptr,
iidD3D12Resource, reinterpret_cast<void**>(&scratchBuffer));
std::printf("CreateScratchBuffer: HRESULT=0x%08lx\n", static_cast<unsigned long>(scratchCreateResult));
if (SUCCEEDED(scratchCreateResult) && scratchBuffer != nullptr)
{
parameters->Set(NVSDK_NGX_Parameter_Scratch, scratchBuffer);
parameters->Set(NVSDK_NGX_Parameter_Scratch_SizeInBytes,
static_cast<unsigned long long>(scratchSize));
}
}
if (commandList != nullptr)
{
NVSDK_NGX_Handle* featureHandle = nullptr;
NVSDK_NGX_Result createFeatureResult = createFeature(
commandList, NVSDK_NGX_Feature_SuperSampling, parameters, &featureHandle);
print_ngx_result("D3D12_CreateFeature(DLSS SR)", createFeatureResult);
std::printf("DLSS SR feature handle: %s\n", featureHandle != nullptr ? "created" : "null");
if (createFeatureResult == NVSDK_NGX_Result_Success && featureHandle != nullptr)
{
NVSDK_NGX_Result releaseResult = releaseFeature(featureHandle);
print_ngx_result("D3D12_ReleaseFeature", releaseResult);
}
}
else
{
std::printf("Skipping DLSS SR CreateFeature because D3D12 command-list creation failed\n");
}
if (scratchBuffer != nullptr)
scratchBuffer->Release();
if (commandList != nullptr)
commandList->Release();
if (commandAllocator != nullptr)
commandAllocator->Release();
NVSDK_NGX_Result destroyResult = destroyParameters(parameters);
print_ngx_result("D3D12_DestroyParameters", destroyResult);
}
}
if (ngxResult == NVSDK_NGX_Result_Success && shutdown != nullptr)
{
NVSDK_NGX_Result shutdownResult = shutdown(device);
print_ngx_result("D3D12_Shutdown1", shutdownResult);
}
device->Release();
amdAdapter->Release();
factory->Release();
FreeLibrary(d3d12Module);
FreeLibrary(dxgiModule);
FreeLibrary(ngxCore);
return 0;
}
+60
View File
@@ -0,0 +1,60 @@
#include <windows.h>
#include <cstdio>
#include <cstdlib>
int main(int argc, char** argv)
{
if (argc != 2)
{
std::fprintf(stderr, "usage: ngx_load_probe.exe PATH_TO_NVNGX_DLSS_DLL\n");
return 2;
}
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
const char* path = argv[1];
HMODULE module = LoadLibraryA(path);
if (module == nullptr)
{
std::fprintf(stderr, "LoadLibraryA failed for '%s': GetLastError=%lu\n", path, GetLastError());
return 1;
}
std::printf("LoadLibraryA succeeded: %s\n", path);
constexpr const char* symbols[] = {
"NVSDK_NGX_GetAPIVersion",
"NVSDK_NGX_D3D12_GetFeatureRequirements",
"NVSDK_NGX_D3D12_Init",
"NVSDK_NGX_D3D12_CreateFeature",
"NVSDK_NGX_D3D12_EvaluateFeature",
"NVSDK_NGX_VULKAN_GetFeatureRequirements",
"NVSDK_NGX_VULKAN_Init",
"NVSDK_NGX_VULKAN_CreateFeature",
"NVSDK_NGX_VULKAN_EvaluateFeature",
"NVSDK_NGX_CUDA_GetFeatureRequirements",
"NVSDK_NGX_CUDA_Init",
"NVSDK_NGX_CUDA_CreateFeature",
"NVSDK_NGX_CUDA_EvaluateFeature",
};
for (const char* symbol : symbols)
{
std::printf("export %-52s %s\n", symbol, GetProcAddress(module, symbol) ? "present" : "absent");
}
FARPROC getApiVersion = GetProcAddress(module, "NVSDK_NGX_GetAPIVersion");
if (getApiVersion == nullptr)
{
std::fprintf(stderr, "NVSDK_NGX_GetAPIVersion export is unavailable\n");
FreeLibrary(module);
return 1;
}
const unsigned int apiVersion = static_cast<unsigned int>(getApiVersion());
std::printf("NVSDK_NGX_GetAPIVersion returned 0x%08x\n", apiVersion);
FreeLibrary(module);
return 0;
}
+269
View File
@@ -0,0 +1,269 @@
#include "ngx_param_abi_probe_shared.h"
inline void* operator new(__SIZE_TYPE__, void* place) noexcept { return place; }
inline void operator delete(void*, void*) noexcept {}
using D4rNgxResult = unsigned int;
struct ID3D11Resource;
struct ID3D12Resource;
struct D4rNgxParameterAbi
{
virtual void Set(const char* name, unsigned long long value) = 0;
virtual void Set(const char* name, float value) = 0;
virtual void Set(const char* name, double value) = 0;
virtual void Set(const char* name, unsigned int value) = 0;
virtual void Set(const char* name, int value) = 0;
virtual void Set(const char* name, ID3D11Resource* value) = 0;
virtual void Set(const char* name, ID3D12Resource* value) = 0;
virtual void Set(const char* name, void* value) = 0;
virtual D4rNgxResult Get(const char* name, unsigned long long* value) const = 0;
virtual D4rNgxResult Get(const char* name, float* value) const = 0;
virtual D4rNgxResult Get(const char* name, double* value) const = 0;
virtual D4rNgxResult Get(const char* name, unsigned int* value) const = 0;
virtual D4rNgxResult Get(const char* name, int* value) const = 0;
virtual D4rNgxResult Get(const char* name, ID3D11Resource** value) const = 0;
virtual D4rNgxResult Get(const char* name, ID3D12Resource** value) const = 0;
virtual D4rNgxResult Get(const char* name, void** value) const = 0;
virtual void Reset() = 0;
};
extern "C" void d4r_ngx_parameter_set_ull(
void* rawParameters, const char* name, unsigned long long value)
{
static_cast<D4rNgxParameterAbi*>(rawParameters)->Set(name, value);
}
extern "C" void d4r_ngx_parameter_set_float(
void* rawParameters, const char* name, float value)
{
static_cast<D4rNgxParameterAbi*>(rawParameters)->Set(name, value);
}
extern "C" void d4r_ngx_parameter_set_double(
void* rawParameters, const char* name, double value)
{
static_cast<D4rNgxParameterAbi*>(rawParameters)->Set(name, value);
}
extern "C" void d4r_ngx_parameter_set_uint(
void* rawParameters, const char* name, unsigned int value)
{
static_cast<D4rNgxParameterAbi*>(rawParameters)->Set(name, value);
}
extern "C" void d4r_ngx_parameter_set_int(
void* rawParameters, const char* name, int value)
{
static_cast<D4rNgxParameterAbi*>(rawParameters)->Set(name, value);
}
extern "C" void d4r_ngx_parameter_set_void(
void* rawParameters, const char* name, void* value)
{
static_cast<D4rNgxParameterAbi*>(rawParameters)->Set(name, value);
}
extern "C" D4rNgxResult d4r_ngx_parameter_get_ull(
void* rawParameters, const char* name, unsigned long long* value)
{
return static_cast<D4rNgxParameterAbi*>(rawParameters)->Get(name, value);
}
extern "C" D4rNgxResult d4r_ngx_parameter_get_float(
void* rawParameters, const char* name, float* value)
{
return static_cast<D4rNgxParameterAbi*>(rawParameters)->Get(name, value);
}
extern "C" D4rNgxResult d4r_ngx_parameter_get_double(
void* rawParameters, const char* name, double* value)
{
return static_cast<D4rNgxParameterAbi*>(rawParameters)->Get(name, value);
}
extern "C" D4rNgxResult d4r_ngx_parameter_get_uint(
void* rawParameters, const char* name, unsigned int* value)
{
return static_cast<D4rNgxParameterAbi*>(rawParameters)->Get(name, value);
}
extern "C" D4rNgxResult d4r_ngx_parameter_get_int(
void* rawParameters, const char* name, int* value)
{
return static_cast<D4rNgxParameterAbi*>(rawParameters)->Get(name, value);
}
extern "C" D4rNgxResult d4r_ngx_parameter_get_void(
void* rawParameters, const char* name, void** value)
{
return static_cast<D4rNgxParameterAbi*>(rawParameters)->Get(name, value);
}
extern "C" D4rNgxResult d4r_ngx_parameter_float_roundtrip(
void* rawParameters, const char* name, float value, float* outValue)
{
auto* parameters = static_cast<D4rNgxParameterAbi*>(rawParameters);
parameters->Set(name, value);
*outValue = 0.0f;
return parameters->Get(name, outValue);
}
extern "C" D4rNgxResult d4r_ngx_parameter_uint_roundtrip(
void* rawParameters, const char* name, unsigned int value, unsigned int* outValue)
{
auto* parameters = static_cast<D4rNgxParameterAbi*>(rawParameters);
parameters->Set(name, value);
*outValue = 0;
return parameters->Get(name, outValue);
}
struct D4rNgxParameterProxy final : D4rNgxParameterAbi
{
explicit D4rNgxParameterProxy(void* rawOriginal)
: original(static_cast<D4rNgxParameterAbi*>(rawOriginal)), recordCount(0), droppedRecords(0)
{
}
void Set(const char* name, unsigned long long value) override
{
original->Set(name, value);
append('S', 'q', name, 0xffffffffu);
}
void Set(const char* name, float value) override
{
original->Set(name, value);
append('S', 'f', name, 0xffffffffu);
}
void Set(const char* name, double value) override
{
original->Set(name, value);
append('S', 'd', name, 0xffffffffu);
}
void Set(const char* name, unsigned int value) override
{
original->Set(name, value);
append('S', 'u', name, 0xffffffffu);
}
void Set(const char* name, int value) override
{
original->Set(name, value);
append('S', 'i', name, 0xffffffffu);
}
void Set(const char* name, ID3D11Resource* value) override
{
original->Set(name, value);
append('S', '1', name, 0xffffffffu);
}
void Set(const char* name, ID3D12Resource* value) override
{
original->Set(name, value);
append('S', '2', name, 0xffffffffu);
}
void Set(const char* name, void* value) override
{
original->Set(name, value);
append('S', 'p', name, 0xffffffffu);
}
D4rNgxResult Get(const char* name, unsigned long long* value) const override
{
const D4rNgxResult result = original->Get(name, value);
append('G', 'q', name, result, result == 1 ? *value : 0);
return result;
}
D4rNgxResult Get(const char* name, float* value) const override
{
const D4rNgxResult result = original->Get(name, value);
append('G', 'f', name, result, result == 1 ? static_cast<unsigned long long>(*value) : 0);
return result;
}
D4rNgxResult Get(const char* name, double* value) const override
{
const D4rNgxResult result = original->Get(name, value);
append('G', 'd', name, result, result == 1 ? static_cast<unsigned long long>(*value) : 0);
return result;
}
D4rNgxResult Get(const char* name, unsigned int* value) const override
{
const D4rNgxResult result = original->Get(name, value);
append('G', 'u', name, result, result == 1 ? *value : 0);
return result;
}
D4rNgxResult Get(const char* name, int* value) const override
{
const D4rNgxResult result = original->Get(name, value);
append('G', 'i', name, result, result == 1 ? static_cast<unsigned int>(*value) : 0);
return result;
}
D4rNgxResult Get(const char* name, ID3D11Resource** value) const override
{
const D4rNgxResult result = original->Get(name, value);
append('G', '1', name, result, result == 1 ? reinterpret_cast<unsigned long long>(*value) : 0);
return result;
}
D4rNgxResult Get(const char* name, ID3D12Resource** value) const override
{
const D4rNgxResult result = original->Get(name, value);
append('G', '2', name, result, result == 1 ? reinterpret_cast<unsigned long long>(*value) : 0);
return result;
}
D4rNgxResult Get(const char* name, void** value) const override
{
const D4rNgxResult result = original->Get(name, value);
append('G', 'p', name, result, result == 1 ? reinterpret_cast<unsigned long long>(*value) : 0);
return result;
}
void Reset() override
{
original->Reset();
append('S', 'R', "Reset", 0xffffffffu);
}
void append(char operation, char valueType, const char* name, D4rNgxResult result,
unsigned long long value = 0) const
{
if (recordCount >= 512)
{
++droppedRecords;
return;
}
D4rNgxParamLogRecord& record = records[recordCount++];
record.operation = static_cast<unsigned char>(operation);
record.valueType = static_cast<unsigned char>(valueType);
record.reserved = 0;
record.result = result;
record.value = value;
unsigned int index = 0;
if (name != nullptr)
{
while (index < 63 && name[index] != '\0')
{
record.name[index] = name[index];
++index;
}
}
record.name[index] = '\0';
}
D4rNgxParameterAbi* original;
mutable unsigned int recordCount;
mutable unsigned int droppedRecords;
mutable D4rNgxParamLogRecord records[512];
};
extern "C" unsigned int d4r_ngx_parameter_proxy_size()
{
return static_cast<unsigned int>(sizeof(D4rNgxParameterProxy));
}
extern "C" void d4r_ngx_parameter_proxy_construct(void* storage, void* original)
{
::new (storage) D4rNgxParameterProxy(original);
}
extern "C" unsigned int d4r_ngx_parameter_proxy_count(const void* storage)
{
return static_cast<const D4rNgxParameterProxy*>(storage)->recordCount;
}
extern "C" unsigned int d4r_ngx_parameter_proxy_dropped(const void* storage)
{
return static_cast<const D4rNgxParameterProxy*>(storage)->droppedRecords;
}
extern "C" int d4r_ngx_parameter_proxy_copy_record(
const void* storage, unsigned int index, D4rNgxParamLogRecord* outRecord)
{
const auto* proxy = static_cast<const D4rNgxParameterProxy*>(storage);
if (index >= proxy->recordCount)
return 0;
*outRecord = proxy->records[index];
return 1;
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
struct D4rNgxParamLogRecord
{
unsigned char operation;
unsigned char valueType;
unsigned short reserved;
unsigned int result;
unsigned long long value;
char name[64];
};
static_assert(sizeof(D4rNgxParamLogRecord) == 80);
+411
View File
@@ -0,0 +1,411 @@
#define WIN32_LEAN_AND_MEAN
#include <wchar.h>
#include <windows.h>
#include <vulkan/vulkan.h>
#include "nvsdk_ngx_vk.h"
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
template <typename Target, typename Source> Target copy_function_pointer(Source source)
{
static_assert(sizeof(Target) == sizeof(Source), "function pointer sizes differ");
Target target{};
std::memcpy(&target, &source, sizeof(target));
return target;
}
template <typename Function> Function load_export(HMODULE module, const char* name)
{
FARPROC raw = GetProcAddress(module, name);
return raw ? copy_function_pointer<Function>(raw) : nullptr;
}
static const char* ngx_result_name(NVSDK_NGX_Result result)
{
switch (result)
{
case NVSDK_NGX_Result_Success:
return "Success";
case NVSDK_NGX_Result_FAIL_FeatureNotSupported:
return "FeatureNotSupported";
case NVSDK_NGX_Result_FAIL_PlatformError:
return "PlatformError";
case NVSDK_NGX_Result_FAIL_InvalidParameter:
return "InvalidParameter";
case NVSDK_NGX_Result_FAIL_NotInitialized:
return "NotInitialized";
case NVSDK_NGX_Result_FAIL_UnableToInitializeFeature:
return "UnableToInitializeFeature";
case NVSDK_NGX_Result_FAIL_OutOfDate:
return "OutOfDate";
default:
return "Other";
}
}
static void print_ngx_result(const char* stage, NVSDK_NGX_Result result)
{
std::printf("%s: result=0x%08x (%s)\n", stage, static_cast<unsigned int>(result), ngx_result_name(result));
}
static std::wstring utf8_to_wide(const char* input)
{
const int required = MultiByteToWideChar(CP_UTF8, 0, input, -1, nullptr, 0);
if (required <= 1)
return {};
std::wstring output(static_cast<size_t>(required), L'\0');
MultiByteToWideChar(CP_UTF8, 0, input, -1, output.data(), required);
output.resize(static_cast<size_t>(required - 1));
return output;
}
int main(int argc, char** argv)
{
if (argc != 4)
{
std::fprintf(stderr, "usage: ngx_vulkan_probe.exe PATH_TO_NGX_CORE_DLL PATH_TO_NVNGX_DLSS_DLL PATH_TO_WRITABLE_APPDATA\n");
return 2;
}
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
const char* ngxCorePath = argv[1];
const char* featureDllPath = argv[2];
std::wstring appData = utf8_to_wide(argv[3]);
std::wstring dllSearchDirectory = utf8_to_wide(featureDllPath);
if (appData.empty() || dllSearchDirectory.empty())
{
std::fprintf(stderr, "Invalid UTF-8 feature-DLL path or appdata path argument\n");
return 2;
}
const size_t separator = dllSearchDirectory.find_last_of(L"\\/");
if (separator == std::wstring::npos)
dllSearchDirectory = L".";
else
dllSearchDirectory.resize(separator);
const wchar_t* featurePaths[] = {dllSearchDirectory.c_str()};
NVSDK_NGX_FeatureCommonInfo featureCommonInfo{};
featureCommonInfo.PathListInfo.Path = featurePaths;
featureCommonInfo.PathListInfo.Length = 1;
HMODULE ngxCore = LoadLibraryA(ngxCorePath);
if (ngxCore == nullptr)
{
std::fprintf(stderr, "LoadLibraryA(%s) failed: GetLastError=%lu\n", ngxCorePath, GetLastError());
return 1;
}
HMODULE ngxFeature = LoadLibraryA(featureDllPath);
if (ngxFeature == nullptr)
{
std::fprintf(stderr, "LoadLibraryA(%s) failed: GetLastError=%lu\n", featureDllPath, GetLastError());
FreeLibrary(ngxCore);
return 1;
}
std::printf("Loaded NGX core: %s\n", ngxCorePath);
std::printf("DLSS SR feature search directory: %ls\n", dllSearchDirectory.c_str());
auto getRequirements = load_export<decltype(&NVSDK_NGX_VULKAN_GetFeatureRequirements)>(
ngxFeature, "NVSDK_NGX_VULKAN_GetFeatureRequirements");
auto getInstanceExtensions = load_export<decltype(&NVSDK_NGX_VULKAN_GetFeatureInstanceExtensionRequirements)>(
ngxFeature, "NVSDK_NGX_VULKAN_GetFeatureInstanceExtensionRequirements");
auto getDeviceExtensions = load_export<decltype(&NVSDK_NGX_VULKAN_GetFeatureDeviceExtensionRequirements)>(
ngxFeature, "NVSDK_NGX_VULKAN_GetFeatureDeviceExtensionRequirements");
if (getRequirements == nullptr || getInstanceExtensions == nullptr || getDeviceExtensions == nullptr)
{
std::fprintf(stderr, "The DLL does not expose the required Vulkan discovery exports\n");
FreeLibrary(ngxFeature);
FreeLibrary(ngxCore);
return 1;
}
NVSDK_NGX_FeatureDiscoveryInfo discovery{};
discovery.SDKVersion = static_cast<NVSDK_NGX_Version>(0x13); // Version resource in tested DLLs reports API 0x13.
discovery.FeatureID = NVSDK_NGX_Feature_SuperSampling;
discovery.Identifier.IdentifierType = NVSDK_NGX_Application_Identifier_Type_Application_Id;
discovery.Identifier.v.ApplicationId = 241534723ULL; // NGXAppId value in this DLL's VERSIONINFO resource.
discovery.ApplicationDataPath = appData.c_str();
discovery.FeatureInfo = &featureCommonInfo;
std::printf("Feature discovery: SDK=0x13 appId=%llu searchPath=%ls appData=%ls\n",
discovery.Identifier.v.ApplicationId, dllSearchDirectory.c_str(), appData.c_str());
uint32_t instanceExtensionCount = 0;
VkExtensionProperties* instanceExtensions = nullptr;
NVSDK_NGX_Result result = getInstanceExtensions(&discovery, &instanceExtensionCount, &instanceExtensions);
print_ngx_result("VULKAN_GetFeatureInstanceExtensionRequirements", result);
std::printf("required instance extensions: %u\n", instanceExtensionCount);
for (uint32_t i = 0; i < instanceExtensionCount && instanceExtensions != nullptr; ++i)
std::printf(" %s (%u)\n", instanceExtensions[i].extensionName, instanceExtensions[i].specVersion);
HMODULE vulkan = LoadLibraryA("vulkan-1.dll");
if (vulkan == nullptr)
{
std::fprintf(stderr, "LoadLibraryA(vulkan-1.dll) failed: GetLastError=%lu\n", GetLastError());
FreeLibrary(ngxCore);
FreeLibrary(ngxFeature);
return 1;
}
auto getInstanceProcAddr = load_export<PFN_vkGetInstanceProcAddr>(vulkan, "vkGetInstanceProcAddr");
if (getInstanceProcAddr == nullptr)
{
std::fprintf(stderr, "vulkan-1.dll has no vkGetInstanceProcAddr export\n");
FreeLibrary(vulkan);
FreeLibrary(ngxFeature);
FreeLibrary(ngxCore);
return 1;
}
auto createInstance = copy_function_pointer<PFN_vkCreateInstance>(
getInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"));
if (createInstance == nullptr)
{
std::fprintf(stderr, "vkCreateInstance is unavailable\n");
FreeLibrary(vulkan);
FreeLibrary(ngxFeature);
FreeLibrary(ngxCore);
return 1;
}
VkApplicationInfo application{};
application.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
application.pApplicationName = "d4r NGX Vulkan discovery probe";
application.applicationVersion = 1;
application.pEngineName = "d4r";
application.engineVersion = 1;
application.apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &application;
std::vector<const char*> enabledInstanceExtensions;
for (uint32_t i = 0; i < instanceExtensionCount && instanceExtensions != nullptr; ++i)
enabledInstanceExtensions.push_back(instanceExtensions[i].extensionName);
createInfo.enabledExtensionCount = static_cast<uint32_t>(enabledInstanceExtensions.size());
createInfo.ppEnabledExtensionNames = enabledInstanceExtensions.data();
VkInstance instance = VK_NULL_HANDLE;
const VkResult vkCreateResult = createInstance(&createInfo, nullptr, &instance);
std::printf("vkCreateInstance: result=%d\n", static_cast<int>(vkCreateResult));
if (vkCreateResult != VK_SUCCESS)
{
FreeLibrary(vulkan);
FreeLibrary(ngxFeature);
FreeLibrary(ngxCore);
return 1;
}
auto enumeratePhysicalDevices = copy_function_pointer<PFN_vkEnumeratePhysicalDevices>(
getInstanceProcAddr(instance, "vkEnumeratePhysicalDevices"));
auto enumerateDeviceExtensions = copy_function_pointer<PFN_vkEnumerateDeviceExtensionProperties>(
getInstanceProcAddr(instance, "vkEnumerateDeviceExtensionProperties"));
auto getPhysicalDeviceProperties = copy_function_pointer<PFN_vkGetPhysicalDeviceProperties>(
getInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties"));
auto destroyInstance = copy_function_pointer<PFN_vkDestroyInstance>(
getInstanceProcAddr(instance, "vkDestroyInstance"));
auto getQueueFamilyProperties = copy_function_pointer<PFN_vkGetPhysicalDeviceQueueFamilyProperties>(
getInstanceProcAddr(instance, "vkGetPhysicalDeviceQueueFamilyProperties"));
auto createDevice = copy_function_pointer<PFN_vkCreateDevice>(getInstanceProcAddr(instance, "vkCreateDevice"));
auto getDeviceProcAddr = copy_function_pointer<PFN_vkGetDeviceProcAddr>(
getInstanceProcAddr(instance, "vkGetDeviceProcAddr"));
if (enumeratePhysicalDevices == nullptr || enumerateDeviceExtensions == nullptr ||
getPhysicalDeviceProperties == nullptr || destroyInstance == nullptr || getQueueFamilyProperties == nullptr ||
createDevice == nullptr || getDeviceProcAddr == nullptr)
{
std::fprintf(stderr, "Required Vulkan instance functions are unavailable\n");
FreeLibrary(vulkan);
FreeLibrary(ngxFeature);
FreeLibrary(ngxCore);
return 1;
}
uint32_t deviceCount = 0;
VkResult enumerateResult = enumeratePhysicalDevices(instance, &deviceCount, nullptr);
if (enumerateResult != VK_SUCCESS || deviceCount == 0)
{
std::fprintf(stderr, "vkEnumeratePhysicalDevices(count) failed: result=%d count=%u\n",
static_cast<int>(enumerateResult), deviceCount);
destroyInstance(instance, nullptr);
FreeLibrary(vulkan);
FreeLibrary(ngxFeature);
FreeLibrary(ngxCore);
return 1;
}
std::vector<VkPhysicalDevice> devices(deviceCount);
enumerateResult = enumeratePhysicalDevices(instance, &deviceCount, devices.data());
if (enumerateResult != VK_SUCCESS)
{
std::fprintf(stderr, "vkEnumeratePhysicalDevices(list) failed: result=%d\n", static_cast<int>(enumerateResult));
destroyInstance(instance, nullptr);
FreeLibrary(vulkan);
FreeLibrary(ngxCore);
return 1;
}
VkPhysicalDevice amdDevice = VK_NULL_HANDLE;
for (VkPhysicalDevice device : devices)
{
VkPhysicalDeviceProperties properties{};
getPhysicalDeviceProperties(device, &properties);
std::printf("Vulkan device: vendor=0x%04x device=0x%04x type=%d name=%s\n", properties.vendorID,
properties.deviceID, static_cast<int>(properties.deviceType), properties.deviceName);
if (properties.vendorID == 0x1002 && amdDevice == VK_NULL_HANDLE)
amdDevice = device;
}
if (amdDevice == VK_NULL_HANDLE)
{
std::fprintf(stderr, "No AMD Vulkan physical device was enumerated\n");
destroyInstance(instance, nullptr);
FreeLibrary(vulkan);
FreeLibrary(ngxCore);
return 1;
}
NVSDK_NGX_FeatureRequirement requirement{};
result = getRequirements(instance, amdDevice, &discovery, &requirement);
print_ngx_result("VULKAN_GetFeatureRequirements(DLSS SR)", result);
if (result == NVSDK_NGX_Result_Success)
{
std::printf("feature support flags: 0x%08x\n", static_cast<unsigned int>(requirement.FeatureSupported));
std::printf("minimum hardware architecture: %u\n", requirement.MinHWArchitecture);
std::printf("minimum OS version: %s\n", requirement.MinOSVersion);
}
uint32_t deviceExtensionCount = 0;
VkExtensionProperties* deviceExtensions = nullptr;
VkExtensionProperties deviceExtensionStorage[32]{};
deviceExtensionCount = 32;
deviceExtensions = deviceExtensionStorage;
result = getDeviceExtensions(instance, amdDevice, &discovery, &deviceExtensionCount, &deviceExtensions);
print_ngx_result("VULKAN_GetFeatureDeviceExtensionRequirements", result);
if (result != NVSDK_NGX_Result_Success)
deviceExtensionCount = 0;
std::printf("required device extensions: %u, properties pointer=%p\n", deviceExtensionCount,
static_cast<void*>(deviceExtensions));
for (uint32_t i = 0; i < deviceExtensionCount && i < 32 && deviceExtensions != nullptr; ++i)
std::printf(" %s (%u)\n", deviceExtensions[i].extensionName, deviceExtensions[i].specVersion);
uint32_t availableExtensionCount = 0;
VkResult extensionResult = enumerateDeviceExtensions(amdDevice, nullptr, &availableExtensionCount, nullptr);
std::vector<VkExtensionProperties> availableExtensions(availableExtensionCount);
if (extensionResult == VK_SUCCESS && availableExtensionCount > 0)
extensionResult = enumerateDeviceExtensions(amdDevice, nullptr, &availableExtensionCount, availableExtensions.data());
std::printf("AMD device-extension enumeration: result=%d count=%u\n", static_cast<int>(extensionResult),
availableExtensionCount);
for (uint32_t i = 0; i < deviceExtensionCount && i < 32 && deviceExtensions != nullptr; ++i)
{
bool supported = false;
for (const auto& extension : availableExtensions)
{
if (std::strcmp(extension.extensionName, deviceExtensions[i].extensionName) == 0)
{
supported = true;
break;
}
}
std::printf(" requirement availability: %-42s %s\n", deviceExtensions[i].extensionName,
supported ? "supported" : "missing");
}
std::vector<const char*> enabledDeviceExtensions;
for (uint32_t i = 0; i < deviceExtensionCount && i < 32 && deviceExtensions != nullptr; ++i)
{
for (const auto& extension : availableExtensions)
{
if (std::strcmp(extension.extensionName, deviceExtensions[i].extensionName) == 0)
{
enabledDeviceExtensions.push_back(deviceExtensions[i].extensionName);
break;
}
}
}
uint32_t queueFamilyCount = 0;
getQueueFamilyProperties(amdDevice, &queueFamilyCount, nullptr);
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
getQueueFamilyProperties(amdDevice, &queueFamilyCount, queueFamilies.data());
uint32_t queueFamilyIndex = UINT32_MAX;
for (uint32_t i = 0; i < queueFamilyCount; ++i)
{
const VkQueueFlags requiredQueueFlags = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT;
if ((queueFamilies[i].queueFlags & requiredQueueFlags) == requiredQueueFlags && queueFamilies[i].queueCount > 0)
{
queueFamilyIndex = i;
break;
}
}
if (queueFamilyIndex == UINT32_MAX)
{
std::fprintf(stderr, "No graphics+compute Vulkan queue family was found\n");
destroyInstance(instance, nullptr);
FreeLibrary(vulkan);
FreeLibrary(ngxFeature);
FreeLibrary(ngxCore);
return 1;
}
const float queuePriority = 1.0f;
VkDeviceQueueCreateInfo queueInfo{};
queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueInfo.queueFamilyIndex = queueFamilyIndex;
queueInfo.queueCount = 1;
queueInfo.pQueuePriorities = &queuePriority;
VkDeviceCreateInfo deviceInfo{};
deviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
deviceInfo.queueCreateInfoCount = 1;
deviceInfo.pQueueCreateInfos = &queueInfo;
deviceInfo.enabledExtensionCount = static_cast<uint32_t>(enabledDeviceExtensions.size());
deviceInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
VkDevice device = VK_NULL_HANDLE;
const VkResult vkDeviceResult = createDevice(amdDevice, &deviceInfo, nullptr, &device);
std::printf("vkCreateDevice (only available NGX-required extensions): result=%d enabled-extension-count=%u\n",
static_cast<int>(vkDeviceResult), deviceInfo.enabledExtensionCount);
if (vkDeviceResult != VK_SUCCESS)
{
destroyInstance(instance, nullptr);
FreeLibrary(vulkan);
FreeLibrary(ngxFeature);
FreeLibrary(ngxCore);
return 1;
}
auto destroyDevice = copy_function_pointer<PFN_vkDestroyDevice>(getDeviceProcAddr(device, "vkDestroyDevice"));
auto init = load_export<decltype(&NVSDK_NGX_VULKAN_Init)>(ngxCore, "NVSDK_NGX_VULKAN_Init");
auto shutdown = load_export<decltype(&NVSDK_NGX_VULKAN_Shutdown1)>(ngxCore, "NVSDK_NGX_VULKAN_Shutdown1");
if (init == nullptr || destroyDevice == nullptr)
{
std::fprintf(stderr, "NGX Vulkan init or Vulkan device cleanup export is unavailable\n");
if (destroyDevice != nullptr)
destroyDevice(device, nullptr);
destroyInstance(instance, nullptr);
FreeLibrary(vulkan);
FreeLibrary(ngxFeature);
FreeLibrary(ngxCore);
return 1;
}
result = init(discovery.Identifier.v.ApplicationId, discovery.ApplicationDataPath, instance, amdDevice, device,
getInstanceProcAddr, getDeviceProcAddr, &featureCommonInfo, discovery.SDKVersion);
print_ngx_result("VULKAN_Init(DLSS SR)", result);
if (result == NVSDK_NGX_Result_Success && shutdown != nullptr)
{
NVSDK_NGX_Result shutdownResult = shutdown(device);
print_ngx_result("VULKAN_Shutdown1", shutdownResult);
}
destroyDevice(device, nullptr);
destroyInstance(instance, nullptr);
FreeLibrary(vulkan);
FreeLibrary(ngxFeature);
FreeLibrary(ngxCore);
return 0;
}
File diff suppressed because it is too large. Load diff
+508
View File
@@ -0,0 +1,508 @@
#include <dlfcn.h>
#include <hip/hip_runtime_api.h>
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iterator>
#include <numeric>
#include <vector>
using CUresult = int;
using CUdevice = int;
using CUdeviceptr = uint64_t;
using CUcontext = void*;
using CUmodule = void*;
using CUfunction = void*;
using CUarray = void*;
using CUtexObject = uint64_t;
struct CUDA_ARRAY_DESCRIPTOR
{
size_t Width;
size_t Height;
uint32_t Format;
uint32_t NumChannels;
};
static_assert(sizeof(CUDA_ARRAY_DESCRIPTOR) == 24);
struct CUDA_MEMCPY2D
{
size_t srcXInBytes;
size_t srcY;
uint32_t srcMemoryType;
uint32_t srcAlignment;
const void* srcHost;
CUdeviceptr srcDevice;
CUarray srcArray;
size_t srcPitch;
size_t dstXInBytes;
size_t dstY;
uint32_t dstMemoryType;
uint32_t dstAlignment;
void* dstHost;
CUdeviceptr dstDevice;
CUarray dstArray;
size_t dstPitch;
size_t WidthInBytes;
size_t Height;
};
static_assert(sizeof(CUDA_MEMCPY2D) == 128);
struct CUDA_RESOURCE_DESC
{
uint32_t resType;
uint32_t alignment;
union
{
struct { CUarray hArray; } array;
int reserved[32];
} res;
uint32_t flags;
uint32_t reserved;
};
static_assert(sizeof(CUDA_RESOURCE_DESC) == 144);
struct CUDA_TEXTURE_DESC
{
uint32_t addressMode[3];
uint32_t filterMode;
uint32_t flags;
uint32_t maxAnisotropy;
uint32_t mipmapFilterMode;
float mipmapLevelBias;
float minMipmapLevelClamp;
float maxMipmapLevelClamp;
float borderColor[4];
int32_t reserved[12];
};
static_assert(sizeof(CUDA_TEXTURE_DESC) == 104);
struct HistogramParams
{
uint32_t offsetX;
uint32_t offsetY;
uint32_t textureWidth;
uint32_t textureHeight;
uint32_t maxX;
uint32_t maxY;
float valueScale;
float weightScale;
float logBias;
uint32_t alignmentPadding;
CUdeviceptr histogram;
CUtexObject texture;
uint8_t flags[8];
};
static_assert(sizeof(HistogramParams) == 64);
static_assert(offsetof(HistogramParams, histogram) == 40);
static_assert(offsetof(HistogramParams, texture) == 48);
static_assert(offsetof(HistogramParams, flags) == 56);
template <typename Function> static Function load_function(void* library, const char* name)
{
void* raw = dlsym(library, name);
Function function{};
static_assert(sizeof(function) == sizeof(raw));
std::memcpy(&function, &raw, sizeof(function));
return function;
}
static void print_cuda_error(const char* stage, CUresult result,
CUresult (*get_error_string)(CUresult, const char**) = nullptr)
{
const char* message = nullptr;
if (get_error_string != nullptr)
get_error_string(result, &message);
std::printf("%s: CUDA result=%d", stage, result);
if (message != nullptr)
std::printf(" (%s)", message);
std::putchar('\n');
}
int main(int argc, char** argv)
{
if (argc != 2)
{
std::fprintf(stderr, "usage: replay_dlss_histogram PATH_TO_LOCALLY_CAPTURED_DLSS_FATBIN\n");
return 2;
}
std::ifstream input(argv[1], std::ios::binary);
if (!input)
{
std::perror("Could not open captured fatbin");
return 2;
}
std::vector<unsigned char> image((std::istreambuf_iterator<char>(input)),
std::istreambuf_iterator<char>());
if (image.size() < 16 || std::memcmp(image.data(), "\x50\xed\x55\xba", 4) != 0)
{
std::fprintf(stderr, "Input is not a CUDA FATBIN_MAGIC v1 payload\n");
return 2;
}
void* library = dlopen("libcuda.so", RTLD_NOW | RTLD_LOCAL);
if (library == nullptr)
{
std::fprintf(stderr, "dlopen(libcuda.so) failed: %s\n", dlerror());
return 1;
}
using InitFn = CUresult (*)(unsigned int);
using DeviceGetFn = CUresult (*)(CUdevice*, int);
using DeviceGetNameFn = CUresult (*)(char*, int, CUdevice);
using ContextCreateFn = CUresult (*)(CUcontext*, unsigned int, CUdevice);
using ContextDestroyFn = CUresult (*)(CUcontext);
using MemAllocFn = CUresult (*)(CUdeviceptr*, size_t);
using MemFreeFn = CUresult (*)(CUdeviceptr);
using CopyHtoDFn = CUresult (*)(CUdeviceptr, const void*, size_t);
using CopyDtoHFn = CUresult (*)(void*, CUdeviceptr, size_t);
using ArrayCreateFn = CUresult (*)(CUarray*, const CUDA_ARRAY_DESCRIPTOR*);
using ArrayDestroyFn = CUresult (*)(CUarray);
using Copy2DFn = CUresult (*)(const CUDA_MEMCPY2D*);
using TextureCreateFn = CUresult (*)(CUtexObject*, const CUDA_RESOURCE_DESC*,
const CUDA_TEXTURE_DESC*, const void*);
using TextureDestroyFn = CUresult (*)(CUtexObject);
using ModuleLoadDataFn = CUresult (*)(CUmodule*, const void*);
using ModuleGetFunctionFn = CUresult (*)(CUfunction*, CUmodule, const char*);
using LaunchKernelFn = CUresult (*)(CUfunction, unsigned int, unsigned int, unsigned int,
unsigned int, unsigned int, unsigned int, unsigned int,
void*, void**, void**);
using ContextSynchronizeFn = CUresult (*)(void);
using ModuleUnloadFn = CUresult (*)(CUmodule);
using GetErrorStringFn = CUresult (*)(CUresult, const char**);
const InitFn init = load_function<InitFn>(library, "cuInit");
const DeviceGetFn device_get = load_function<DeviceGetFn>(library, "cuDeviceGet");
const DeviceGetNameFn device_get_name = load_function<DeviceGetNameFn>(library, "cuDeviceGetName");
const ContextCreateFn context_create = load_function<ContextCreateFn>(library, "cuCtxCreate_v2");
const ContextDestroyFn context_destroy = load_function<ContextDestroyFn>(library, "cuCtxDestroy_v2");
const MemAllocFn mem_alloc = load_function<MemAllocFn>(library, "cuMemAlloc_v2");
const MemFreeFn mem_free = load_function<MemFreeFn>(library, "cuMemFree_v2");
const CopyHtoDFn copy_htod = load_function<CopyHtoDFn>(library, "cuMemcpyHtoD_v2");
const CopyDtoHFn copy_dtoh = load_function<CopyDtoHFn>(library, "cuMemcpyDtoH_v2");
const ArrayCreateFn array_create = load_function<ArrayCreateFn>(library, "cuArrayCreate_v2");
const ArrayDestroyFn array_destroy = load_function<ArrayDestroyFn>(library, "cuArrayDestroy");
const Copy2DFn copy_2d = load_function<Copy2DFn>(library, "cuMemcpy2D_v2");
const TextureCreateFn texture_create = load_function<TextureCreateFn>(library, "cuTexObjectCreate");
const TextureDestroyFn texture_destroy = load_function<TextureDestroyFn>(library, "cuTexObjectDestroy");
const ModuleLoadDataFn module_load = load_function<ModuleLoadDataFn>(library, "cuModuleLoadData");
const ModuleGetFunctionFn module_get_function =
load_function<ModuleGetFunctionFn>(library, "cuModuleGetFunction");
const LaunchKernelFn launch_kernel = load_function<LaunchKernelFn>(library, "cuLaunchKernel");
const ContextSynchronizeFn context_synchronize =
load_function<ContextSynchronizeFn>(library, "cuCtxSynchronize");
const ModuleUnloadFn module_unload = load_function<ModuleUnloadFn>(library, "cuModuleUnload");
const GetErrorStringFn get_error_string = load_function<GetErrorStringFn>(library, "cuGetErrorString");
if (init == nullptr || device_get == nullptr || context_create == nullptr || context_destroy == nullptr ||
mem_alloc == nullptr || mem_free == nullptr || copy_htod == nullptr || copy_dtoh == nullptr ||
array_create == nullptr || array_destroy == nullptr || copy_2d == nullptr || texture_create == nullptr ||
texture_destroy == nullptr || module_load == nullptr || module_get_function == nullptr ||
launch_kernel == nullptr || context_synchronize == nullptr || module_unload == nullptr)
{
std::fprintf(stderr, "ZLUDA libcuda.so is missing one or more array/texture/driver exports\n");
dlclose(library);
return 1;
}
CUresult result = init(0);
if (result != 0)
{
print_cuda_error("cuInit", result, get_error_string);
dlclose(library);
return 1;
}
CUdevice device = 0;
result = device_get(&device, 0);
if (result != 0)
{
print_cuda_error("cuDeviceGet", result, get_error_string);
dlclose(library);
return 1;
}
char device_name[256] = {0};
if (device_get_name != nullptr && device_get_name(device_name, sizeof(device_name), device) == 0)
std::printf("ZLUDA CUDA device: %s\n", device_name);
CUcontext context = nullptr;
result = context_create(&context, 0, device);
if (result != 0)
{
print_cuda_error("cuCtxCreate_v2", result, get_error_string);
dlclose(library);
return 1;
}
CUmodule module = nullptr;
result = module_load(&module, image.data());
print_cuda_error("cuModuleLoadData(official DLSS histogram fatbin)", result, get_error_string);
if (result != 0)
{
context_destroy(context);
dlclose(library);
return 1;
}
CUfunction kernel = nullptr;
result = module_get_function(&kernel, module, "cuda_histogram_kernel");
print_cuda_error("cuModuleGetFunction(cuda_histogram_kernel)", result, get_error_string);
if (result != 0)
{
module_unload(module);
context_destroy(context);
dlclose(library);
return 1;
}
constexpr unsigned int width = 32;
constexpr unsigned int height = 16;
constexpr size_t pixel_count = width * height;
std::vector<float> pixels(pixel_count * 4);
for (size_t i = 0; i < pixel_count; ++i)
{
pixels[i * 4 + 0] = 0.5f;
pixels[i * 4 + 1] = 0.5f;
pixels[i * 4 + 2] = 0.5f;
pixels[i * 4 + 3] = 1.0f;
}
CUDA_ARRAY_DESCRIPTOR array_desc{};
array_desc.Width = width;
array_desc.Height = height;
array_desc.Format = 32; // CU_AD_FORMAT_FLOAT
array_desc.NumChannels = 4;
CUarray array = nullptr;
result = array_create(&array, &array_desc);
print_cuda_error("cuArrayCreate_v2(RGBA32F 32x32)", result, get_error_string);
if (result != 0)
{
module_unload(module);
context_destroy(context);
dlclose(library);
return 1;
}
CUDA_MEMCPY2D copy{};
copy.srcMemoryType = 1; // CU_MEMORYTYPE_HOST
copy.srcHost = pixels.data();
copy.srcPitch = width * 4 * sizeof(float);
copy.dstMemoryType = 3; // CU_MEMORYTYPE_ARRAY
copy.dstArray = array;
copy.WidthInBytes = width * 4 * sizeof(float);
copy.Height = height;
result = copy_2d(&copy);
if (result != 0)
{
print_cuda_error("cuMemcpy2D_v2(texture upload)", result, get_error_string);
array_destroy(array);
module_unload(module);
context_destroy(context);
dlclose(library);
return 1;
}
std::vector<float> array_readback(pixels.size(), 0.0f);
CUDA_MEMCPY2D copy_back{};
copy_back.srcMemoryType = 3; // CU_MEMORYTYPE_ARRAY
copy_back.srcArray = array;
copy_back.dstMemoryType = 1; // CU_MEMORYTYPE_HOST
copy_back.dstHost = array_readback.data();
copy_back.dstPitch = width * 4 * sizeof(float);
copy_back.WidthInBytes = width * 4 * sizeof(float);
copy_back.Height = height;
result = copy_2d(&copy_back);
const bool array_upload_valid = result == 0 && array_readback == pixels;
std::printf("RGBA32F texture upload/readback: %s\n", array_upload_valid ? "PASS" : "FAIL");
if (!array_upload_valid)
{
print_cuda_error("cuMemcpy2D_v2(texture readback)", result, get_error_string);
array_destroy(array);
module_unload(module);
context_destroy(context);
dlclose(library);
return 1;
}
CUDA_RESOURCE_DESC resource_desc{};
resource_desc.resType = 0; // CU_RESOURCE_TYPE_ARRAY
resource_desc.res.array.hArray = array;
CUDA_TEXTURE_DESC texture_desc{};
texture_desc.addressMode[0] = 1; // clamp
texture_desc.addressMode[1] = 1;
texture_desc.addressMode[2] = 1;
texture_desc.filterMode = 0; // point
texture_desc.maxAnisotropy = 0;
texture_desc.mipmapFilterMode = 0;
HIP_RESOURCE_DESC hip_resource_desc{};
HIP_TEXTURE_DESC hip_texture_desc{};
static_assert(sizeof(HIP_RESOURCE_DESC) == sizeof(CUDA_RESOURCE_DESC));
static_assert(sizeof(HIP_TEXTURE_DESC) == sizeof(CUDA_TEXTURE_DESC));
std::memcpy(&hip_resource_desc, &resource_desc, sizeof(hip_resource_desc));
std::memcpy(&hip_texture_desc, &texture_desc, sizeof(hip_texture_desc));
CUtexObject texture = 0;
result = texture_create(&texture, &resource_desc, &texture_desc, nullptr);
print_cuda_error("ZLUDA cuTexObjectCreate(RGBA32F test texture)", result, get_error_string);
bool used_direct_hip_texture = false;
hipTextureObject_t hip_texture = 0;
if (result != 0)
{
hipError_t direct_hip_texture_result = hipTexObjectCreate(
&hip_texture, &hip_resource_desc, &hip_texture_desc, nullptr);
std::printf("Direct HIP texture-object fallback: %s (%d)\n",
hipGetErrorName(direct_hip_texture_result), static_cast<int>(direct_hip_texture_result));
if (direct_hip_texture_result == hipSuccess)
{
texture = static_cast<CUtexObject>(reinterpret_cast<uintptr_t>(hip_texture));
result = 0;
used_direct_hip_texture = true;
}
}
if (result != 0)
{
array_destroy(array);
module_unload(module);
context_destroy(context);
dlclose(library);
return 1;
}
const char texture_test_ptx[] =
".version 6.5\n"
".target sm_30\n"
".address_size 64\n"
".visible .entry d4r_texture_probe(\n"
".param .u64 out_ptr, .param .u64 texobj, .param .s32 coord_x, .param .s32 coord_y) {\n"
".reg .u64 %rd<3>; .reg .s32 %ix, %iy; .reg .f32 %f<4>;\n"
"ld.param.u64 %rd1, [out_ptr]; ld.param.u64 %rd2, [texobj];\n"
"ld.param.s32 %ix, [coord_x]; ld.param.s32 %iy, [coord_y];\n"
"tex.2d.v4.f32.s32 {%f0,%f1,%f2,%f3}, [%rd2,{%ix,%iy}];\n"
"st.global.v4.f32 [%rd1], {%f0,%f1,%f2,%f3}; ret; }\n";
CUmodule texture_test_module = nullptr;
CUfunction texture_test_kernel = nullptr;
CUdeviceptr texture_test_output = 0;
float sampled_pixel[4] = {};
CUresult texture_test_result = module_load(&texture_test_module, texture_test_ptx);
if (texture_test_result == 0)
texture_test_result = module_get_function(&texture_test_kernel, texture_test_module, "d4r_texture_probe");
if (texture_test_result == 0)
texture_test_result = mem_alloc(&texture_test_output, sizeof(sampled_pixel));
int sample_x = static_cast<int>(width / 2);
int sample_y = static_cast<int>(height / 2);
void* texture_test_parameters[] = {&texture_test_output, &texture, &sample_x, &sample_y};
if (texture_test_result == 0)
texture_test_result = launch_kernel(texture_test_kernel, 1, 1, 1, 1, 1, 1, 0,
nullptr, texture_test_parameters, nullptr);
if (texture_test_result == 0)
texture_test_result = context_synchronize();
if (texture_test_result == 0)
texture_test_result = copy_dtoh(sampled_pixel, texture_test_output, sizeof(sampled_pixel));
const float* expected_pixel = &pixels[(sample_y * width + sample_x) * 4];
const bool texture_probe_pass = texture_test_result == 0 &&
std::equal(std::begin(sampled_pixel), std::end(sampled_pixel), expected_pixel,
[](float actual, float expected) { return std::abs(actual - expected) < 0.001f; });
std::printf("ZLUDA texture-object readback: %s (sample=%.3f,%.3f,%.3f,%.3f expected=%.3f,%.3f,%.3f,%.3f)\n",
texture_probe_pass ? "PASS" : "FAIL", sampled_pixel[0], sampled_pixel[1],
sampled_pixel[2], sampled_pixel[3], expected_pixel[0], expected_pixel[1],
expected_pixel[2], expected_pixel[3]);
if (texture_test_result != 0)
print_cuda_error("ZLUDA texture-object probe", texture_test_result, get_error_string);
if (texture_test_output != 0)
mem_free(texture_test_output);
if (texture_test_module != nullptr)
module_unload(texture_test_module);
std::vector<float> zero_histogram(128, 0.0f);
CUdeviceptr histogram_device = 0;
result = mem_alloc(&histogram_device, zero_histogram.size() * sizeof(float));
if (result == 0)
result = copy_htod(histogram_device, zero_histogram.data(), zero_histogram.size() * sizeof(float));
if (result != 0)
{
print_cuda_error("allocate/zero histogram output", result, get_error_string);
if (used_direct_hip_texture)
(void)hipDestroyTextureObject(hip_texture);
else
texture_destroy(texture);
array_destroy(array);
module_unload(module);
context_destroy(context);
dlclose(library);
return 1;
}
HistogramParams params{};
params.offsetX = 0;
params.offsetY = 0;
params.textureWidth = width - 1;
params.textureHeight = height - 1;
params.maxX = width - 1;
params.maxY = height - 1;
params.valueScale = 1.0f;
params.weightScale = 1.0f;
params.logBias = 1.5f;
params.histogram = histogram_device;
params.texture = texture;
void* kernel_parameters[] = {&params};
result = launch_kernel(kernel, 4, 1, 1, 8, 16, 1, 0, nullptr, kernel_parameters, nullptr);
print_cuda_error("cuLaunchKernel(cuda_histogram_kernel)", result, get_error_string);
if (result == 0)
result = context_synchronize();
std::vector<float> histogram(128, 0.0f);
if (result == 0)
result = copy_dtoh(histogram.data(), histogram_device, histogram.size() * sizeof(float));
if (result != 0)
{
print_cuda_error("synchronize/read histogram output", result, get_error_string);
mem_free(histogram_device);
if (used_direct_hip_texture)
(void)hipDestroyTextureObject(hip_texture);
else
texture_destroy(texture);
array_destroy(array);
module_unload(module);
context_destroy(context);
dlclose(library);
return 1;
}
const float total = std::accumulate(histogram.begin(), histogram.end(), 0.0f);
const size_t nan_count = static_cast<size_t>(std::count_if(histogram.begin(), histogram.end(),
[](float value) { return std::isnan(value); }));
const size_t infinite_count = static_cast<size_t>(std::count_if(histogram.begin(), histogram.end(),
[](float value) { return std::isinf(value); }));
const auto nonzero = std::count_if(histogram.begin(), histogram.end(), [](float value) {
return std::isfinite(value) && value != 0.0f;
});
auto top = std::max_element(histogram.begin(), histogram.end());
const size_t top_bin = static_cast<size_t>(top - histogram.begin());
std::printf("Histogram output: total=%.6f nonzero_bins=%zu NaNs=%zu Infs=%zu top_bin=%zu top_value=%.6f\n",
total, static_cast<size_t>(nonzero), nan_count, infinite_count, top_bin, *top);
const bool finite = std::all_of(histogram.begin(), histogram.end(), [](float value) {
return std::isfinite(value) && value >= 0.0f;
});
const float expected_half = static_cast<float>(pixel_count) / 2.0f;
const bool expected_bins = std::abs(histogram[63] - expected_half) < 0.01f &&
std::abs(histogram[64] - expected_half) < 0.01f;
std::printf("Expected histogram bins 63/64: %.6f / %.6f (expected %.6f each)\n",
histogram[63], histogram[64], expected_half);
const bool valid = texture_probe_pass && finite &&
std::abs(total - static_cast<float>(pixel_count)) < 0.1f && nonzero == 2 && expected_bins;
std::printf("Official DLSS histogram kernel test: %s (uniform RGBA32F %ux%u synthetic input)\n",
valid ? "PASS" : "FAIL", width, height);
mem_free(histogram_device);
if (used_direct_hip_texture)
(void)hipDestroyTextureObject(hip_texture);
else
texture_destroy(texture);
array_destroy(array);
module_unload(module);
context_destroy(context);
dlclose(library);
return valid ? 0 : 1;
}
+233
View File
@@ -0,0 +1,233 @@
#include <dlfcn.h>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iterator>
#include <vector>
using CUresult = int;
using CUdevice = int;
using CUdeviceptr = uint64_t;
using CUcontext = void*;
using CUmodule = void*;
using CUfunction = void*;
using CUstream = void*;
template <typename Function> static Function load_function(void* library, const char* name)
{
void* raw = dlsym(library, name);
Function function{};
static_assert(sizeof(function) == sizeof(raw));
std::memcpy(&function, &raw, sizeof(function));
return function;
}
static void print_cuda_error(const char* stage, CUresult result,
CUresult (*get_error_string)(CUresult, const char**) = nullptr)
{
const char* message = nullptr;
if (get_error_string != nullptr)
get_error_string(result, &message);
std::printf("%s: CUDA result=%d", stage, result);
if (message != nullptr)
std::printf(" (%s)", message);
std::putchar('\n');
}
int main(int argc, char** argv)
{
if (argc != 2 && argc != 3)
{
std::fprintf(stderr,
"usage: replay_dlss_kernel PATH_TO_LOCALLY_CAPTURED_DLSS_FATBIN [KERNEL_NAME]\n");
return 2;
}
const char* kernel_name = argc == 3 ? argv[2] : "cuda_clear_buffer_kernel";
std::ifstream input(argv[1], std::ios::binary);
if (!input)
{
std::perror("Could not open captured fatbin");
return 2;
}
std::vector<unsigned char> image((std::istreambuf_iterator<char>(input)),
std::istreambuf_iterator<char>());
if (image.size() < 16 || std::memcmp(image.data(), "\x50\xed\x55\xba", 4) != 0)
{
std::fprintf(stderr, "Input is not a CUDA FATBIN_MAGIC v1 payload\n");
return 2;
}
void* library = dlopen("libcuda.so", RTLD_NOW | RTLD_LOCAL);
if (library == nullptr)
{
std::fprintf(stderr, "dlopen(libcuda.so) failed: %s\n", dlerror());
return 1;
}
using InitFn = CUresult (*)(unsigned int);
using DeviceGetFn = CUresult (*)(CUdevice*, int);
using ContextCreateFn = CUresult (*)(CUcontext*, unsigned int, CUdevice);
using ContextDestroyFn = CUresult (*)(CUcontext);
using MemAllocFn = CUresult (*)(CUdeviceptr*, size_t);
using MemFreeFn = CUresult (*)(CUdeviceptr);
using CopyHtoDFn = CUresult (*)(CUdeviceptr, const void*, size_t);
using CopyDtoHFn = CUresult (*)(void*, CUdeviceptr, size_t);
using ModuleLoadDataFn = CUresult (*)(CUmodule*, const void*);
using ModuleGetFunctionFn = CUresult (*)(CUfunction*, CUmodule, const char*);
using LaunchKernelFn = CUresult (*)(CUfunction, unsigned int, unsigned int, unsigned int,
unsigned int, unsigned int, unsigned int, unsigned int,
CUstream, void**, void**);
using ContextSynchronizeFn = CUresult (*)(void);
using ModuleUnloadFn = CUresult (*)(CUmodule);
using GetErrorStringFn = CUresult (*)(CUresult, const char**);
const InitFn init = load_function<InitFn>(library, "cuInit");
const DeviceGetFn device_get = load_function<DeviceGetFn>(library, "cuDeviceGet");
const ContextCreateFn context_create = load_function<ContextCreateFn>(library, "cuCtxCreate_v2");
const ContextDestroyFn context_destroy = load_function<ContextDestroyFn>(library, "cuCtxDestroy_v2");
const MemAllocFn mem_alloc = load_function<MemAllocFn>(library, "cuMemAlloc_v2");
const MemFreeFn mem_free = load_function<MemFreeFn>(library, "cuMemFree_v2");
const CopyHtoDFn copy_htod = load_function<CopyHtoDFn>(library, "cuMemcpyHtoD_v2");
const CopyDtoHFn copy_dtoh = load_function<CopyDtoHFn>(library, "cuMemcpyDtoH_v2");
const ModuleLoadDataFn module_load = load_function<ModuleLoadDataFn>(library, "cuModuleLoadData");
const ModuleGetFunctionFn module_get_function =
load_function<ModuleGetFunctionFn>(library, "cuModuleGetFunction");
const LaunchKernelFn launch_kernel = load_function<LaunchKernelFn>(library, "cuLaunchKernel");
const ContextSynchronizeFn context_synchronize =
load_function<ContextSynchronizeFn>(library, "cuCtxSynchronize");
const ModuleUnloadFn module_unload = load_function<ModuleUnloadFn>(library, "cuModuleUnload");
const GetErrorStringFn get_error_string = load_function<GetErrorStringFn>(library, "cuGetErrorString");
if (init == nullptr || device_get == nullptr || context_create == nullptr || context_destroy == nullptr ||
mem_alloc == nullptr || mem_free == nullptr || copy_htod == nullptr || copy_dtoh == nullptr ||
module_load == nullptr || module_get_function == nullptr || launch_kernel == nullptr ||
context_synchronize == nullptr || module_unload == nullptr)
{
std::fprintf(stderr, "ZLUDA libcuda.so is missing one or more driver exports\n");
dlclose(library);
return 1;
}
CUresult result = init(0);
if (result != 0)
{
print_cuda_error("cuInit", result, get_error_string);
dlclose(library);
return 1;
}
CUdevice device = 0;
result = device_get(&device, 0);
if (result != 0)
{
print_cuda_error("cuDeviceGet", result, get_error_string);
dlclose(library);
return 1;
}
CUcontext context = nullptr;
result = context_create(&context, 0, device);
if (result != 0)
{
print_cuda_error("cuCtxCreate_v2", result, get_error_string);
dlclose(library);
return 1;
}
CUmodule module = nullptr;
result = module_load(&module, image.data());
print_cuda_error("cuModuleLoadData(official DLSS fatbin)", result, get_error_string);
if (result != 0)
{
context_destroy(context);
dlclose(library);
return 1;
}
CUfunction kernel = nullptr;
result = module_get_function(&kernel, module, kernel_name);
char lookup_stage[256];
std::snprintf(lookup_stage, sizeof(lookup_stage), "cuModuleGetFunction(%s)", kernel_name);
print_cuda_error(lookup_stage, result, get_error_string);
if (result != 0)
{
module_unload(module);
context_destroy(context);
dlclose(library);
return 1;
}
if (std::strcmp(kernel_name, "cuda_clear_buffer_kernel") != 0)
{
std::printf("Kernel-resolution-only probe: PASS\n");
module_unload(module);
context_destroy(context);
dlclose(library);
return 0;
}
unsigned int buffer_width = 64;
unsigned int buffer_height = 32;
unsigned int clear_width = 32;
unsigned int clear_height = 16;
unsigned int clear_value = 0x5a;
unsigned int debug_checks = 0;
const size_t byte_count = buffer_width * buffer_height;
std::vector<unsigned char> host(byte_count, 0x11);
CUdeviceptr device_buffer = 0;
result = mem_alloc(&device_buffer, byte_count);
if (result == 0)
result = copy_htod(device_buffer, host.data(), byte_count);
if (result != 0)
{
print_cuda_error("allocate and initialize test buffer", result, get_error_string);
module_unload(module);
context_destroy(context);
dlclose(library);
return 1;
}
unsigned int x = 0;
unsigned int y = 0;
uint64_t pointer_argument = device_buffer;
void* parameters[] = {&buffer_width, &buffer_height, &x, &y, &clear_width, &clear_height,
&clear_value, &debug_checks, &pointer_argument};
result = launch_kernel(kernel, 2, 1, 1, 16, 16, 1, 0, nullptr, parameters, nullptr);
print_cuda_error("cuLaunchKernel(cuda_clear_buffer_kernel)", result, get_error_string);
if (result == 0)
result = context_synchronize();
if (result == 0)
result = copy_dtoh(host.data(), device_buffer, byte_count);
if (result != 0)
{
print_cuda_error("synchronize and read back kernel output", result, get_error_string);
mem_free(device_buffer);
module_unload(module);
context_destroy(context);
dlclose(library);
return 1;
}
bool valid = true;
size_t changed = 0;
for (unsigned int row = 0; row < buffer_height; ++row)
{
for (unsigned int column = 0; column < buffer_width; ++column)
{
const unsigned char expected = row < clear_height && column < clear_width ? clear_value : 0x11;
const unsigned char actual = host[row * buffer_width + column];
if (actual != expected)
valid = false;
if (actual != 0x11)
++changed;
}
}
std::printf("Official DLSS kernel test: %s (changed %zu bytes in a %ux%u region; surrounding bytes preserved)\n",
valid ? "PASS" : "FAIL", changed, clear_width, clear_height);
mem_free(device_buffer);
module_unload(module);
context_destroy(context);
dlclose(library);
return valid ? 0 : 1;
}
+194
View File
@@ -0,0 +1,194 @@
// Replays one captured DLSS kernel launch outside Wine with buffer-only
// arguments, using resource dumps from the Wine bridge's
// D4R_CUDA_LAUNCH_DUMP_DIR instrumentation.
//
// usage: replay_dlss_layer MODULE KERNEL GX,GY,GZ BX,BY,BZ SHARED OUTDIR WORD...
// MODULE PTX text or CUDA fatbin
// WORD one 8-byte packed-argument word: either a hex literal (0x...) or
// @FILE#0xOFFSET, which uploads FILE into a fresh device buffer (once
// per distinct FILE) and passes that buffer's address plus OFFSET.
// After the launch every uploaded buffer is written to OUTDIR/<basename>.out.
#include <dlfcn.h>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iterator>
#include <string>
#include <vector>
using CUresult = int;
using CUdevice = int;
using CUcontext = void*;
using CUmodule = void*;
using CUfunction = void*;
using CUstream = void*;
using CUdeviceptr = uint64_t;
template <typename Function> static Function load_function(void* library, const char* name)
{
void* raw = dlsym(library, name);
Function function{};
static_assert(sizeof(function) == sizeof(raw));
std::memcpy(&function, &raw, sizeof(function));
return function;
}
static bool read_file(const std::string& path, std::vector<char>& data)
{
std::ifstream input(path, std::ios::binary);
if (!input)
return false;
data.assign(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());
return true;
}
static bool parse_triple(const char* text, unsigned int values[3])
{
return std::sscanf(text, "%u,%u,%u", &values[0], &values[1], &values[2]) == 3;
}
struct Buffer
{
std::string path;
CUdeviceptr device = 0;
size_t bytes = 0;
};
int main(int argc, char** argv)
{
if (argc < 8)
{
std::fprintf(stderr, "usage: %s MODULE KERNEL GX,GY,GZ BX,BY,BZ SHARED OUTDIR WORD...\n", argv[0]);
return 2;
}
unsigned int grid[3], block[3];
if (!parse_triple(argv[3], grid) || !parse_triple(argv[4], block))
{
std::fprintf(stderr, "grid and block must be X,Y,Z\n");
return 2;
}
const unsigned int shared = static_cast<unsigned int>(std::strtoul(argv[5], nullptr, 0));
const std::string outputDirectory = argv[6];
std::vector<char> module;
if (!read_file(argv[1], module))
{
std::fprintf(stderr, "cannot read %s\n", argv[1]);
return 1;
}
module.push_back('\0'); // PTX text must be NUL terminated; harmless for fatbins
void* library = dlopen("libcuda.so", RTLD_NOW | RTLD_LOCAL);
if (library == nullptr)
{
std::fprintf(stderr, "dlopen(libcuda.so) failed: %s\n", dlerror());
return 1;
}
const auto init = load_function<CUresult (*)(unsigned int)>(library, "cuInit");
const auto deviceGet = load_function<CUresult (*)(CUdevice*, int)>(library, "cuDeviceGet");
const auto contextCreate = load_function<CUresult (*)(CUcontext*, unsigned int, CUdevice)>(library, "cuCtxCreate_v2");
const auto moduleLoad = load_function<CUresult (*)(CUmodule*, const void*)>(library, "cuModuleLoadData");
const auto moduleGetFunction = load_function<CUresult (*)(CUfunction*, CUmodule, const char*)>(library, "cuModuleGetFunction");
const auto alloc = load_function<CUresult (*)(CUdeviceptr*, size_t)>(library, "cuMemAlloc_v2");
const auto copyHtoD = load_function<CUresult (*)(CUdeviceptr, const void*, size_t)>(library, "cuMemcpyHtoD_v2");
const auto copyDtoH = load_function<CUresult (*)(void*, CUdeviceptr, size_t)>(library, "cuMemcpyDtoH_v2");
const auto launch = load_function<CUresult (*)(CUfunction, unsigned int, unsigned int, unsigned int, unsigned int,
unsigned int, unsigned int, unsigned int, CUstream, void**, void**)>(
library, "cuLaunchKernel");
const auto synchronize = load_function<CUresult (*)(void)>(library, "cuCtxSynchronize");
if (!init || !deviceGet || !contextCreate || !moduleLoad || !moduleGetFunction || !alloc ||
!copyHtoD || !copyDtoH || !launch || !synchronize)
{
std::fprintf(stderr, "ZLUDA libcuda.so is missing a required export\n");
return 1;
}
CUdevice device = 0;
CUcontext context = nullptr;
CUmodule loaded = nullptr;
CUfunction function = nullptr;
CUresult result = init(0);
if (result == 0)
result = deviceGet(&device, 0);
if (result == 0)
result = contextCreate(&context, 0, device);
if (result == 0)
result = moduleLoad(&loaded, module.data());
if (result == 0)
result = moduleGetFunction(&function, loaded, argv[2]);
if (result != 0)
{
std::fprintf(stderr, "module setup for %s failed: CUDA result=%d\n", argv[2], result);
return 1;
}
std::vector<Buffer> buffers;
std::vector<uint64_t> words;
for (int index = 7; index < argc; ++index)
{
const std::string word = argv[index];
if (word.empty() || word[0] != '@')
{
words.push_back(std::strtoull(word.c_str(), nullptr, 0));
continue;
}
const size_t plus = word.rfind('#');
const std::string path = word.substr(1, plus == std::string::npos ? std::string::npos : plus - 1);
const uint64_t offset = plus == std::string::npos ? 0 : std::strtoull(word.c_str() + plus + 1, nullptr, 0);
Buffer* buffer = nullptr;
for (Buffer& existing : buffers)
if (existing.path == path)
buffer = &existing;
if (buffer == nullptr)
{
std::vector<char> contents;
if (!read_file(path, contents))
{
std::fprintf(stderr, "cannot read %s\n", path.c_str());
return 1;
}
Buffer created;
created.path = path;
created.bytes = contents.size();
result = alloc(&created.device, created.bytes);
if (result == 0)
result = copyHtoD(created.device, contents.data(), created.bytes);
if (result != 0)
{
std::fprintf(stderr, "upload of %s failed: CUDA result=%d\n", path.c_str(), result);
return 1;
}
buffers.push_back(created);
buffer = &buffers.back();
}
words.push_back(buffer->device + offset);
}
size_t argumentBytes = words.size() * sizeof(uint64_t);
void* extra[] = {reinterpret_cast<void*>(1), words.data(), reinterpret_cast<void*>(2), &argumentBytes,
nullptr};
result = launch(function, grid[0], grid[1], grid[2], block[0], block[1], block[2], shared, nullptr,
nullptr, extra);
if (result == 0)
result = synchronize();
std::printf("%s grid=%u,%u,%u block=%u,%u,%u: CUDA result=%d\n", argv[2], grid[0], grid[1], grid[2],
block[0], block[1], block[2], result);
if (result != 0)
return 1;
for (const Buffer& buffer : buffers)
{
std::vector<char> contents(buffer.bytes);
result = copyDtoH(contents.data(), buffer.device, buffer.bytes);
const size_t slash = buffer.path.rfind('/');
const std::string name = outputDirectory + "/" +
(slash == std::string::npos ? buffer.path : buffer.path.substr(slash + 1)) + ".out";
std::ofstream output(name, std::ios::binary);
output.write(contents.data(), static_cast<std::streamsize>(contents.size()));
std::printf("wrote %s (%zu bytes, CUDA result=%d)\n", name.c_str(), buffer.bytes, result);
}
return 0;
}
+355
View File
@@ -0,0 +1,355 @@
#include <dlfcn.h>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <vector>
using CUresult = int;
using CUdevice = int;
using CUcontext = void*;
using CUarray = void*;
using CUmodule = void*;
using CUfunction = void*;
using CUstream = void*;
using CUsurfObject = uint64_t;
struct CUDA_ARRAY3D_DESCRIPTOR
{
size_t Width;
size_t Height;
size_t Depth;
uint32_t Format;
uint32_t NumChannels;
uint32_t Flags;
};
static_assert(sizeof(CUDA_ARRAY3D_DESCRIPTOR) == 40);
struct CUDA_RESOURCE_DESC
{
uint32_t resType;
uint32_t alignment;
union
{
struct { CUarray hArray; } array;
int reserved[32];
} res;
uint32_t flags;
uint32_t reserved;
};
static_assert(sizeof(CUDA_RESOURCE_DESC) == 144);
struct CUDA_MEMCPY2D
{
size_t srcXInBytes;
size_t srcY;
uint32_t srcMemoryType;
uint32_t srcAlignment;
const void* srcHost;
uint64_t srcDevice;
CUarray srcArray;
size_t srcPitch;
size_t dstXInBytes;
size_t dstY;
uint32_t dstMemoryType;
uint32_t dstAlignment;
void* dstHost;
uint64_t dstDevice;
CUarray dstArray;
size_t dstPitch;
size_t WidthInBytes;
size_t Height;
};
static_assert(sizeof(CUDA_MEMCPY2D) == 128);
template <typename Function> static Function load_function(void* library, const char* name)
{
void* raw = dlsym(library, name);
Function function{};
static_assert(sizeof(function) == sizeof(raw));
std::memcpy(&function, &raw, sizeof(function));
return function;
}
static CUresult report(const char* stage, CUresult result,
CUresult (*get_error_string)(CUresult, const char**) = nullptr)
{
const char* message = nullptr;
if (get_error_string != nullptr)
get_error_string(result, &message);
std::printf("%s: CUDA result=%d", stage, result);
if (message != nullptr)
std::printf(" (%s)", message);
std::putchar('\n');
return result;
}
// One unformatted 4-byte store (sust.b.2d.v2.b16.zero, and the same bytes via
// sust.b.2d.b32.zero) into a surface of the given CUDA array format. X is a byte offset (unformatted surface addressing). The expected
// result is the fill pattern with the four stored bytes replaced at X, or the
// untouched fill pattern when X is out of bounds.
struct StoreCase
{
const char* name;
uint32_t format; // CUarray_format
uint32_t channels;
uint32_t channelBytes;
uint32_t x; // byte offset
uint32_t y;
uint16_t value0;
uint16_t value1;
uint8_t fill[16]; // one pixel of initial contents
};
int main()
{
constexpr unsigned int width = 16;
constexpr unsigned int height = 16;
const char* ptx = R"PTX(
.version 8.9
.target sm_89
.address_size 64
.visible .entry d4r_surface_half_store_probe(
.param .b64 surface,
.param .b32 x,
.param .b32 y,
.param .b32 packed
)
{
.reg .b64 %rd<2>;
.reg .b16 %rs<2>;
.reg .b32 %r<4>;
ld.param.b64 %rd1, [surface];
ld.param.b32 %r0, [x];
ld.param.b32 %r1, [y];
ld.param.b32 %r2, [packed];
mov.b32 {%rs0, %rs1}, %r2;
sust.b.2d.v2.b16.zero [%rd1, {%r0,%r1}], {%rs0,%rs1};
ret;
}
.visible .entry d4r_surface_b32_store_probe(
.param .b64 surface,
.param .b32 x,
.param .b32 y,
.param .b32 packed
)
{
.reg .b64 %rd<2>;
.reg .b32 %r<4>;
ld.param.b64 %rd1, [surface];
ld.param.b32 %r0, [x];
ld.param.b32 %r1, [y];
ld.param.b32 %r2, [packed];
sust.b.2d.b32.zero [%rd1, {%r0,%r1}], %r2;
ret;
}
)PTX";
// Fill values: 0x3400 is half 0.25, 0x3e800000 is float 0.25.
const StoreCase cases[] = {
{"R16F store spans two pixels", 0x10, 1, 2, 4 * 2, 6, 0x3555, 0xc248,
{0x00, 0x34}},
{"R16F row start pair", 0x10, 1, 2, 0, 0, 0x8001, 0x7bff,
{0x00, 0x34}},
{"R16F row end pair", 0x10, 1, 2, width * 2 - 4, 15, 0xfc00, 0x0400,
{0x00, 0x34}},
{"R16 uint store spans two pixels", 0x02, 1, 2, 10 * 2, 3, 0xfffe, 0x1234,
{0x11, 0x22}},
{"R8 uint store spans four pixels", 0x01, 1, 1, 8, 5, 0x80ff, 0x017f,
{0x5a}},
{"RG16F in-bounds", 0x10, 2, 2, 5 * 4, 7, 0x3555, 0xc248,
{0x00, 0x34, 0x00, 0x34}},
{"RG16F subnormal/max", 0x10, 2, 2, 9 * 4, 3, 0x0001, 0x7bff,
{0x00, 0x34, 0x00, 0x34}},
{"RG16F negative zero/min normal", 0x10, 2, 2, 0, 0, 0x8000, 0x0400,
{0x00, 0x34, 0x00, 0x34}},
{"RG16F .zero out-of-bounds", 0x10, 2, 2, width * 4, 7, 0x3555, 0xc248,
{0x00, 0x34, 0x00, 0x34}},
{"RGBA16F upper half-pixel", 0x10, 4, 2, 3 * 8 + 4, 2, 0x3555, 0xc248,
{0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34}},
{"RGBA16F lower half-pixel", 0x10, 4, 2, 11 * 8, 12, 0xbc00, 0x4900,
{0x00, 0x34, 0x00, 0x34, 0x00, 0x34, 0x00, 0x34}},
{"R32F packed halves", 0x20, 1, 4, 6 * 4, 1, 0x3555, 0xc248,
{0x00, 0x00, 0x80, 0x3e}},
{"RG16 uint", 0x02, 2, 2, 4 * 4, 9, 0xfffe, 0x1234,
{0x11, 0x11, 0x22, 0x22}},
{"R32F denormal bits", 0x20, 1, 4, 2 * 4, 4, 0x0001, 0x0000,
{0x00, 0x00, 0x80, 0x3e}},
{"RG16F infinity/quiet NaN", 0x10, 2, 2, 13 * 4, 13, 0xfc00, 0x7e00,
{0x00, 0x34, 0x00, 0x34}},
};
void* library = dlopen("libcuda.so", RTLD_NOW | RTLD_LOCAL);
if (library == nullptr)
{
std::fprintf(stderr, "dlopen(libcuda.so) failed: %s\n", dlerror());
return 1;
}
using InitFn = CUresult (*)(unsigned int);
using DeviceGetFn = CUresult (*)(CUdevice*, int);
using ContextCreateFn = CUresult (*)(CUcontext*, unsigned int, CUdevice);
using ContextDestroyFn = CUresult (*)(CUcontext);
using Array3DCreateFn = CUresult (*)(CUarray*, const CUDA_ARRAY3D_DESCRIPTOR*);
using ArrayDestroyFn = CUresult (*)(CUarray);
using SurfaceCreateFn = CUresult (*)(CUsurfObject*, const CUDA_RESOURCE_DESC*);
using SurfaceDestroyFn = CUresult (*)(CUsurfObject);
using Copy2DFn = CUresult (*)(const CUDA_MEMCPY2D*);
using ModuleLoadFn = CUresult (*)(CUmodule*, const void*);
using ModuleGetFunctionFn = CUresult (*)(CUfunction*, CUmodule, const char*);
using LaunchKernelFn = CUresult (*)(CUfunction, unsigned int, unsigned int, unsigned int,
unsigned int, unsigned int, unsigned int, unsigned int,
CUstream, void**, void**);
using ContextSynchronizeFn = CUresult (*)(void);
using ModuleUnloadFn = CUresult (*)(CUmodule);
using GetErrorStringFn = CUresult (*)(CUresult, const char**);
const auto init = load_function<InitFn>(library, "cuInit");
const auto deviceGet = load_function<DeviceGetFn>(library, "cuDeviceGet");
const auto contextCreate = load_function<ContextCreateFn>(library, "cuCtxCreate_v2");
const auto contextDestroy = load_function<ContextDestroyFn>(library, "cuCtxDestroy_v2");
const auto array3DCreate = load_function<Array3DCreateFn>(library, "cuArray3DCreate_v2");
const auto arrayDestroy = load_function<ArrayDestroyFn>(library, "cuArrayDestroy");
const auto surfaceCreate = load_function<SurfaceCreateFn>(library, "cuSurfObjectCreate");
const auto surfaceDestroy = load_function<SurfaceDestroyFn>(library, "cuSurfObjectDestroy");
const auto copy2D = load_function<Copy2DFn>(library, "cuMemcpy2D_v2");
const auto moduleLoad = load_function<ModuleLoadFn>(library, "cuModuleLoadData");
const auto moduleGetFunction = load_function<ModuleGetFunctionFn>(library, "cuModuleGetFunction");
const auto launchKernel = load_function<LaunchKernelFn>(library, "cuLaunchKernel");
const auto synchronize = load_function<ContextSynchronizeFn>(library, "cuCtxSynchronize");
const auto moduleUnload = load_function<ModuleUnloadFn>(library, "cuModuleUnload");
const auto getErrorString = load_function<GetErrorStringFn>(library, "cuGetErrorString");
if (init == nullptr || deviceGet == nullptr || contextCreate == nullptr || contextDestroy == nullptr ||
array3DCreate == nullptr || arrayDestroy == nullptr || surfaceCreate == nullptr ||
surfaceDestroy == nullptr || copy2D == nullptr || moduleLoad == nullptr ||
moduleGetFunction == nullptr || launchKernel == nullptr || synchronize == nullptr ||
moduleUnload == nullptr)
{
std::fprintf(stderr, "ZLUDA libcuda.so is missing one or more half-surface probe exports\n");
dlclose(library);
return 1;
}
CUcontext context = nullptr;
CUmodule module = nullptr;
CUfunction functions[2] = {};
const char* functionNames[2] = {"sust.b.2d.v2.b16", "sust.b.2d.b32"};
CUresult result = report("cuInit", init(0), getErrorString);
CUdevice device = 0;
if (result == 0)
result = report("cuDeviceGet", deviceGet(&device, 0), getErrorString);
if (result == 0)
result = report("cuCtxCreate_v2", contextCreate(&context, 0, device), getErrorString);
if (result == 0)
result = report("cuModuleLoadData(half surface store PTX)", moduleLoad(&module, ptx), getErrorString);
if (result == 0)
result = report("cuModuleGetFunction(d4r_surface_half_store_probe)",
moduleGetFunction(&functions[0], module, "d4r_surface_half_store_probe"), getErrorString);
if (result == 0)
result = report("cuModuleGetFunction(d4r_surface_b32_store_probe)",
moduleGetFunction(&functions[1], module, "d4r_surface_b32_store_probe"), getErrorString);
size_t passed = 0;
size_t total = 2 * sizeof(cases) / sizeof(cases[0]);
for (size_t functionIndex = 0; functionIndex < 2; ++functionIndex)
for (const StoreCase& storeCase : cases)
{
CUfunction function = functions[functionIndex];
if (result != 0)
break;
const size_t pixelBytes = static_cast<size_t>(storeCase.channels) * storeCase.channelBytes;
const size_t rowBytes = width * pixelBytes;
std::vector<uint8_t> expected(rowBytes * height);
for (size_t offset = 0; offset < expected.size(); ++offset)
expected[offset] = storeCase.fill[offset % pixelBytes];
std::vector<uint8_t> surfaceBytes = expected;
const uint8_t stored[4] = {
static_cast<uint8_t>(storeCase.value0), static_cast<uint8_t>(storeCase.value0 >> 8),
static_cast<uint8_t>(storeCase.value1), static_cast<uint8_t>(storeCase.value1 >> 8)};
if (storeCase.x + 4 <= rowBytes && storeCase.y < height)
std::memcpy(expected.data() + storeCase.y * rowBytes + storeCase.x, stored, 4);
CUarray array = nullptr;
CUsurfObject surface = 0;
const CUDA_ARRAY3D_DESCRIPTOR arrayDescriptor{width, height, 0, storeCase.format,
storeCase.channels, 2 /* SURFACE_LDST */};
CUresult caseResult = array3DCreate(&array, &arrayDescriptor);
if (caseResult == 0)
{
CUDA_RESOURCE_DESC resource{};
resource.resType = 0; // CUDA_RESOURCE_TYPE_ARRAY
resource.res.array.hArray = array;
caseResult = surfaceCreate(&surface, &resource);
}
if (caseResult == 0)
{
CUDA_MEMCPY2D upload{};
upload.srcMemoryType = 1; // CUDA_MEMORYTYPE_HOST
upload.srcHost = surfaceBytes.data();
upload.srcPitch = rowBytes;
upload.dstMemoryType = 3; // CUDA_MEMORYTYPE_ARRAY
upload.dstArray = array;
upload.WidthInBytes = rowBytes;
upload.Height = height;
caseResult = copy2D(&upload);
}
if (caseResult == 0)
{
uint64_t surfaceArgument = surface;
uint32_t x = storeCase.x;
uint32_t y = storeCase.y;
uint32_t packed = storeCase.value0 | (static_cast<uint32_t>(storeCase.value1) << 16);
void* arguments[] = {&surfaceArgument, &x, &y, &packed};
caseResult = launchKernel(function, 1, 1, 1, 1, 1, 1, 0, nullptr, arguments, nullptr);
if (caseResult == 0)
caseResult = synchronize();
}
if (caseResult == 0)
{
CUDA_MEMCPY2D readback{};
readback.srcMemoryType = 3; // CUDA_MEMORYTYPE_ARRAY
readback.srcArray = array;
readback.dstMemoryType = 1; // CUDA_MEMORYTYPE_HOST
readback.dstHost = surfaceBytes.data();
readback.dstPitch = rowBytes;
readback.WidthInBytes = rowBytes;
readback.Height = height;
caseResult = copy2D(&readback);
}
size_t mismatches = 0;
for (size_t offset = 0; caseResult == 0 && offset < expected.size(); offset += pixelBytes)
{
if (std::memcmp(surfaceBytes.data() + offset, expected.data() + offset, pixelBytes) == 0)
continue;
++mismatches;
std::printf(" pixel (%zu,%zu) got", (offset % rowBytes) / pixelBytes, offset / rowBytes);
for (size_t b = 0; b < pixelBytes; ++b)
std::printf(" %02x", surfaceBytes[offset + b]);
std::printf(" expected");
for (size_t b = 0; b < pixelBytes; ++b)
std::printf(" %02x", expected[offset + b]);
std::putchar('\n');
}
const bool casePassed = caseResult == 0 && mismatches == 0;
passed += casePassed ? 1 : 0;
std::printf("%s %s: %s (CUDA result=%d, mismatched pixels=%zu)\n", functionNames[functionIndex],
storeCase.name, casePassed ? "PASS" : "FAIL", caseResult, mismatches);
if (surface != 0)
surfaceDestroy(surface);
if (array != nullptr)
arrayDestroy(array);
}
if (result == 0)
std::printf("sust.b.2d unformatted 4-byte surface store tests: %zu/%zu passed\n", passed, total);
const int exitCode = result == 0 && passed == total ? 0 : 1;
if (module != nullptr)
moduleUnload(module);
if (context != nullptr)
contextDestroy(context);
dlclose(library);
return exitCode;
}
+859
View File
@@ -0,0 +1,859 @@
#include <dlfcn.h>
#include <array>
#include <bit>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <vector>
using CUresult = int;
using CUdevice = int;
using CUcontext = void*;
using CUarray = void*;
using CUmodule = void*;
using CUfunction = void*;
using CUstream = void*;
using CUdeviceptr = uint64_t;
using CUsurfObject = uint64_t;
struct CUDA_ARRAY3D_DESCRIPTOR
{
size_t Width;
size_t Height;
size_t Depth;
uint32_t Format;
uint32_t NumChannels;
uint32_t Flags;
};
static_assert(sizeof(CUDA_ARRAY3D_DESCRIPTOR) == 40);
struct CUDA_ARRAY_DESCRIPTOR
{
size_t Width;
size_t Height;
uint32_t Format;
uint32_t NumChannels;
};
static_assert(sizeof(CUDA_ARRAY_DESCRIPTOR) == 24);
struct CUDA_RESOURCE_DESC
{
uint32_t resType;
uint32_t alignment;
union
{
struct { CUarray hArray; } array;
int reserved[32];
} res;
uint32_t flags;
uint32_t reserved;
};
static_assert(sizeof(CUDA_RESOURCE_DESC) == 144);
struct CUDA_MEMCPY2D
{
size_t srcXInBytes;
size_t srcY;
uint32_t srcMemoryType;
uint32_t srcAlignment;
const void* srcHost;
uint64_t srcDevice;
CUarray srcArray;
size_t srcPitch;
size_t dstXInBytes;
size_t dstY;
uint32_t dstMemoryType;
uint32_t dstAlignment;
void* dstHost;
uint64_t dstDevice;
CUarray dstArray;
size_t dstPitch;
size_t WidthInBytes;
size_t Height;
};
static_assert(sizeof(CUDA_MEMCPY2D) == 128);
template <typename Function> static Function load_function(void* library, const char* name)
{
void* raw = dlsym(library, name);
Function function{};
static_assert(sizeof(function) == sizeof(raw));
std::memcpy(&function, &raw, sizeof(function));
return function;
}
static CUresult report(const char* stage, CUresult result,
CUresult (*get_error_string)(CUresult, const char**) = nullptr)
{
const char* message = nullptr;
if (get_error_string != nullptr)
get_error_string(result, &message);
std::printf("%s: CUDA result=%d", stage, result);
if (message != nullptr)
std::printf(" (%s)", message);
std::putchar('\n');
return result;
}
int main()
{
constexpr unsigned int width = 16;
constexpr unsigned int height = 16;
constexpr unsigned int x = 5;
constexpr unsigned int y = 7;
constexpr uint32_t value = 0x3f800000u; // float 1.0
constexpr uint32_t outOfBoundsX = width;
const char* ptx = R"PTX(
.version 8.9
.target sm_89
.address_size 64
.visible .entry d4r_surface_store_probe(
.param .b64 surface,
.param .b32 x,
.param .b32 y,
.param .b32 value
)
{
.reg .b64 %rd<2>;
.reg .b32 %r<4>;
ld.param.b64 %rd1, [surface];
ld.param.b32 %r0, [x];
ld.param.b32 %r1, [y];
ld.param.b32 %r2, [value];
sust.p.2d.b32.zero [%rd1, {%r0,%r1}], {%r2};
ret;
}
.visible .entry d4r_surface_normalized_probe(
.param .b64 surface,
.param .b32 x,
.param .b32 y,
.param .f32 red,
.param .f32 green,
.param .f32 blue,
.param .f32 alpha
)
{
.reg .b64 %rd<2>;
.reg .b32 %r<2>;
.reg .f32 %f<4>;
ld.param.b64 %rd1, [surface];
ld.param.b32 %r0, [x];
ld.param.b32 %r1, [y];
ld.param.f32 %f0, [red];
ld.param.f32 %f1, [green];
ld.param.f32 %f2, [blue];
ld.param.f32 %f3, [alpha];
sust.p.2d.v4.b32.zero [%rd1, {%r0,%r1}], {%f0,%f1,%f2,%f3};
ret;
}
.visible .entry d4r_surface_integer_probe(
.param .b64 surface,
.param .b32 x,
.param .b32 y,
.param .b32 red,
.param .b32 green,
.param .b32 blue,
.param .b32 alpha
)
{
.reg .b64 %rd<2>;
.reg .b32 %r<6>;
ld.param.b64 %rd1, [surface];
ld.param.b32 %r0, [x];
ld.param.b32 %r1, [y];
ld.param.b32 %r2, [red];
ld.param.b32 %r3, [green];
ld.param.b32 %r4, [blue];
ld.param.b32 %r5, [alpha];
sust.p.2d.v4.b32.zero [%rd1, {%r0,%r1}], {%r2,%r3,%r4,%r5};
ret;
}
)PTX";
void* library = dlopen("libcuda.so", RTLD_NOW | RTLD_LOCAL);
if (library == nullptr)
{
std::fprintf(stderr, "dlopen(libcuda.so) failed: %s\n", dlerror());
return 1;
}
void* hipLibrary = dlopen("libamdhip64.so.7", RTLD_NOW | RTLD_LOCAL);
if (hipLibrary == nullptr)
{
std::fprintf(stderr, "dlopen(libamdhip64.so.7) failed: %s\n", dlerror());
dlclose(library);
return 1;
}
using InitFn = CUresult (*)(unsigned int);
using DeviceGetFn = CUresult (*)(CUdevice*, int);
using ContextCreateFn = CUresult (*)(CUcontext*, unsigned int, CUdevice);
using ContextDestroyFn = CUresult (*)(CUcontext);
using Array3DCreateFn = CUresult (*)(CUarray*, const CUDA_ARRAY3D_DESCRIPTOR*);
using ArrayCreateFn = CUresult (*)(CUarray*, const CUDA_ARRAY_DESCRIPTOR*);
using ArrayDestroyFn = CUresult (*)(CUarray);
using SurfaceCreateFn = CUresult (*)(CUsurfObject*, const CUDA_RESOURCE_DESC*);
using SurfaceDestroyFn = CUresult (*)(CUsurfObject);
using SurfaceGetDescFn = CUresult (*)(CUDA_RESOURCE_DESC*, CUsurfObject);
using Copy2DFn = CUresult (*)(const CUDA_MEMCPY2D*);
using MemAllocFn = CUresult (*)(CUdeviceptr*, size_t);
using MemFreeFn = CUresult (*)(CUdeviceptr);
using ModuleLoadFn = CUresult (*)(CUmodule*, const void*);
using ModuleGetFunctionFn = CUresult (*)(CUfunction*, CUmodule, const char*);
using LaunchKernelFn = CUresult (*)(CUfunction, unsigned int, unsigned int, unsigned int,
unsigned int, unsigned int, unsigned int, unsigned int,
CUstream, void**, void**);
using ContextSynchronizeFn = CUresult (*)(void);
using ModuleUnloadFn = CUresult (*)(CUmodule);
using GetErrorStringFn = CUresult (*)(CUresult, const char**);
const auto init = load_function<InitFn>(library, "cuInit");
const auto deviceGet = load_function<DeviceGetFn>(library, "cuDeviceGet");
const auto contextCreate = load_function<ContextCreateFn>(library, "cuCtxCreate_v2");
const auto contextDestroy = load_function<ContextDestroyFn>(library, "cuCtxDestroy_v2");
const auto array3DCreate = load_function<Array3DCreateFn>(library, "cuArray3DCreate_v2");
const auto arrayCreate = load_function<ArrayCreateFn>(library, "cuArrayCreate_v2");
const auto arrayDestroy = load_function<ArrayDestroyFn>(library, "cuArrayDestroy");
const auto surfaceCreate = load_function<SurfaceCreateFn>(library, "cuSurfObjectCreate");
const auto surfaceDestroy = load_function<SurfaceDestroyFn>(library, "cuSurfObjectDestroy");
const auto surfaceGetDesc = load_function<SurfaceGetDescFn>(library, "cuSurfObjectGetResourceDesc");
const auto copy2D = load_function<Copy2DFn>(library, "cuMemcpy2D_v2");
const auto memAlloc = load_function<MemAllocFn>(library, "cuMemAlloc_v2");
const auto memFree = load_function<MemFreeFn>(library, "cuMemFree_v2");
// Inspect ZLUDA's physical RGBA32F backing without invoking the public
// CUDA packed-format copy contract that this probe validates below.
const auto backingCopy2D = load_function<Copy2DFn>(hipLibrary, "hipMemcpyParam2D");
const auto moduleLoad = load_function<ModuleLoadFn>(library, "cuModuleLoadData");
const auto moduleGetFunction = load_function<ModuleGetFunctionFn>(library, "cuModuleGetFunction");
const auto launchKernel = load_function<LaunchKernelFn>(library, "cuLaunchKernel");
const auto synchronize = load_function<ContextSynchronizeFn>(library, "cuCtxSynchronize");
const auto moduleUnload = load_function<ModuleUnloadFn>(library, "cuModuleUnload");
const auto getErrorString = load_function<GetErrorStringFn>(library, "cuGetErrorString");
if (init == nullptr || deviceGet == nullptr || contextCreate == nullptr || contextDestroy == nullptr ||
array3DCreate == nullptr || arrayCreate == nullptr || arrayDestroy == nullptr || surfaceCreate == nullptr ||
surfaceDestroy == nullptr || surfaceGetDesc == nullptr || copy2D == nullptr || backingCopy2D == nullptr ||
memAlloc == nullptr || memFree == nullptr ||
moduleLoad == nullptr || moduleGetFunction == nullptr || launchKernel == nullptr ||
synchronize == nullptr || moduleUnload == nullptr)
{
std::fprintf(stderr, "ZLUDA libcuda.so is missing one or more surface probe exports\n");
dlclose(library);
return 1;
}
CUcontext context = nullptr;
CUarray array = nullptr;
CUsurfObject surface = 0;
CUmodule module = nullptr;
int exitCode = 1;
CUresult result = report("cuInit", init(0), getErrorString);
CUdevice device = 0;
if (result == 0)
result = report("cuDeviceGet", deviceGet(&device, 0), getErrorString);
if (result == 0)
result = report("cuCtxCreate_v2", contextCreate(&context, 0, device), getErrorString);
const CUDA_ARRAY3D_DESCRIPTOR arrayDescriptor{width, height, 0, 32, 1, 2};
if (result == 0)
result = report("cuArray3DCreate_v2(R32F; SURFACE_LDST)",
array3DCreate(&array, &arrayDescriptor), getErrorString);
if (result == 0)
{
CUDA_RESOURCE_DESC resource{};
resource.resType = 0; // CUDA_RESOURCE_TYPE_ARRAY
resource.res.array.hArray = array;
result = report("cuSurfObjectCreate", surfaceCreate(&surface, &resource), getErrorString);
}
if (result == 0)
{
CUDA_RESOURCE_DESC roundTrip{};
result = report("cuSurfObjectGetResourceDesc",
surfaceGetDesc(&roundTrip, surface), getErrorString);
if (result == 0)
std::printf("Surface descriptor round-trip: type=%u array=%p expected=%p\n",
roundTrip.resType, roundTrip.res.array.hArray, array);
}
std::vector<float> pixels(width * height, 0.25f);
if (result == 0)
{
CUDA_MEMCPY2D upload{};
upload.srcMemoryType = 1; // CUDA_MEMORYTYPE_HOST
upload.srcHost = pixels.data();
upload.srcPitch = width * sizeof(float);
upload.dstMemoryType = 3; // CUDA_MEMORYTYPE_ARRAY
upload.dstArray = array;
upload.WidthInBytes = width * sizeof(float);
upload.Height = height;
result = report("cuMemcpy2D_v2(upload test surface)", copy2D(&upload), getErrorString);
}
if (result == 0)
result = report("cuModuleLoadData(surface store PTX)", moduleLoad(&module, ptx), getErrorString);
CUfunction function = nullptr;
if (result == 0)
result = report("cuModuleGetFunction(d4r_surface_store_probe)",
moduleGetFunction(&function, module, "d4r_surface_store_probe"), getErrorString);
auto launchStore = [&](unsigned int px, unsigned int py, uint32_t word, const char* label) {
uint64_t surfaceArgument = surface;
void* arguments[] = {&surfaceArgument, &px, &py, &word};
CUresult launchResult = report(label,
launchKernel(function, 1, 1, 1, 1, 1, 1, 0, nullptr, arguments, nullptr), getErrorString);
if (launchResult == 0)
launchResult = report("cuCtxSynchronize", synchronize(), getErrorString);
return launchResult;
};
if (result == 0)
result = launchStore(x, y, value, "cuLaunchKernel(surface in-bounds write)");
if (result == 0)
result = launchStore(outOfBoundsX, y, value, "cuLaunchKernel(surface .zero out-of-bounds write)");
if (result == 0)
{
CUDA_MEMCPY2D readback{};
readback.srcMemoryType = 3; // CUDA_MEMORYTYPE_ARRAY
readback.srcArray = array;
readback.dstMemoryType = 1; // CUDA_MEMORYTYPE_HOST
readback.dstHost = pixels.data();
readback.dstPitch = width * sizeof(float);
readback.WidthInBytes = width * sizeof(float);
readback.Height = height;
result = report("cuMemcpy2D_v2(read back surface)", copy2D(&readback), getErrorString);
}
bool valid = result == 0;
size_t changed = 0;
for (size_t index = 0; index < pixels.size(); ++index)
{
const float expected = index == static_cast<size_t>(y) * width + x ? 1.0f : 0.25f;
if (pixels[index] != expected)
valid = false;
if (pixels[index] != 0.25f)
++changed;
}
if (result == 0)
std::printf("Surface store smoke test: %s (changed %zu pixel; expected one changed pixel)\n",
valid ? "PASS" : "FAIL", changed);
// CUDA sust.p interprets input as f32 for UNORM/SNORM surfaces. These
// deterministic bytes expose a conversion error when a normalized CUDA
// array is represented by a plain HIP integer array.
CUfunction normalizedFunction = nullptr;
CUfunction integerFunction = nullptr;
if (result == 0)
result = report("cuModuleGetFunction(d4r_surface_normalized_probe)",
moduleGetFunction(&normalizedFunction, module, "d4r_surface_normalized_probe"), getErrorString);
if (result == 0)
result = report("cuModuleGetFunction(d4r_surface_integer_probe)",
moduleGetFunction(&integerFunction, module, "d4r_surface_integer_probe"), getErrorString);
auto checkFormatted = [&](uint32_t format, const char* name, std::array<uint32_t, 4> words,
std::array<uint8_t, 4> expected, CUfunction storeFunction) {
constexpr size_t testWidth = 4;
constexpr size_t testHeight = 4;
CUarray testArray = nullptr;
CUsurfObject testSurface = 0;
const CUDA_ARRAY_DESCRIPTOR descriptor{testWidth, testHeight, format, 4};
CUresult status = report(name, arrayCreate(&testArray, &descriptor), getErrorString);
if (status == 0)
{
CUDA_RESOURCE_DESC resource{};
resource.resType = 0;
resource.res.array.hArray = testArray;
status = report("cuSurfObjectCreate(normalized)", surfaceCreate(&testSurface, &resource), getErrorString);
}
std::array<uint8_t, testWidth * testHeight * 4> bytes{};
if (status == 0)
{
CUDA_MEMCPY2D upload{};
upload.srcMemoryType = 1;
upload.srcHost = bytes.data();
upload.srcPitch = testWidth * 4;
upload.dstMemoryType = 3;
upload.dstArray = testArray;
upload.WidthInBytes = testWidth * 4;
upload.Height = testHeight;
status = report("cuMemcpy2D_v2(normalized upload)", copy2D(&upload), getErrorString);
}
if (status == 0)
{
uint64_t handle = testSurface;
unsigned int px = 1, py = 2;
void* arguments[] = {&handle, &px, &py, &words[0], &words[1], &words[2], &words[3]};
status = report("cuLaunchKernel(formatted store)",
launchKernel(storeFunction, 1, 1, 1, 1, 1, 1, 0, nullptr, arguments, nullptr),
getErrorString);
if (status == 0)
status = report("cuCtxSynchronize(normalized)", synchronize(), getErrorString);
}
if (status == 0)
{
CUDA_MEMCPY2D readback{};
readback.srcMemoryType = 3;
readback.srcArray = testArray;
readback.dstMemoryType = 1;
readback.dstHost = bytes.data();
readback.dstPitch = testWidth * 4;
readback.WidthInBytes = testWidth * 4;
readback.Height = testHeight;
status = report("cuMemcpy2D_v2(normalized readback)", copy2D(&readback), getErrorString);
}
const size_t pixelOffset = (2 * testWidth + 1) * 4;
std::printf("%s: actual=%02x,%02x,%02x,%02x expected=%02x,%02x,%02x,%02x\n", name,
bytes[pixelOffset], bytes[pixelOffset + 1], bytes[pixelOffset + 2], bytes[pixelOffset + 3],
expected[0], expected[1], expected[2], expected[3]);
bool caseValid = status == 0;
for (size_t index = 0; index < bytes.size(); ++index)
{
const uint8_t expectedByte = index >= pixelOffset && index < pixelOffset + 4
? expected[index - pixelOffset] : 0;
if (bytes[index] != expectedByte)
caseValid = false;
}
if (testSurface != 0)
surfaceDestroy(testSurface);
if (testArray != nullptr)
arrayDestroy(testArray);
std::printf("%s formatted store: %s\n", name, caseValid ? "PASS" : "FAIL");
return caseValid;
};
if (result == 0)
{
valid &= checkFormatted(194, "RGBA8 UNORM",
{std::bit_cast<uint32_t>(0.25f), std::bit_cast<uint32_t>(0.5f),
std::bit_cast<uint32_t>(0.75f), std::bit_cast<uint32_t>(1.0f)},
{0x40, 0x80, 0xbf, 0xff}, normalizedFunction);
valid &= checkFormatted(200, "RGBA8 SNORM",
{std::bit_cast<uint32_t>(-1.0f), std::bit_cast<uint32_t>(-0.5f),
std::bit_cast<uint32_t>(0.5f), std::bit_cast<uint32_t>(1.0f)},
{0x81, 0xc0, 0x40, 0x7f}, normalizedFunction);
valid &= checkFormatted(1, "RGBA8 UINT", {1u, 127u, 200u, 255u},
{0x01, 0x7f, 0xc8, 0xff}, integerFunction);
valid &= checkFormatted(8, "RGBA8 SINT",
{uint32_t(-128), uint32_t(-64), 64u, 127u},
{0x80, 0xc0, 0x40, 0x7f}, integerFunction);
// The transformer output writes an RGBA16F CUDA surface and the input
// kernels write RG16F motion and R16F surfaces with the formatted v4
// store; only the channels the format has are kept.
for (uint32_t halfChannels : {4u, 2u, 1u})
{
CUarray halfArray = nullptr;
CUsurfObject halfSurface = 0;
const CUDA_ARRAY3D_DESCRIPTOR halfDescriptor{4, 4, 0, 16, halfChannels, 2};
CUresult halfStatus = array3DCreate(&halfArray, &halfDescriptor);
if (halfStatus == 0)
{
CUDA_RESOURCE_DESC resource{};
resource.resType = 0;
resource.res.array.hArray = halfArray;
halfStatus = surfaceCreate(&halfSurface, &resource);
}
std::vector<uint16_t> halfPixels(4 * 4 * halfChannels, 0);
const size_t halfPitch = 4 * halfChannels * sizeof(uint16_t);
if (halfStatus == 0)
{
CUDA_MEMCPY2D upload{};
upload.srcMemoryType = 1;
upload.srcHost = halfPixels.data();
upload.srcPitch = halfPitch;
upload.dstMemoryType = 3;
upload.dstArray = halfArray;
upload.WidthInBytes = halfPitch;
upload.Height = 4;
halfStatus = copy2D(&upload);
}
if (halfStatus == 0)
{
uint64_t handle = halfSurface;
unsigned int px = 1, py = 2;
std::array<float, 4> values{0.25f, 0.5f, 0.75f, 1.0f};
void* arguments[] = {&handle, &px, &py, &values[0], &values[1], &values[2], &values[3]};
halfStatus = launchKernel(normalizedFunction, 1, 1, 1, 1, 1, 1, 0, nullptr, arguments, nullptr);
if (halfStatus == 0)
halfStatus = synchronize();
}
if (halfStatus == 0)
{
CUDA_MEMCPY2D readback{};
readback.srcMemoryType = 3;
readback.srcArray = halfArray;
readback.dstMemoryType = 1;
readback.dstHost = halfPixels.data();
readback.dstPitch = halfPitch;
readback.WidthInBytes = halfPitch;
readback.Height = 4;
halfStatus = copy2D(&readback);
}
const size_t halfPixelOffset = (2 * 4 + 1) * halfChannels;
const std::array<uint16_t, 4> halfExpected{0x3400u, 0x3800u, 0x3a00u, 0x3c00u};
bool halfValid = halfStatus == 0;
for (size_t index = 0; index < halfPixels.size(); ++index)
{
const uint16_t expected = index >= halfPixelOffset && index < halfPixelOffset + halfChannels
? halfExpected[index - halfPixelOffset] : 0;
if (halfPixels[index] != expected)
{
if (halfValid)
std::printf(" %u-channel f16 pixel value %zu: got 0x%04x expected 0x%04x\n", halfChannels,
index, halfPixels[index], expected);
halfValid = false;
}
}
std::printf("%s formatted v4 store: %s (CUDA result=%d)\n",
halfChannels == 4 ? "RGBA16F" : halfChannels == 2 ? "RG16F" : "R16F",
halfValid ? "PASS" : "FAIL", halfStatus);
valid &= halfValid;
if (halfSurface != 0)
surfaceDestroy(halfSurface);
if (halfArray != nullptr)
arrayDestroy(halfArray);
}
// ZLUDA currently backs CUDA's packed UNORM 10:10:10:2 format 80
// with RGBA32F. Probe the GPU-side formatted-store conversion in
// that backing representation; host packed-copy semantics are a
// separate contract.
CUarray packedArray = nullptr;
CUsurfObject packedSurface = 0;
const CUDA_ARRAY_DESCRIPTOR packedDescriptor{4, 4, 80, 4};
CUresult packedStatus = report("CUDA format 80 array", arrayCreate(&packedArray, &packedDescriptor),
getErrorString);
if (packedStatus == 0)
{
CUDA_RESOURCE_DESC resource{};
resource.resType = 0;
resource.res.array.hArray = packedArray;
packedStatus = report("cuSurfObjectCreate(format 80)",
surfaceCreate(&packedSurface, &resource), getErrorString);
}
std::array<float, 4 * 4 * 4> packedPixels{};
if (packedStatus == 0)
{
CUDA_MEMCPY2D upload{};
upload.srcMemoryType = 1;
upload.srcHost = packedPixels.data();
upload.srcPitch = 4 * 4 * sizeof(float);
upload.dstMemoryType = 10; // HIP array, bypassing CUDA format conversion
upload.dstArray = packedArray;
upload.WidthInBytes = upload.srcPitch;
upload.Height = 4;
packedStatus = report("hipMemcpyParam2D(format 80 backing upload)", backingCopy2D(&upload));
}
std::array<float, 4> packedInput{0.1234f, 0.501f, 0.8765f, 0.45f};
if (packedStatus == 0)
{
uint64_t handle = packedSurface;
unsigned int px = 1, py = 2;
void* arguments[] = {&handle, &px, &py, &packedInput[0], &packedInput[1],
&packedInput[2], &packedInput[3]};
packedStatus = report("cuLaunchKernel(format 80 formatted store)",
launchKernel(normalizedFunction, 1, 1, 1, 1, 1, 1, 0, nullptr,
arguments, nullptr), getErrorString);
if (packedStatus == 0)
packedStatus = report("cuCtxSynchronize(format 80)", synchronize(), getErrorString);
}
if (packedStatus == 0)
{
CUDA_MEMCPY2D readback{};
readback.srcMemoryType = 10; // HIP array, inspect physical backing
readback.srcArray = packedArray;
readback.dstMemoryType = 1;
readback.dstHost = packedPixels.data();
readback.dstPitch = 4 * 4 * sizeof(float);
readback.WidthInBytes = readback.dstPitch;
readback.Height = 4;
packedStatus = report("hipMemcpyParam2D(format 80 backing readback)", backingCopy2D(&readback));
}
const size_t packedPixelOffset = (2 * 4 + 1) * 4;
const std::array<float, 4> packedExpected{
126.0f / 1023.0f, 513.0f / 1023.0f, 897.0f / 1023.0f, 1.0f / 3.0f};
bool packedValid = packedStatus == 0;
for (size_t index = 0; index < packedPixels.size(); ++index)
{
const float expected = index >= packedPixelOffset && index < packedPixelOffset + 4
? packedExpected[index - packedPixelOffset] : 0.0f;
if (std::fabs(packedPixels[index] - expected) > 1e-6f)
packedValid = false;
}
std::printf("format 80 backing: actual=%g,%g,%g,%g expected=%g,%g,%g,%g: %s\n",
packedPixels[packedPixelOffset], packedPixels[packedPixelOffset + 1],
packedPixels[packedPixelOffset + 2], packedPixels[packedPixelOffset + 3],
packedExpected[0], packedExpected[1], packedExpected[2], packedExpected[3],
packedValid ? "PASS" : "FAIL");
valid &= packedValid;
// The public CUDA array is four bytes per pixel even though ZLUDA
// uses sixteen bytes per pixel internally. Test both copy directions
// with ordinary packed host rows, including their documented pitch.
std::array<uint32_t, 4 * 4> packedHost{};
for (size_t index = 0; index < packedHost.size(); ++index)
{
const uint32_t red = uint32_t(index * 137u) & 1023u;
const uint32_t green = uint32_t(index * 257u) & 1023u;
const uint32_t blue = uint32_t(index * 379u) & 1023u;
const uint32_t alpha = uint32_t(index) & 3u;
packedHost[index] = red | (green << 10) | (blue << 20) | (alpha << 30);
}
if (packedStatus == 0)
{
CUDA_MEMCPY2D upload{};
upload.srcMemoryType = 1;
upload.srcHost = packedHost.data();
upload.srcPitch = 4 * sizeof(uint32_t);
upload.dstMemoryType = 3;
upload.dstArray = packedArray;
upload.WidthInBytes = upload.srcPitch;
upload.Height = 4;
packedStatus = report("cuMemcpy2D_v2(format 80 packed host upload)", copy2D(&upload), getErrorString);
}
std::array<float, 4 * 4 * 4> copiedBacking{};
if (packedStatus == 0)
{
CUDA_MEMCPY2D readback{};
readback.srcMemoryType = 10;
readback.srcArray = packedArray;
readback.dstMemoryType = 1;
readback.dstHost = copiedBacking.data();
readback.dstPitch = 4 * 4 * sizeof(float);
readback.WidthInBytes = readback.dstPitch;
readback.Height = 4;
packedStatus = report("hipMemcpyParam2D(format 80 backing after packed upload)", backingCopy2D(&readback));
}
bool packedCopyValid = packedStatus == 0;
if (packedCopyValid)
{
for (size_t index = 0; index < packedHost.size(); ++index)
{
const uint32_t word = packedHost[index];
const std::array<float, 4> expected{
float(word & 1023u) / 1023.0f,
float((word >> 10) & 1023u) / 1023.0f,
float((word >> 20) & 1023u) / 1023.0f,
float(word >> 30) / 3.0f};
for (size_t channel = 0; channel < 4; ++channel)
packedCopyValid &= std::fabs(copiedBacking[index * 4 + channel] - expected[channel]) < 1e-6f;
}
}
std::array<uint32_t, 4 * 4> packedRoundTrip{};
if (packedStatus == 0)
{
CUDA_MEMCPY2D readback{};
readback.srcMemoryType = 3;
readback.srcArray = packedArray;
readback.dstMemoryType = 1;
readback.dstHost = packedRoundTrip.data();
readback.dstPitch = 4 * sizeof(uint32_t);
readback.WidthInBytes = readback.dstPitch;
readback.Height = 4;
packedStatus = report("cuMemcpy2D_v2(format 80 packed host readback)", copy2D(&readback), getErrorString);
packedCopyValid &= packedStatus == 0 && packedRoundTrip == packedHost;
}
std::printf("format 80 packed host copy: %s\n", packedCopyValid ? "PASS" : "FAIL");
valid &= packedCopyValid;
std::array<uint32_t, 5 * 4> paddedHost{};
for (size_t index = 0; index < paddedHost.size(); ++index)
paddedHost[index] = (uint32_t(index * 17u) & 1023u)
| ((uint32_t(index * 29u) & 1023u) << 10)
| ((uint32_t(index * 41u) & 1023u) << 20)
| ((uint32_t(index) & 3u) << 30);
std::array<uint32_t, 4 * 4> subrectExpected = packedHost;
for (size_t row = 0; row < 2; ++row)
for (size_t column = 0; column < 2; ++column)
subrectExpected[(row + 1) * 4 + column + 1] = paddedHost[(row + 1) * 5 + column + 1];
if (packedStatus == 0)
{
CUDA_MEMCPY2D upload{};
upload.srcXInBytes = sizeof(uint32_t);
upload.srcY = 1;
upload.srcMemoryType = 1;
upload.srcHost = paddedHost.data();
upload.srcPitch = 5 * sizeof(uint32_t);
upload.dstXInBytes = sizeof(uint32_t);
upload.dstY = 1;
upload.dstMemoryType = 3;
upload.dstArray = packedArray;
upload.WidthInBytes = 2 * sizeof(uint32_t);
upload.Height = 2;
packedStatus = report("cuMemcpy2D_v2(format 80 packed subrect upload)", copy2D(&upload), getErrorString);
}
if (packedStatus == 0)
{
CUDA_MEMCPY2D readback{};
readback.srcMemoryType = 3;
readback.srcArray = packedArray;
readback.dstMemoryType = 1;
readback.dstHost = packedRoundTrip.data();
readback.dstPitch = 4 * sizeof(uint32_t);
readback.WidthInBytes = readback.dstPitch;
readback.Height = 4;
packedStatus = report("cuMemcpy2D_v2(format 80 after subrect)", copy2D(&readback), getErrorString);
}
const bool subrectValid = packedStatus == 0 && packedRoundTrip == subrectExpected;
std::printf("format 80 packed subrect/pitch: %s\n", subrectValid ? "PASS" : "FAIL");
valid &= subrectValid;
if (packedStatus == 0)
{
uint64_t handle = packedSurface;
unsigned int px = 1, py = 2;
void* arguments[] = {&handle, &px, &py, &packedInput[0], &packedInput[1],
&packedInput[2], &packedInput[3]};
packedStatus = report("cuLaunchKernel(format 80 store for packed readback)",
launchKernel(normalizedFunction, 1, 1, 1, 1, 1, 1, 0, nullptr,
arguments, nullptr), getErrorString);
if (packedStatus == 0)
packedStatus = report("cuCtxSynchronize(format 80 packed readback)", synchronize(), getErrorString);
}
if (packedStatus == 0)
{
CUDA_MEMCPY2D readback{};
readback.srcMemoryType = 3;
readback.srcArray = packedArray;
readback.dstMemoryType = 1;
readback.dstHost = packedRoundTrip.data();
readback.dstPitch = 4 * sizeof(uint32_t);
readback.WidthInBytes = readback.dstPitch;
readback.Height = 4;
packedStatus = report("cuMemcpy2D_v2(format 80 GPU store to packed host)", copy2D(&readback), getErrorString);
}
subrectExpected[2 * 4 + 1] = 126u | (513u << 10) | (897u << 20) | (1u << 30);
const bool gpuStoreReadbackValid = packedStatus == 0 && packedRoundTrip == subrectExpected;
std::printf("format 80 GPU store to packed host: %s\n", gpuStoreReadbackValid ? "PASS" : "FAIL");
valid &= gpuStoreReadbackValid;
CUdeviceptr devicePacked = 0;
CUresult deviceStatus = report("cuMemAlloc_v2(format 80 packed device rows)",
memAlloc(&devicePacked, packedHost.size() * sizeof(uint32_t)), getErrorString);
if (deviceStatus == 0)
{
CUDA_MEMCPY2D copy{};
copy.srcMemoryType = 1;
copy.srcHost = packedHost.data();
copy.srcPitch = 4 * sizeof(uint32_t);
copy.dstMemoryType = 2;
copy.dstDevice = devicePacked;
copy.dstPitch = copy.srcPitch;
copy.WidthInBytes = copy.srcPitch;
copy.Height = 4;
deviceStatus = report("cuMemcpy2D_v2(format 80 host to device staging)", copy2D(&copy), getErrorString);
}
if (deviceStatus == 0)
{
CUDA_MEMCPY2D copy{};
copy.srcMemoryType = 2;
copy.srcDevice = devicePacked;
copy.srcPitch = 4 * sizeof(uint32_t);
copy.dstMemoryType = 3;
copy.dstArray = packedArray;
copy.WidthInBytes = copy.srcPitch;
copy.Height = 4;
deviceStatus = report("cuMemcpy2D_v2(format 80 packed device to array)", copy2D(&copy), getErrorString);
}
if (deviceStatus == 0)
{
CUDA_MEMCPY2D readback{};
readback.srcMemoryType = 10;
readback.srcArray = packedArray;
readback.dstMemoryType = 1;
readback.dstHost = copiedBacking.data();
readback.dstPitch = 4 * 4 * sizeof(float);
readback.WidthInBytes = readback.dstPitch;
readback.Height = 4;
deviceStatus = report("hipMemcpyParam2D(format 80 backing after device upload)", backingCopy2D(&readback));
}
bool deviceCopyValid = deviceStatus == 0;
if (deviceCopyValid)
{
for (size_t index = 0; index < packedHost.size(); ++index)
{
const uint32_t word = packedHost[index];
const std::array<float, 4> expected{
float(word & 1023u) / 1023.0f,
float((word >> 10) & 1023u) / 1023.0f,
float((word >> 20) & 1023u) / 1023.0f,
float(word >> 30) / 3.0f};
for (size_t channel = 0; channel < 4; ++channel)
deviceCopyValid &= std::fabs(copiedBacking[index * 4 + channel] - expected[channel]) < 1e-6f;
}
}
if (deviceStatus == 0)
{
CUDA_MEMCPY2D copy{};
copy.srcMemoryType = 3;
copy.srcArray = packedArray;
copy.dstMemoryType = 2;
copy.dstDevice = devicePacked;
copy.dstPitch = 4 * sizeof(uint32_t);
copy.WidthInBytes = copy.dstPitch;
copy.Height = 4;
deviceStatus = report("cuMemcpy2D_v2(format 80 array to packed device)", copy2D(&copy), getErrorString);
}
if (deviceStatus == 0)
{
CUDA_MEMCPY2D copy{};
copy.srcMemoryType = 2;
copy.srcDevice = devicePacked;
copy.srcPitch = 4 * sizeof(uint32_t);
copy.dstMemoryType = 1;
copy.dstHost = packedRoundTrip.data();
copy.dstPitch = copy.srcPitch;
copy.WidthInBytes = copy.srcPitch;
copy.Height = 4;
deviceStatus = report("cuMemcpy2D_v2(format 80 packed device to host)", copy2D(&copy), getErrorString);
deviceCopyValid &= deviceStatus == 0 && packedRoundTrip == packedHost;
}
std::printf("format 80 packed device copy: %s\n", deviceCopyValid ? "PASS" : "FAIL");
valid &= deviceCopyValid;
if (devicePacked != 0)
memFree(devicePacked);
CUarray otherPackedArray = nullptr;
CUresult arrayCopyStatus = report("CUDA format 80 second array",
arrayCreate(&otherPackedArray, &packedDescriptor), getErrorString);
if (arrayCopyStatus == 0)
{
CUDA_MEMCPY2D copy{};
copy.srcMemoryType = 3;
copy.srcArray = packedArray;
copy.dstMemoryType = 3;
copy.dstArray = otherPackedArray;
copy.WidthInBytes = 4 * sizeof(uint32_t);
copy.Height = 4;
arrayCopyStatus = report("cuMemcpy2D_v2(format 80 array to array)", copy2D(&copy), getErrorString);
}
if (arrayCopyStatus == 0)
{
CUDA_MEMCPY2D readback{};
readback.srcMemoryType = 3;
readback.srcArray = otherPackedArray;
readback.dstMemoryType = 1;
readback.dstHost = packedRoundTrip.data();
readback.dstPitch = 4 * sizeof(uint32_t);
readback.WidthInBytes = readback.dstPitch;
readback.Height = 4;
arrayCopyStatus = report("cuMemcpy2D_v2(format 80 second array readback)", copy2D(&readback), getErrorString);
}
const bool arrayCopyValid = arrayCopyStatus == 0 && packedRoundTrip == packedHost;
std::printf("format 80 array to array copy: %s\n", arrayCopyValid ? "PASS" : "FAIL");
valid &= arrayCopyValid;
if (otherPackedArray != nullptr)
arrayDestroy(otherPackedArray);
if (packedSurface != 0)
surfaceDestroy(packedSurface);
if (packedArray != nullptr)
arrayDestroy(packedArray);
}
exitCode = valid ? 0 : 1;
if (surface != 0)
surfaceDestroy(surface);
if (array != nullptr)
arrayDestroy(array);
if (module != nullptr)
moduleUnload(module);
if (context != nullptr)
contextDestroy(context);
dlclose(hipLibrary);
dlclose(library);
return exitCode;
}
+472
View File
@@ -0,0 +1,472 @@
#include <dlfcn.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <vector>
using CUresult = int;
using CUdevice = int;
using CUcontext = void*;
using CUarray = void*;
using CUmodule = void*;
using CUfunction = void*;
using CUstream = void*;
using CUdeviceptr = uint64_t;
using CUtexObject = uint64_t;
using CUsurfObject = uint64_t;
struct CUDA_ARRAY_DESCRIPTOR
{
size_t Width;
size_t Height;
uint32_t Format;
uint32_t NumChannels;
};
static_assert(sizeof(CUDA_ARRAY_DESCRIPTOR) == 24);
struct CUDA_RESOURCE_DESC
{
uint32_t resType;
uint32_t alignment;
union
{
struct { CUarray hArray; } array;
int reserved[32];
} res;
uint32_t flags;
uint32_t reserved;
};
static_assert(sizeof(CUDA_RESOURCE_DESC) == 144);
struct CUDA_TEXTURE_DESC
{
uint32_t addressMode[3];
uint32_t filterMode;
uint32_t flags;
uint32_t maxAnisotropy;
uint32_t mipmapFilterMode;
float mipmapLevelBias;
float minMipmapLevelClamp;
float maxMipmapLevelClamp;
float borderColor[4];
int reserved[12];
};
static_assert(sizeof(CUDA_TEXTURE_DESC) == 104);
struct CUDA_MEMCPY2D
{
size_t srcXInBytes;
size_t srcY;
uint32_t srcMemoryType;
uint32_t srcAlignment;
const void* srcHost;
CUdeviceptr srcDevice;
CUarray srcArray;
size_t srcPitch;
size_t dstXInBytes;
size_t dstY;
uint32_t dstMemoryType;
uint32_t dstAlignment;
void* dstHost;
CUdeviceptr dstDevice;
CUarray dstArray;
size_t dstPitch;
size_t WidthInBytes;
size_t Height;
};
static_assert(sizeof(CUDA_MEMCPY2D) == 128);
template <typename Function> static Function load_function(void* library, const char* name)
{
void* raw = dlsym(library, name);
Function function{};
static_assert(sizeof(function) == sizeof(raw));
std::memcpy(&function, &raw, sizeof(function));
return function;
}
static CUresult report(const char* stage, CUresult result,
CUresult (*get_error_string)(CUresult, const char**) = nullptr)
{
const char* message = nullptr;
if (get_error_string != nullptr)
get_error_string(result, &message);
std::printf("%s: CUDA result=%d", stage, result);
if (message != nullptr)
std::printf(" (%s)", message);
std::putchar('\n');
return result;
}
// Integer-coordinate texture reads (tex.base.2d / tex.2d with .s32
// coordinates) must return the exact texel at those indices for every sampler
// configuration, including NGX's: normalized coordinates with linear
// filtering. Every texel holds a distinct value so coordinate mistakes show.
int main()
{
constexpr unsigned int width = 8;
constexpr unsigned int height = 8;
const char* ptx = R"PTX(
.version 8.9
.target sm_89
.address_size 64
.visible .entry d4r_half_texture_sample_probe(
.param .b64 texture_object,
.param .b64 output_pointer
)
{
.reg .b64 %rd<5>;
.reg .b32 %r<5>;
.reg .f32 %f<18>;
ld.param.b64 %rd1, [texture_object];
ld.param.b64 %rd2, [output_pointer];
mov.u32 %r1, %tid.x;
mov.u32 %r2, %tid.y;
tex.base.2d.v4.f32.s32 {%f0,%f1,%f2,%f3}, [%rd1, {%r1,%r2}];
tex.2d.v4.f32.s32 {%f4,%f5,%f6,%f7}, [%rd1, {%r1,%r2}];
cvt.rn.f32.u32 %f8, %r1;
cvt.rn.f32.u32 %f9, %r2;
add.f32 %f8, %f8, 0f3E800000;
add.f32 %f9, %f9, 0f3F400000;
mul.f32 %f8, %f8, 0f3E000000;
mul.f32 %f9, %f9, 0f3E000000;
tex.base.2d.v4.f32.f32 {%f10,%f11,%f12,%f13}, [%rd1, {%f8,%f9}];
tex.level.2d.v4.f32.f32 {%f14,%f15,%f16,%f17}, [%rd1, {%f8,%f9}], 0f00000000;
mad.lo.u32 %r3, %r2, 8, %r1;
mul.wide.u32 %rd3, %r3, 64;
add.s64 %rd4, %rd2, %rd3;
st.global.v4.f32 [%rd4], {%f0,%f1,%f2,%f3};
st.global.v4.f32 [%rd4+16], {%f4,%f5,%f6,%f7};
st.global.v4.f32 [%rd4+32], {%f10,%f11,%f12,%f13};
st.global.v4.f32 [%rd4+48], {%f14,%f15,%f16,%f17};
ret;
}
.visible .entry d4r_half_surface_store_probe(.param .b64 surface_object)
{
.reg .b64 %rd<2>;
.reg .b32 %r<2>;
.reg .f32 %f<4>;
ld.param.b64 %rd0, [surface_object];
mov.u32 %r0, 3;
mov.u32 %r1, 4;
mov.f32 %f0, 0f3E800000;
mov.f32 %f1, 0f3F000000;
mov.f32 %f2, 0f3F400000;
mov.f32 %f3, 0f3F800000;
sust.p.2d.v4.b32.zero [%rd0, {%r0,%r1}], {%f0,%f1,%f2,%f3};
ret;
}
)PTX";
void* library = dlopen("libcuda.so", RTLD_NOW | RTLD_LOCAL);
if (library == nullptr)
{
std::fprintf(stderr, "dlopen(libcuda.so) failed: %s\n", dlerror());
return 1;
}
using InitFn = CUresult (*)(unsigned int);
using DeviceGetFn = CUresult (*)(CUdevice*, int);
using ContextCreateFn = CUresult (*)(CUcontext*, unsigned int, CUdevice);
using ContextDestroyFn = CUresult (*)(CUcontext);
using ArrayCreateFn = CUresult (*)(CUarray*, const CUDA_ARRAY_DESCRIPTOR*);
using ArrayDestroyFn = CUresult (*)(CUarray);
using TextureCreateFn = CUresult (*)(CUtexObject*, const CUDA_RESOURCE_DESC*,
const CUDA_TEXTURE_DESC*, const void*);
using TextureDestroyFn = CUresult (*)(CUtexObject);
using SurfaceCreateFn = CUresult (*)(CUsurfObject*, const CUDA_RESOURCE_DESC*);
using SurfaceDestroyFn = CUresult (*)(CUsurfObject);
using Copy2DFn = CUresult (*)(const CUDA_MEMCPY2D*);
using MemAllocFn = CUresult (*)(CUdeviceptr*, size_t);
using MemFreeFn = CUresult (*)(CUdeviceptr);
using CopyDtoHFn = CUresult (*)(void*, CUdeviceptr, size_t);
using ModuleLoadFn = CUresult (*)(CUmodule*, const void*);
using ModuleGetFunctionFn = CUresult (*)(CUfunction*, CUmodule, const char*);
using LaunchKernelFn = CUresult (*)(CUfunction, unsigned int, unsigned int, unsigned int,
unsigned int, unsigned int, unsigned int, unsigned int,
CUstream, void**, void**);
using ContextSynchronizeFn = CUresult (*)(void);
using ModuleUnloadFn = CUresult (*)(CUmodule);
using GetErrorStringFn = CUresult (*)(CUresult, const char**);
const auto init = load_function<InitFn>(library, "cuInit");
const auto deviceGet = load_function<DeviceGetFn>(library, "cuDeviceGet");
const auto contextCreate = load_function<ContextCreateFn>(library, "cuCtxCreate_v2");
const auto contextDestroy = load_function<ContextDestroyFn>(library, "cuCtxDestroy_v2");
const auto arrayCreate = load_function<ArrayCreateFn>(library, "cuArrayCreate_v2");
const auto arrayDestroy = load_function<ArrayDestroyFn>(library, "cuArrayDestroy");
const auto textureCreate = load_function<TextureCreateFn>(library, "cuTexObjectCreate");
const auto textureDestroy = load_function<TextureDestroyFn>(library, "cuTexObjectDestroy");
const auto surfaceCreate = load_function<SurfaceCreateFn>(library, "cuSurfObjectCreate");
const auto surfaceDestroy = load_function<SurfaceDestroyFn>(library, "cuSurfObjectDestroy");
const auto copy2D = load_function<Copy2DFn>(library, "cuMemcpy2D_v2");
const auto memAlloc = load_function<MemAllocFn>(library, "cuMemAlloc_v2");
const auto memFree = load_function<MemFreeFn>(library, "cuMemFree_v2");
const auto copyDtoH = load_function<CopyDtoHFn>(library, "cuMemcpyDtoH_v2");
const auto moduleLoad = load_function<ModuleLoadFn>(library, "cuModuleLoadData");
const auto moduleGetFunction = load_function<ModuleGetFunctionFn>(library, "cuModuleGetFunction");
const auto launchKernel = load_function<LaunchKernelFn>(library, "cuLaunchKernel");
const auto synchronize = load_function<ContextSynchronizeFn>(library, "cuCtxSynchronize");
const auto moduleUnload = load_function<ModuleUnloadFn>(library, "cuModuleUnload");
const auto getErrorString = load_function<GetErrorStringFn>(library, "cuGetErrorString");
if (init == nullptr || deviceGet == nullptr || contextCreate == nullptr || contextDestroy == nullptr ||
arrayCreate == nullptr || arrayDestroy == nullptr || textureCreate == nullptr ||
textureDestroy == nullptr || surfaceCreate == nullptr || surfaceDestroy == nullptr ||
copy2D == nullptr || memAlloc == nullptr || memFree == nullptr ||
copyDtoH == nullptr || moduleLoad == nullptr || moduleGetFunction == nullptr ||
launchKernel == nullptr || synchronize == nullptr || moduleUnload == nullptr)
{
std::fprintf(stderr, "ZLUDA libcuda.so is missing one or more texture probe exports\n");
dlclose(library);
return 1;
}
CUcontext context = nullptr;
CUarray array = nullptr;
CUdeviceptr deviceOutput = 0;
CUmodule module = nullptr;
CUresult result = report("cuInit", init(0), getErrorString);
CUdevice device = 0;
if (result == 0)
result = report("cuDeviceGet", deviceGet(&device, 0), getErrorString);
if (result == 0)
result = report("cuCtxCreate_v2", contextCreate(&context, 0, device), getErrorString);
// Texel (x, y) = (x/8, y/8, 0.5 + x/16, 1), all exact in f16.
auto expected_texel = [](unsigned int x, unsigned int y) {
return std::array<float, 4>{x / 8.0f, y / 8.0f, 0.5f + x / 16.0f, 1.0f};
};
auto float_to_half_exact = [](float value) -> uint16_t {
if (value == 0.0f)
return 0;
int exponent = 0;
const float fraction = std::frexp(value, &exponent); // value = fraction * 2^exponent
const uint16_t mantissa = static_cast<uint16_t>((fraction * 2.0f - 1.0f) * 1024.0f);
return static_cast<uint16_t>(((exponent - 1 + 15) << 10) | mantissa);
};
const size_t outputFloats = static_cast<size_t>(width) * height * 16;
if (result == 0)
result = report("cuMemAlloc_v2(output)", memAlloc(&deviceOutput, outputFloats * sizeof(float)), getErrorString);
if (result == 0)
result = report("cuModuleLoadData(texture sample PTX)", moduleLoad(&module, ptx), getErrorString);
CUfunction function = nullptr;
if (result == 0)
result = report("cuModuleGetFunction(d4r_half_texture_sample_probe)",
moduleGetFunction(&function, module, "d4r_half_texture_sample_probe"), getErrorString);
struct SamplerCase
{
const char* name;
uint32_t filterMode;
uint32_t flags;
};
const SamplerCase samplerCases[] = {
{"point, unnormalized", 0, 0},
{"linear, normalized coordinates (NGX)", 1, 2 /* CU_TRSF_NORMALIZED_COORDINATES */},
{"point, normalized coordinates (NGX)", 0, 2},
};
// DLSS reads RGBA16F colour, RG16F motion vectors and R32F depth; only
// the channels a format has are compared.
struct FormatCase
{
const char* name;
uint32_t format;
uint32_t channels;
};
const FormatCase formatCases[] = {
{"RGBA16F", 0x10, 4}, {"RG16F", 0x10, 2}, {"R16F", 0x10, 1}, {"R32F", 0x20, 1}, {"RG32F", 0x20, 2},
};
bool allPass = true;
for (const FormatCase& formatCase : formatCases)
{
if (result != 0)
break;
const size_t elementBytes = formatCase.format == 0x20 ? 4 : 2;
const size_t rowBytes = width * formatCase.channels * elementBytes;
std::vector<uint8_t> input(rowBytes * height);
for (unsigned int y = 0; y < height; ++y)
for (unsigned int x = 0; x < width; ++x)
for (unsigned int channel = 0; channel < formatCase.channels; ++channel)
{
const float value = expected_texel(x, y)[channel];
uint8_t* at = input.data() + y * rowBytes + (x * formatCase.channels + channel) * elementBytes;
if (elementBytes == 2)
{
const uint16_t half = float_to_half_exact(value);
std::memcpy(at, &half, 2);
}
else
std::memcpy(at, &value, 4);
}
CUarray formatArray = nullptr;
const CUDA_ARRAY_DESCRIPTOR descriptor{width, height, formatCase.format, formatCase.channels};
CUresult status = arrayCreate(&formatArray, &descriptor);
if (status == 0)
{
CUDA_MEMCPY2D upload{};
upload.srcMemoryType = 1; // CUDA_MEMORYTYPE_HOST
upload.srcHost = input.data();
upload.srcPitch = rowBytes;
upload.dstMemoryType = 3; // CUDA_MEMORYTYPE_ARRAY
upload.dstArray = formatArray;
upload.WidthInBytes = rowBytes;
upload.Height = height;
status = copy2D(&upload);
}
std::vector<uint8_t> readbackBytes(input.size(), 0);
if (status == 0)
{
CUDA_MEMCPY2D readback{};
readback.srcMemoryType = 3;
readback.srcArray = formatArray;
readback.dstMemoryType = 1;
readback.dstHost = readbackBytes.data();
readback.dstPitch = rowBytes;
readback.WidthInBytes = rowBytes;
readback.Height = height;
status = copy2D(&readback);
}
const bool roundTrip = status == 0 && readbackBytes == input;
std::printf("%s array upload/readback: %s (CUDA result=%d)\n", formatCase.name, roundTrip ? "PASS" : "FAIL", status);
allPass &= roundTrip;
for (const SamplerCase& samplerCase : samplerCases)
{
if (status != 0)
break;
CUtexObject texture = 0;
CUDA_RESOURCE_DESC resource{};
resource.resType = 0; // CUDA_RESOURCE_TYPE_ARRAY
resource.res.array.hArray = formatArray;
CUDA_TEXTURE_DESC sampler{};
sampler.addressMode[0] = 1; // clamp
sampler.addressMode[1] = 1;
sampler.filterMode = samplerCase.filterMode;
sampler.flags = samplerCase.flags;
status = textureCreate(&texture, &resource, &sampler, nullptr);
if (status == 0)
{
uint64_t textureArgument = texture;
uint64_t outputArgument = deviceOutput;
void* arguments[] = {&textureArgument, &outputArgument};
status = launchKernel(function, 1, 1, 1, width, height, 1, 0, nullptr, arguments, nullptr);
}
if (status == 0)
status = synchronize();
std::vector<float> sampled(outputFloats);
if (status == 0)
status = copyDtoH(sampled.data(), deviceOutput, outputFloats * sizeof(float));
size_t mismatches = 0;
for (unsigned int y = 0; status == 0 && y < height; ++y)
for (unsigned int x = 0; x < width; ++x)
for (unsigned int form = 0; form < (samplerCase.flags == 2 ? 4u : 2u); ++form)
for (unsigned int channel = 0; channel < formatCase.channels; ++channel)
{
const float got = sampled[(y * width + x) * 16 + form * 4 + channel];
float want = expected_texel(x, y)[channel];
if (form >= 2 && samplerCase.filterMode == 1)
{
const auto topLeft = expected_texel(x == 0 ? 0 : x - 1, y)[channel];
const auto topRight = expected_texel(x, y)[channel];
const auto bottomLeft = expected_texel(x == 0 ? 0 : x - 1, std::min(y + 1, height - 1))[channel];
const auto bottomRight = expected_texel(x, std::min(y + 1, height - 1))[channel];
want = 0.75f * (0.25f * topLeft + 0.75f * topRight)
+ 0.25f * (0.25f * bottomLeft + 0.75f * bottomRight);
}
if (std::fabs(got - want) > 1e-6f && mismatches++ < 4)
std::printf(" %s %s %s (%u,%u) channel %u: got %g expected %g\n", formatCase.name,
samplerCase.name,
form == 0 ? "tex.base" : form == 1 ? "tex" : form == 2 ? "tex.base normalized" : "tex.level LOD0",
x, y, channel, got, want);
}
const bool casePass = status == 0 && mismatches == 0;
allPass = allPass && casePass;
std::printf("%s fetch, %s: %s (CUDA result=%d, %zu mismatches)\n", formatCase.name,
samplerCase.name, casePass ? "PASS" : "FAIL", status, mismatches);
if (texture != 0)
textureDestroy(texture);
}
if (formatCase.channels == 4 && formatCase.format == 0x10)
array = formatArray; // reused by the surface coherence check below
else if (formatArray != nullptr)
arrayDestroy(formatArray);
}
if (result == 0)
{
CUDA_RESOURCE_DESC resource{};
resource.resType = 0;
resource.res.array.hArray = array;
CUDA_TEXTURE_DESC sampler{};
sampler.addressMode[0] = sampler.addressMode[1] = 1;
sampler.flags = 2;
CUsurfObject surface = 0;
CUtexObject texture = 0;
CUfunction storeFunction = nullptr;
CUresult status = surfaceCreate(&surface, &resource);
if (status == 0)
status = textureCreate(&texture, &resource, &sampler, nullptr);
if (status == 0)
status = moduleGetFunction(&storeFunction, module, "d4r_half_surface_store_probe");
if (status == 0)
{
uint64_t handle = surface;
void* args[] = {&handle};
status = launchKernel(storeFunction, 1, 1, 1, 1, 1, 1, 0, nullptr, args, nullptr);
}
if (status == 0)
status = synchronize();
if (status == 0)
{
uint64_t textureArgument = texture;
uint64_t outputArgument = deviceOutput;
void* args[] = {&textureArgument, &outputArgument};
status = launchKernel(function, 1, 1, 1, width, height, 1, 0, nullptr, args, nullptr);
}
if (status == 0)
status = synchronize();
std::vector<float> sampled(outputFloats);
if (status == 0)
status = copyDtoH(sampled.data(), deviceOutput, outputFloats * sizeof(float));
const std::array<float, 4> expectedStored{0.25f, 0.5f, 0.75f, 1.0f};
size_t mismatches = 0;
for (unsigned int y = 0; status == 0 && y < height; ++y)
for (unsigned int x = 0; x < width; ++x)
for (unsigned int form = 0; form < 4; ++form)
for (unsigned int channel = 0; channel < 4; ++channel)
{
const float expected = x == 3 && y == 4 ? expectedStored[channel]
: expected_texel(x, y)[channel];
const float got = sampled[(y * width + x) * 16 + form * 4 + channel];
if (got != expected && mismatches++ < 5)
std::printf(" surface->texture (%u,%u) form %u channel %u: got %g expected %g\n",
x, y, form, channel, got, expected);
}
const bool coherencePass = status == 0 && mismatches == 0;
std::printf("RGBA16F surface store to existing texture: %s (CUDA result=%d, %zu mismatches)\n",
coherencePass ? "PASS" : "FAIL", status, mismatches);
allPass &= coherencePass;
if (texture != 0)
textureDestroy(texture);
if (surface != 0)
surfaceDestroy(surface);
}
if (result == 0)
std::printf("RGBA16F texture sample smoke test: %s\n", allPass ? "PASS" : "FAIL");
const int exitCode = result == 0 && allPass ? 0 : 1;
if (deviceOutput != 0)
memFree(deviceOutput);
if (array != nullptr)
arrayDestroy(array);
if (module != nullptr)
moduleUnload(module);
if (context != nullptr)
contextDestroy(context);
dlclose(library);
return exitCode;
}
File diff suppressed because it is too large. Load diff
+101
View File
@@ -0,0 +1,101 @@
@ stdcall cuInit(long) cuInit
@ stdcall cuDeviceGetCount(ptr) cuDeviceGetCount
@ stdcall cuDeviceGet(ptr long) cuDeviceGet
@ stdcall cuCtxPushCurrent_v2(ptr) cuCtxPushCurrent_v2
@ stdcall cuCtxPopCurrent_v2(ptr) cuCtxPopCurrent_v2
@ stdcall cuCtxGetDevice(ptr) cuCtxGetDevice
@ stdcall cuCtxCreate_v2(ptr long long) cuCtxCreate_v2
@ stdcall cuCtxDestroy_v2(ptr) cuCtxDestroy_v2
@ stdcall cuCtxSetCurrent(ptr) cuCtxSetCurrent
@ stdcall cuCtxSynchronize() cuCtxSynchronize
@ stdcall cuDeviceGetLuid(ptr ptr long) cuDeviceGetLuid
@ stdcall cuDeviceGetAttribute(ptr long long) cuDeviceGetAttribute
@ stdcall cuDeviceGetUuid(ptr long) cuDeviceGetUuid
@ stdcall cuGetErrorString(long ptr) cuGetErrorString
@ stdcall cuGetProcAddress(ptr ptr long long) cuGetProcAddress
@ stdcall cuGetProcAddress_v2(ptr ptr long long ptr) cuGetProcAddress_v2
@ stdcall cuArrayCreate(ptr ptr) cuArrayCreate
@ stdcall cuArray3DCreate(ptr ptr) cuArray3DCreate
@ stdcall cuArrayDestroy(ptr) cuArrayDestroy
@ stdcall cuArrayGetDescriptor(ptr ptr) cuArrayGetDescriptor
@ stdcall cuDestroyExternalMemory(ptr) cuDestroyExternalMemory
@ stdcall cuLaunchKernel(ptr long long long long long long long ptr ptr ptr) cuLaunchKernel
@ stdcall cuMemAlloc(ptr long long) cuMemAlloc
@ stdcall cuMemAllocHost(ptr long long) cuMemAllocHost
@ stdcall cuMemFree(long long) cuMemFree
@ stdcall cuMemFreeHost(ptr) cuMemFreeHost
@ stdcall cuMemcpy2D(ptr) cuMemcpy2D
@ stdcall cuMemcpyDtoH(ptr long long) cuMemcpyDtoH
@ stdcall cuMemcpyHtoDAsync(long long ptr long long ptr) cuMemcpyHtoDAsync
@ stdcall cuMipmappedArrayDestroy(ptr) cuMipmappedArrayDestroy
@ stdcall cuModuleGetFunction(ptr ptr ptr) cuModuleGetFunction
@ stdcall cuModuleLoad(ptr ptr) cuModuleLoad
@ stdcall cuModuleLoadData(ptr ptr) cuModuleLoadData
@ stdcall cuModuleLoadDataEx(ptr ptr long ptr ptr) cuModuleLoadDataEx
@ stdcall cuModuleUnload(ptr) cuModuleUnload
@ stdcall cuSurfObjectCreate(ptr ptr) cuSurfObjectCreate
@ stdcall cuSurfObjectDestroy(long long) cuSurfObjectDestroy
@ stdcall cuSurfObjectGetResourceDesc(ptr long long) cuSurfObjectGetResourceDesc
@ stdcall cuTexObjectCreate(ptr ptr ptr ptr) cuTexObjectCreate
@ stdcall cuTexObjectDestroy(long long) cuTexObjectDestroy
@ stdcall cuTexObjectGetResourceDesc(ptr long long) cuTexObjectGetResourceDesc
@ stdcall cuEventCreate(ptr ptr) cuEventCreate
@ stdcall cuEventDestroy(ptr) cuEventDestroy
@ stdcall cuEventDestroy_v2(ptr) cuEventDestroy_v2
@ stdcall cuEventRecord(ptr ptr) cuEventRecord
@ stdcall cuEventRecordWithFlags(ptr ptr ptr) cuEventRecordWithFlags
@ stdcall cuEventSynchronize(ptr) cuEventSynchronize
@ stdcall cuEventQuery(ptr) cuEventQuery
@ stdcall cuEventElapsedTime(ptr ptr ptr) cuEventElapsedTime
@ stdcall cuStreamCreate(ptr ptr) cuStreamCreate
@ stdcall cuStreamCreateWithPriority(ptr ptr ptr) cuStreamCreateWithPriority
@ stdcall cuStreamDestroy(ptr) cuStreamDestroy
@ stdcall cuStreamDestroy_v2(ptr) cuStreamDestroy_v2
@ stdcall cuStreamSynchronize(ptr) cuStreamSynchronize
@ stdcall cuStreamQuery(ptr) cuStreamQuery
@ stdcall cuStreamWaitEvent(ptr ptr ptr) cuStreamWaitEvent
@ stdcall cuStreamGetFlags(ptr ptr) cuStreamGetFlags
@ stdcall cuStreamGetPriority(ptr ptr) cuStreamGetPriority
@ stdcall cuCtxGetCurrent(ptr) cuCtxGetCurrent
@ stdcall cuCtxGetStreamPriorityRange(ptr ptr) cuCtxGetStreamPriorityRange
@ stdcall cuCtxGetLimit(ptr ptr) cuCtxGetLimit
@ stdcall cuCtxSetLimit(ptr ptr) cuCtxSetLimit
@ stdcall cuDriverGetVersion(ptr) cuDriverGetVersion
@ stdcall cuDeviceGetName(ptr ptr ptr) cuDeviceGetName
@ stdcall cuDeviceTotalMem(ptr ptr) cuDeviceTotalMem
@ stdcall cuDeviceTotalMem_v2(ptr ptr) cuDeviceTotalMem_v2
@ stdcall cuDeviceComputeCapability(ptr ptr ptr) cuDeviceComputeCapability
@ stdcall cuMemGetInfo(ptr ptr) cuMemGetInfo
@ stdcall cuMemGetInfo_v2(ptr ptr) cuMemGetInfo_v2
@ stdcall cuMemcpyHtoD(ptr ptr ptr) cuMemcpyHtoD
@ stdcall cuMemcpyHtoD_v2(ptr ptr ptr) cuMemcpyHtoD_v2
@ stdcall cuMemcpyDtoD(ptr ptr ptr) cuMemcpyDtoD
@ stdcall cuMemcpyDtoDAsync(ptr ptr ptr ptr) cuMemcpyDtoDAsync
@ stdcall cuMemcpyDtoHAsync(ptr ptr ptr ptr) cuMemcpyDtoHAsync
@ stdcall cuMemsetD8(ptr ptr ptr) cuMemsetD8
@ stdcall cuMemsetD16(ptr ptr ptr) cuMemsetD16
@ stdcall cuMemsetD32(ptr ptr ptr) cuMemsetD32
@ stdcall cuMemsetD8Async(ptr ptr ptr ptr) cuMemsetD8Async
@ stdcall cuMemsetD16Async(ptr ptr ptr ptr) cuMemsetD16Async
@ stdcall cuMemsetD32Async(ptr ptr ptr ptr) cuMemsetD32Async
@ stdcall cuMemsetD2D8(ptr ptr ptr ptr ptr) cuMemsetD2D8
@ stdcall cuMemsetD2D32(ptr ptr ptr ptr ptr) cuMemsetD2D32
@ stdcall cuFuncSetAttribute(ptr ptr ptr) cuFuncSetAttribute
@ stdcall cuFuncGetAttribute(ptr ptr ptr) cuFuncGetAttribute
@ stdcall cuModuleGetGlobal(ptr ptr ptr ptr) cuModuleGetGlobal
@ stdcall cuOccupancyMaxActiveBlocksPerMultiprocessor(ptr ptr ptr ptr) cuOccupancyMaxActiveBlocksPerMultiprocessor
@ stdcall cuOccupancyMaxPotentialBlockSize(ptr ptr ptr ptr ptr ptr) cuOccupancyMaxPotentialBlockSize
@ stdcall cuMemAllocPitch(ptr ptr ptr ptr ptr) cuMemAllocPitch
@ stdcall cuMemHostAlloc(ptr ptr ptr) cuMemHostAlloc
@ stdcall cuPointerGetAttribute(ptr ptr ptr) cuPointerGetAttribute
@ stdcall d4rImportVulkanMemory(ptr int64 int64 ptr ptr) d4rImportVulkanMemory
@ stdcall d4rReleaseVulkanMemory(ptr) d4rReleaseVulkanMemory
@ stdcall d4rMemcpy2DAsync(ptr ptr) d4rMemcpy2DAsync
@ stdcall d4rCtxSynchronize() d4rCtxSynchronize
@ stdcall d4rStreamWaitValue32(int64 long) d4rStreamWaitValue32
@ stdcall d4rWriteValue32(int64 long) d4rWriteValue32
@ stdcall d4rSetArrayRedirect(ptr int64 long) d4rSetArrayRedirect
@ stdcall d4rRegisterLinearTexture(int64 int64 int64 long long) d4rRegisterLinearTexture
@ stdcall d4rSetEnv(str str long) d4rSetEnv
@ stdcall d4rLoadError() d4rLoadError
@ stdcall d4rOutputKernelNative() d4rOutputKernelNative