d4r: official NVIDIA DLSS on AMD GPUs under Linux and Proton
This commit is contained in:
commit
118173ecd7
128 files changed
+31333
No files matched your search
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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())
|
||||
@@ -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(¶meters);
|
||||
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;
|
||||
}
|
||||
@@ -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, ®ion);
|
||||
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, ®ion);
|
||||
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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
Executable
+505
@@ -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())
|
||||
@@ -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
@@ -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(¶meters);
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
@@ -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(©);
|
||||
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(©_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[] = {¶ms};
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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(©), 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(©), 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(©), 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(©), 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(©), 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;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
Reference in new issue
Block a user