Fix Windows NVAPI loading the gfx1101 build on every other AMD GPU
0.1.5 shipped one d4r/zluda/nvapi64.dll (the gfx1101 build) for all targets, and compat_nvapi_path() preferred it over d4r/nvapi/<arch>/. Nothing on Windows ever set D4R_GPU_ARCH, so on any other GPU NVAPI failed with "No selected gfx1101 device", NGX CUDA init failed, and DLSS stayed unavailable (black frames, no OptiScaler upscaler section). - Select the NVAPI per architecture; never fall back to a shared DLL in a multi-target folder, and report a missing build by name. - The loader and runtime publish D4R_GPU_ARCH and D4R_GPU_LUID for the game's adapter, so NVAPI picks the right device with several GPUs. - nvapi64.dll exports the target it was built for; the runtime refuses a mismatch, and the packager and d4r-check read the same marker. - Release packaging no longer ships d4r/zluda/nvapi64.dll and rejects NVAPI builds for the wrong target, without a marker, or missing. - Move target resolution, device matching and loader choice into gpu_selection.h; regression tests cover all 11 targets, bad or missing D4R_GPU_ARCH, mixed installs, multi-GPU and missing files. - d4r-check: accept the FSR keys in the shipped d4r.ini.
This commit is contained in:
1 parent
df9f2cd012
commit
7da2f9ac39
16 files changed
+530
-46
No files matched your search
@@ -6,6 +6,7 @@
|
||||
// Keeping every file in one folder preserves NGX's search for
|
||||
// nvngx_dlss.dll beside the calling module.
|
||||
#include "d3d12_external.h"
|
||||
#include "gpu_environment.h"
|
||||
#include "ngx_public_abi.h"
|
||||
#include <dxgi.h>
|
||||
#include <fstream>
|
||||
@@ -54,22 +55,25 @@ HMODULE select(const LUID* luid) {
|
||||
d4r::diag::HipApi hip((dir / L"hip").string());
|
||||
if (hip.hipInit(0) != hipSuccess) throw std::runtime_error("the AMD HIP runtime found no usable GPU; update the AMD graphics driver");
|
||||
int count = 0; (void)hip.hipGetDeviceCount(&count);
|
||||
std::string found, detected;
|
||||
std::vector<d4r::diag::GpuCandidate> devices;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
hipDeviceProp_t props{};
|
||||
if (hip.hipGetDeviceProperties(&props, i) != hipSuccess) continue;
|
||||
const auto arch = d4r::diag::gpu_architecture(props.gcnArchName);
|
||||
detected += (detected.empty() ? "" : ", ") + arch + " (" + props.name + ")";
|
||||
if (luid && std::memcmp(props.luid, luid, sizeof(*luid))) continue;
|
||||
if (!luid && !std::filesystem::exists(dir / (L"_nvngx_" + d4r::diag::wide(arch) + L".dll"))) continue;
|
||||
if (!found.empty() && found != arch) throw std::runtime_error("several GPUs with different architectures; start the game once DLSS selects one");
|
||||
found = arch;
|
||||
d4r::diag::GpuCandidate device{props.name, d4r::diag::gpu_architecture(props.gcnArchName), {}};
|
||||
std::memcpy(device.luid.data(), props.luid, device.luid.size());
|
||||
devices.push_back(device);
|
||||
}
|
||||
if (found.empty()) throw std::runtime_error("no supported AMD GPU for this game's adapter. Detected: " +
|
||||
(detected.empty() ? std::string("none") : detected) + ". This release supports: " + supported_list(dir) + ".");
|
||||
d4r::diag::AdapterLuid wanted{};
|
||||
if (luid) std::memcpy(wanted.data(), luid, wanted.size());
|
||||
const auto choice = d4r::diag::choose_loader_architecture(devices, luid ? &wanted : nullptr,
|
||||
[&](const std::string& arch) { return std::filesystem::exists(dir / (L"_nvngx_" + d4r::diag::wide(arch) + L".dll")); },
|
||||
supported_list(dir));
|
||||
if (!choice.error.empty()) throw std::runtime_error(choice.error);
|
||||
const auto& found = choice.architecture;
|
||||
const auto path = dir / (L"_nvngx_" + d4r::diag::wide(found) + L".dll");
|
||||
if (!std::filesystem::exists(path))
|
||||
throw std::runtime_error("this release has no build for your GPU (" + found + "). It supports: " + supported_list(dir) + ".");
|
||||
// The shim, NVAPI and the NGX core start from the environment: state the adapter's real architecture (and,
|
||||
// once the game's adapter is known, its LUID) before the first of them loads, replacing any stale value.
|
||||
d4r::diag::publish_selected_gpu(found, luid ? &wanted : nullptr);
|
||||
shim = LoadLibraryExW(path.c_str(), nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
|
||||
if (!shim) {
|
||||
selectionFailed = true; // A broken install will not fix itself during this session.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
// Publishes the GPU the game renders on to the rest of the process. The NVAPI
|
||||
// compatibility DLL, the NGX core's CUDA initialization and any later shim
|
||||
// start from the environment, so the loader and the shim state the adapter's
|
||||
// real architecture and LUID before they load them.
|
||||
#include "gpu_selection.h"
|
||||
#include <windows.h>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace d4r::diag {
|
||||
// Updates the OS block (other DLLs, ZLUDA) and the CRT copy that getenv reads.
|
||||
inline void set_process_variable(const char* name, const std::string& value) {
|
||||
SetEnvironmentVariableA(name, value.c_str());
|
||||
_putenv_s(name, value.c_str());
|
||||
}
|
||||
inline void publish_selected_gpu(const std::string& architecture, const AdapterLuid* luid = nullptr) {
|
||||
set_process_variable("D4R_GPU_ARCH", architecture);
|
||||
if (luid) set_process_variable("D4R_GPU_LUID", format_luid(*luid));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
// GPU-architecture selection rules shared by the loader, the NGX shim and the
|
||||
// NVAPI compatibility DLL. Pure logic: no HIP, no Windows headers, so the same
|
||||
// code is unit-tested without a GPU (see player_config_probe.cpp).
|
||||
//
|
||||
// One physical adapter decides everything: its HIP architecture picks the
|
||||
// _nvngx_<arch>.dll shim, the nvapi\<arch>\nvapi64.dll and the native kernels,
|
||||
// and its LUID picks the device when several GPUs share an architecture.
|
||||
#include "gpu_target.h"
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace d4r::diag {
|
||||
using AdapterLuid = std::array<unsigned char, 8>; // Memory layout of LUID: LowPart, then HighPart.
|
||||
|
||||
struct GpuCandidate { std::string name, architecture; AdapterLuid luid{}; };
|
||||
|
||||
inline std::string supported_gpu_list() {
|
||||
std::string list;
|
||||
for (const auto& target : gpu_targets) list += (list.empty() ? "" : ", ") + std::string(target.architecture);
|
||||
return list;
|
||||
}
|
||||
inline std::string describe_gpus(const std::vector<GpuCandidate>& devices) {
|
||||
std::string text;
|
||||
for (const auto& device : devices) text += (text.empty() ? "" : ", ") + device.architecture + " (" + device.name + ")";
|
||||
return text.empty() ? "none" : text;
|
||||
}
|
||||
|
||||
// "HHHHHHHH:LLLLLLLL", the form the logs already use for adapter LUIDs.
|
||||
inline std::string format_luid(const AdapterLuid& luid) {
|
||||
unsigned low = 0, high = 0;
|
||||
for (int i = 0; i < 4; ++i) { low |= unsigned(luid[i]) << (8 * i); high |= unsigned(luid[4 + i]) << (8 * i); }
|
||||
char text[32]; std::snprintf(text, sizeof(text), "%08x:%08x", high, low);
|
||||
return text;
|
||||
}
|
||||
inline bool parse_luid(const char* text, AdapterLuid& luid) {
|
||||
const std::string value = text ? text : "";
|
||||
if (value.size() != 17 || value[8] != ':' || value.find_first_not_of("0123456789abcdefABCDEF:") != std::string::npos ||
|
||||
value.find(':', 9) != std::string::npos) return false;
|
||||
const unsigned high = std::stoul(value.substr(0, 8), nullptr, 16), low = std::stoul(value.substr(9), nullptr, 16);
|
||||
for (int i = 0; i < 4; ++i) { luid[i] = static_cast<unsigned char>(low >> (8 * i)); luid[4 + i] = static_cast<unsigned char>(high >> (8 * i)); }
|
||||
return true;
|
||||
}
|
||||
|
||||
// D4R_GPU_ARCH, when valid, names the architecture this process runs for; the
|
||||
// build's own target applies when it is absent. A set but unsupported value is
|
||||
// reported and ignored: it must not silently pick a different GPU target.
|
||||
struct TargetResolution { std::string architecture, source, warning; };
|
||||
inline TargetResolution resolve_target_arch(const char* configured, const char* built_in) {
|
||||
TargetResolution result{built_in, "build", {}};
|
||||
if (!configured || !*configured) return result;
|
||||
const std::string arch = gpu_architecture(configured); // "gfx1101:xnack-" -> "gfx1101"
|
||||
if (supported_gpu_target(arch)) { result.architecture = arch; result.source = "D4R_GPU_ARCH"; return result; }
|
||||
result.warning = std::string("D4R_GPU_ARCH='") + configured + "' is not a supported target (supported: " +
|
||||
supported_gpu_list() + "); using the build's target " + built_in;
|
||||
return result;
|
||||
}
|
||||
|
||||
inline int find_gpu_device(const std::vector<GpuCandidate>& devices, const std::string& target, int requested = -1,
|
||||
const AdapterLuid* luid = nullptr) {
|
||||
for (size_t i = 0; i < devices.size(); ++i)
|
||||
if (devices[i].architecture == target && (requested < 0 || requested == int(i)) && (!luid || devices[i].luid == *luid))
|
||||
return int(i);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Why no device matched, in terms a player can act on: unsupported hardware,
|
||||
// a file built for another GPU, or an adapter HIP cannot see.
|
||||
inline std::string no_device_message(const std::vector<GpuCandidate>& devices, const std::string& target,
|
||||
const AdapterLuid* luid = nullptr) {
|
||||
const std::string prefix = "No selected " + target + " device for this adapter. ";
|
||||
if (devices.empty()) return prefix + "The AMD HIP runtime reports no GPU; update the AMD graphics driver.";
|
||||
const GpuCandidate* actual = nullptr;
|
||||
if (luid) {
|
||||
for (const auto& device : devices) if (device.luid == *luid) actual = &device;
|
||||
if (!actual) return prefix + "No HIP device has the game's adapter LUID " + format_luid(*luid) + ". HIP sees: " + describe_gpus(devices) + ".";
|
||||
} else {
|
||||
for (const auto& device : devices) if (supported_gpu_target(device.architecture)) { actual = &device; break; }
|
||||
}
|
||||
if (!actual) return prefix + "Detected: " + describe_gpus(devices) + ", none supported by d4r (supported: " + supported_gpu_list() + ").";
|
||||
if (!supported_gpu_target(actual->architecture))
|
||||
return prefix + "The GPU is " + actual->architecture + " (" + actual->name + "), which d4r does not support (supported: " + supported_gpu_list() + ").";
|
||||
return prefix + "The GPU is " + actual->architecture + " (" + actual->name + ") but this file is built for " + target +
|
||||
". A file for another GPU was loaded (nvapi\\" + actual->architecture + "\\nvapi64.dll, _nvngx_" + actual->architecture +
|
||||
".dll, or a stale D4R_GPU_ARCH); extract the d4r ZIP again.";
|
||||
}
|
||||
|
||||
// An NVAPI (or any per-target file) that names another target than the selected GPU's is a mixed install.
|
||||
inline std::string nvapi_target_error(const std::string& built, const std::string& wanted, const std::string& path) {
|
||||
if (built == wanted) return {};
|
||||
return "nvapi64.dll (" + path + ") is built for " + (built.empty() ? std::string("an unknown target") : built) +
|
||||
" but the selected GPU is " + wanted + "; use d4r\\nvapi\\" + wanted + "\\nvapi64.dll and extract the d4r ZIP again if files are mixed up";
|
||||
}
|
||||
|
||||
// Loader: the HIP architecture of the game's adapter (matched by LUID), or,
|
||||
// before any adapter is known, the only architecture that has a build.
|
||||
struct LoaderChoice { std::string architecture, error; };
|
||||
inline LoaderChoice choose_loader_architecture(const std::vector<GpuCandidate>& devices, const AdapterLuid* luid,
|
||||
const std::function<bool(const std::string&)>& has_build, const std::string& available) {
|
||||
LoaderChoice choice;
|
||||
for (const auto& device : devices) {
|
||||
if (luid && device.luid != *luid) continue;
|
||||
if (!luid && !has_build(device.architecture)) continue;
|
||||
if (!choice.architecture.empty() && choice.architecture != device.architecture)
|
||||
return {{}, "several GPUs with different architectures; start the game once DLSS selects one"};
|
||||
choice.architecture = device.architecture;
|
||||
}
|
||||
if (choice.architecture.empty())
|
||||
choice.error = "no supported AMD GPU for this game's adapter. Detected: " + describe_gpus(devices) +
|
||||
". This release supports: " + available + ".";
|
||||
else if (!supported_gpu_target(choice.architecture))
|
||||
choice.error = "this GPU (" + choice.architecture + ") is not supported by d4r. This release supports: " + available + ".";
|
||||
else if (!has_build(choice.architecture))
|
||||
choice.error = "this release has no build for your GPU (" + choice.architecture + "). It supports: " + available + ".";
|
||||
if (!choice.error.empty()) choice.architecture.clear();
|
||||
return choice;
|
||||
}
|
||||
}
|
||||
+30
-16
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include "diagnostic.h"
|
||||
#include "gpu_target.h"
|
||||
#include "gpu_selection.h"
|
||||
#include <hip/hip_runtime_api.h>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
@@ -43,14 +43,16 @@ struct HipApi {
|
||||
if (verbose || result != hipSuccess) std::printf("HIP %s -> %d (%s)\n", call, static_cast<int>(result), hipGetErrorName(result));
|
||||
if (result != hipSuccess) throw std::runtime_error(std::string(call) + ": " + hipGetErrorString(result));
|
||||
}
|
||||
// The package's backend sets D4R_GPU_ARCH to the GPU type of the game's adapter before it loads this DLL, so one
|
||||
// build serves every supported type (a compile-time-only target locked RDNA3 / gfx1201 cards out: "No selected
|
||||
// gfx1200 device"). Unknown or missing values fall back to the build's own target.
|
||||
// The architecture this process runs for: D4R_GPU_ARCH when it names a supported target, otherwise the build's
|
||||
// own D4R_TARGET_ARCH. The loader and the shim publish the adapter's real architecture there before the
|
||||
// NVAPI/NGX components start (publish_selected_gpu in gpu_environment.h), so every component agrees. A value that
|
||||
// is set but unsupported is reported and ignored; select_architecture still refuses any device that is not of
|
||||
// the architecture in use.
|
||||
static const char* target_arch() {
|
||||
static const std::string arch = [] {
|
||||
const char* e = std::getenv("D4R_GPU_ARCH");
|
||||
const std::string v = e ? gpu_architecture(e) : std::string(); // "gfx1101:xnack-" -> "gfx1101"
|
||||
return supported_gpu_target(v) ? v : std::string(D4R_TARGET_ARCH);
|
||||
const auto resolved = resolve_target_arch(std::getenv("D4R_GPU_ARCH"), D4R_TARGET_ARCH);
|
||||
if (!resolved.warning.empty()) std::fprintf(stderr, "d4r: %s\n", resolved.warning.c_str());
|
||||
return resolved.architecture;
|
||||
}();
|
||||
return arch.c_str();
|
||||
}
|
||||
@@ -59,6 +61,14 @@ struct HipApi {
|
||||
const char* e = std::getenv("D4R_HIP_DEVICE");
|
||||
return e && *e ? std::atoi(e) : -1;
|
||||
}
|
||||
// The adapter the loader or shim selected (D4R_GPU_LUID), for components that start before any D3D12 device is
|
||||
// at hand. Absent means "any device of the target architecture"; a malformed value is an error, never ignored.
|
||||
static bool configured_luid(AdapterLuid& luid) {
|
||||
const char* text = std::getenv("D4R_GPU_LUID");
|
||||
if (!text || !*text) return false;
|
||||
if (!parse_luid(text, luid)) throw std::runtime_error(std::string("D4R_GPU_LUID='") + text + "' is not HHHHHHHH:LLLLLLLL");
|
||||
return true;
|
||||
}
|
||||
int select_architecture(int requested, hipDeviceProp_t& selected, const void* required_luid = nullptr,
|
||||
bool allow_no_match = false) const {
|
||||
// Architecture spoofing invalidates a gfx12 correctness test.
|
||||
@@ -71,24 +81,28 @@ struct HipApi {
|
||||
std::printf("HIP runtime=%d driver_api=%d headers=%d.%d.%d sizeof(properties)=%zu\n",
|
||||
version, driver, HIP_VERSION_MAJOR, HIP_VERSION_MINOR, HIP_VERSION_PATCH, sizeof(hipDeviceProp_t));
|
||||
check(hipGetDeviceCount(&count), "hipGetDeviceCount");
|
||||
int found = -1;
|
||||
std::vector<GpuCandidate> devices;
|
||||
requested = requested_device(requested);
|
||||
AdapterLuid wanted{};
|
||||
const AdapterLuid* luid = nullptr;
|
||||
if (required_luid) { std::memcpy(wanted.data(), required_luid, wanted.size()); luid = &wanted; }
|
||||
else if (configured_luid(wanted)) luid = &wanted;
|
||||
std::vector<hipDeviceProp_t> properties;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
hipDeviceProp_t props{};
|
||||
check(hipGetDeviceProperties(&props, i), "hipGetDevicePropertiesR0600");
|
||||
std::printf("GPU ordinal=%d name=%s gcnArchName=%s wave=%d vram=%zu pci=%04x:%02x:%02x\n",
|
||||
i, props.name, props.gcnArchName, props.warpSize, props.totalGlobalMem,
|
||||
props.pciDomainID, props.pciBusID, props.pciDeviceID);
|
||||
const std::string arch = props.gcnArchName;
|
||||
if (gpu_architecture(arch) == target_arch() && (requested < 0 || requested == i) && found < 0 &&
|
||||
(!required_luid || std::memcmp(props.luid, required_luid, sizeof(props.luid)) == 0)) {
|
||||
found = i;
|
||||
selected = props;
|
||||
}
|
||||
GpuCandidate candidate{props.name, gpu_architecture(props.gcnArchName), {}};
|
||||
std::memcpy(candidate.luid.data(), props.luid, candidate.luid.size());
|
||||
devices.push_back(candidate);
|
||||
properties.push_back(props);
|
||||
}
|
||||
const int found = find_gpu_device(devices, target_arch(), requested, luid);
|
||||
if (found < 0 && allow_no_match) return -1; // Capability enumeration can include Intel/WARP adapters.
|
||||
if (found < 0) throw std::runtime_error(std::string("No selected ") + target_arch() +
|
||||
" device for this adapter; use the package matching the actual HIP architecture (see GPU log)");
|
||||
if (found < 0) throw std::runtime_error(no_device_message(devices, target_arch(), luid));
|
||||
selected = properties[found];
|
||||
require_gpu_target(selected.gcnArchName, target_arch());
|
||||
check(hipSetDevice(found), "hipSetDevice");
|
||||
std::printf("SELECTED HIP ordinal=%d architecture=%s validation=%s\n", found, target_arch(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "d3d12_external.h"
|
||||
#include "gpu_environment.h"
|
||||
#include "d3d12_boundary_timing.h"
|
||||
#include "pixel_conversion.h"
|
||||
#include "cuda_image_api.h"
|
||||
@@ -23,6 +24,19 @@ inline std::string env_path(const char* name) {
|
||||
if (!std::filesystem::path(diag::wide(value)).is_absolute()) throw std::runtime_error(std::string(name) + " must be absolute");
|
||||
return value;
|
||||
}
|
||||
// The NVAPI compatibility DLL names the GPU target it was built for. A file for another GPU (a mixed install, or
|
||||
// a shared copy from an older release) is refused here with the fix spelled out; older builds without the export
|
||||
// are accepted and still validate the device themselves.
|
||||
inline void require_nvapi_target(const diag::Library& nvapi, const std::string& path) {
|
||||
using Target = const char*(__cdecl*)();
|
||||
Target target = nullptr;
|
||||
try { target = nvapi.symbol<Target>("d4r_NvapiTargetArchitecture"); }
|
||||
catch (const std::exception&) { std::printf("D4R_NVAPI target=unknown (no d4r_NvapiTargetArchitecture export) path=%s\n", path.c_str()); return; }
|
||||
const std::string built = target ? target() : "";
|
||||
const std::string wanted = diag::HipApi::target_arch();
|
||||
std::printf("D4R_NVAPI target=%s wanted=%s path=%s\n", built.c_str(), wanted.c_str(), path.c_str());
|
||||
if (const auto error = diag::nvapi_target_error(built, wanted, path); !error.empty()) throw std::runtime_error(error);
|
||||
}
|
||||
inline void ngx_check(unsigned result, const char* operation) {
|
||||
if (result != 1 || !std::getenv("D4R_QUIET_API")) std::printf("D4R_NGX %s result=0x%08x\n", operation, result);
|
||||
if (result != 1) throw std::runtime_error(std::string(operation) + " NGX=" + std::to_string(result));
|
||||
@@ -105,6 +119,9 @@ struct Runtime {
|
||||
hip.select_architecture(-1, props, &luid);
|
||||
hip.verbose = cuda.verbose = !std::getenv("D4R_QUIET_API");
|
||||
if (std::memcmp(&luid, props.luid, sizeof(luid))) throw std::runtime_error("D3D12/HIP LUID mismatch");
|
||||
// NVAPI and the NGX core start from the environment: tell them which adapter and architecture this is.
|
||||
diag::AdapterLuid selectedLuid{}; std::memcpy(selectedLuid.data(), props.luid, selectedLuid.size());
|
||||
diag::publish_selected_gpu(diag::HipApi::target_arch(), &selectedLuid);
|
||||
cuda.check(cuda.cuInit(0), "cuInit(Windows runtime)");
|
||||
int count = 0; cuda.check(cuda.cuDeviceGetCount(&count), "cuDeviceGetCount");
|
||||
for (int i = 0; i < count; ++i) {
|
||||
@@ -124,6 +141,7 @@ struct Runtime {
|
||||
// NGX's loader can probe CUDA during DLL initialization: establish
|
||||
// the correct primary context before loading either NVIDIA DLL.
|
||||
nvapi = std::make_unique<diag::Library>(diag::wide(env_path("D4R_NVAPI_DLL")));
|
||||
require_nvapi_target(*nvapi, env_path("D4R_NVAPI_DLL"));
|
||||
dlss = std::make_unique<diag::Library>(diag::wide(env_path("D4R_DLSS_DLL")));
|
||||
core = std::make_unique<diag::Library>(diag::wide(env_path("D4R_NGX_CORE")));
|
||||
allocate = core->symbol<Allocate>("NVSDK_NGX_CUDA_AllocateParameters");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "diagnostic.h"
|
||||
#include "gpu_target.h"
|
||||
#if defined(D4R_NVAPI_COMPAT)
|
||||
#include "hip_api.h"
|
||||
#include <memory>
|
||||
@@ -83,6 +84,14 @@ int __cdecl logical_info(void* logical, LogicalGpuData* info)
|
||||
}
|
||||
#endif
|
||||
|
||||
// The GPU target this DLL was built for. The marker string is also searched for by the packaging scripts, so a
|
||||
// release cannot ship (and a shim cannot load) an NVAPI built for another GPU.
|
||||
static const char target_marker[] = "D4R_NVAPI_TARGET=" D4R_TARGET_ARCH;
|
||||
extern "C" __declspec(dllexport) const char* __cdecl d4r_NvapiTargetArchitecture()
|
||||
{
|
||||
return target_marker + sizeof("D4R_NVAPI_TARGET=") - 1;
|
||||
}
|
||||
|
||||
// Trace build forwards responses unchanged. The compatibility build adds the
|
||||
// public topology APIs above; other queries still go to upstream ZLUDA NVAPI.
|
||||
extern "C" __declspec(dllexport) void* __cdecl nvapi_QueryInterface(unsigned id)
|
||||
|
||||
@@ -272,11 +272,39 @@ inline void verify_native_fp8_bundle(const std::filesystem::path& dir, const Set
|
||||
contents.find("tokenLanes=true\n") == std::string::npos)
|
||||
throw std::runtime_error("[Kernels] NativeFp8=true needs matching gfx12 token-lane M kernels; rebuild the package with -TokenLanes and restart.");
|
||||
}
|
||||
inline std::filesystem::path compat_nvapi_path(const std::filesystem::path& dir) {
|
||||
const auto colocated = dir / L"zluda" / L"nvapi64.dll";
|
||||
if (std::filesystem::is_regular_file(colocated)) return colocated;
|
||||
const auto arch = dir / L"nvapi" / diag::wide(diag::HipApi::target_arch()) / L"nvapi64.dll";
|
||||
return std::filesystem::is_regular_file(arch) ? arch : dir / L"nvapi" / L"nvapi64.dll";
|
||||
// The compatibility NVAPI is built for one GPU target, so it is never shared between targets. A release carries
|
||||
// one per target in nvapi\<arch>\; that copy is the only acceptable one for <arch>. A single-target layout (quick
|
||||
// test, game package) has just nvapi\nvapi64.dll or the copy beside nvcuda.dll, and the shim then checks the
|
||||
// target the file names. In a multi-target folder a missing copy is an error: falling back to a shared DLL is
|
||||
// how a gfx1101 NVAPI once served a gfx1151 GPU ("No selected gfx1101 device").
|
||||
struct NvapiChoice { std::filesystem::path path; std::string error; };
|
||||
inline NvapiChoice select_compat_nvapi(const std::filesystem::path& dir, const std::string& arch) {
|
||||
const auto file = [&](const std::string& target) { return dir / L"nvapi" / diag::wide(target) / L"nvapi64.dll"; };
|
||||
if (std::filesystem::is_regular_file(file(arch))) return {file(arch), {}};
|
||||
std::string others;
|
||||
for (const auto& target : diag::gpu_targets)
|
||||
if (std::filesystem::is_regular_file(file(target.architecture)))
|
||||
others += (others.empty() ? "" : ", ") + std::string(target.architecture);
|
||||
if (!others.empty())
|
||||
return {{}, "this release has no NVAPI build for " + arch + " (d4r\\nvapi\\" + arch + "\\nvapi64.dll is missing; it has " +
|
||||
others + "); extract the d4r ZIP again"};
|
||||
for (const auto& single : {dir / L"nvapi" / L"nvapi64.dll", dir / L"zluda" / L"nvapi64.dll"})
|
||||
if (std::filesystem::is_regular_file(single)) return {single, {}};
|
||||
return {{}, "nvapi64.dll is missing (expected d4r\\nvapi\\" + arch + "\\nvapi64.dll); extract the d4r ZIP again"};
|
||||
}
|
||||
inline std::filesystem::path compat_nvapi_path(const std::filesystem::path& dir, const std::string& arch = diag::HipApi::target_arch()) {
|
||||
const auto choice = select_compat_nvapi(dir, arch);
|
||||
if (!choice.error.empty()) throw std::runtime_error(choice.error);
|
||||
return choice.path;
|
||||
}
|
||||
// A shared nvapi64.dll beside nvcuda.dll (d4r 0.1.5 shipped the gfx1101 build there for every GPU) is on PATH and
|
||||
// could be loaded by name ahead of the right file; it is ignored here, so say so.
|
||||
inline void note_stale_shared_nvapi(const std::filesystem::path& dir, std::vector<std::string>& notes) {
|
||||
bool perTarget = false;
|
||||
for (const auto& target : diag::gpu_targets)
|
||||
perTarget = perTarget || std::filesystem::is_regular_file(dir / L"nvapi" / diag::wide(target.architecture) / L"nvapi64.dll");
|
||||
if (perTarget && std::filesystem::is_regular_file(dir / L"zluda" / L"nvapi64.dll"))
|
||||
notes.push_back("d4r\\zluda\\nvapi64.dll is left over from an older d4r release (one GPU's build shared by all); it is ignored. Delete it");
|
||||
}
|
||||
inline std::filesystem::path raw_nvapi_backend_path(const std::filesystem::path& dir) {
|
||||
return dir / L"nvapi" / L"backend" / L"nvapi64.dll";
|
||||
@@ -325,7 +353,10 @@ inline void apply(const std::filesystem::path& dir, const Settings& settings) {
|
||||
const auto p = [&](const std::wstring& relative) { return std::optional<std::wstring>((dir / relative).wstring()); };
|
||||
const std::optional<std::wstring> on = std::wstring(L"1"), off = std::wstring(L"0"), unset;
|
||||
const wchar_t* path = _wgetenv(L"PATH");
|
||||
const auto compatNvapi = compat_nvapi_path(dir);
|
||||
const std::string arch = diag::HipApi::target_arch();
|
||||
// Startup has already verified the file (verify_nvapi); a missing one leaves D4R_NVAPI_DLL unset so a stale
|
||||
// value from an earlier run cannot point at a DLL for another GPU.
|
||||
const auto nvapi = select_compat_nvapi(dir, arch);
|
||||
// Same codegen and paths as windows-game.ps1; only diagnostics differ.
|
||||
const std::pair<const wchar_t*, std::optional<std::wstring>> values[] = {
|
||||
{L"D4R_RR_BACKEND", settings.rrBackend == "off" ? unset : std::optional<std::wstring>(diag::wide(settings.rrBackend))},
|
||||
@@ -339,7 +370,8 @@ inline void apply(const std::filesystem::path& dir, const Settings& settings) {
|
||||
{L"D4R_FSR_CACHE_DIR", settings.rrBackend == "fsr" ? std::optional<std::wstring>((cache / L"vkd3d").wstring()) : unset},
|
||||
{L"D4R_HIP_ROOT", p(L"hip")}, {L"HIP_PATH", p(L"hip")},
|
||||
{L"D4R_NVCUDA_DLL", p(L"zluda\\nvcuda.dll")}, {L"ZLUDA_CUDA_LIB", p(L"zluda\\nvcuda.dll")},
|
||||
{L"D4R_NVAPI_DLL", std::optional<std::wstring>(compatNvapi.wstring())},
|
||||
{L"D4R_GPU_ARCH", std::optional<std::wstring>(diag::wide(arch))},
|
||||
{L"D4R_NVAPI_DLL", nvapi.error.empty() ? std::optional<std::wstring>(nvapi.path.wstring()) : unset},
|
||||
{L"D4R_NVAPI_BACKEND", std::optional<std::wstring>(raw_nvapi_backend_path(dir).wstring())},
|
||||
{L"D4R_NGX_CORE", p(L"ngx\\_nvngx.dll")}, {L"D4R_DLSS_DLL", p(L"dlss\\nvngx_dlss.dll")},
|
||||
{L"D4R_NGX_DATA_DIR", std::optional<std::wstring>(ngxData.wstring())},
|
||||
@@ -392,6 +424,8 @@ inline void ensure_environment() {
|
||||
reject_translation_layer();
|
||||
if (settings.nativeFp8) verify_native_fp8_bundle(dir, &settings);
|
||||
verify_code_objects(dir, &settings);
|
||||
compat_nvapi_path(dir); // Throws with the fix spelled out when this GPU has no NVAPI build.
|
||||
note_stale_shared_nvapi(dir, current.notes);
|
||||
apply(dir, settings);
|
||||
current.active = true; current.preset = settings.preset;
|
||||
const char* model = settings.preset == 5 ? "E" : settings.preset == 12 ? "L" : settings.preset == 13 ? "M" : "K";
|
||||
|
||||
@@ -21,10 +21,194 @@ std::string error_of(const std::function<void()>& fn) {
|
||||
try { fn(); } catch (const std::exception& error) { return error.what(); }
|
||||
return {};
|
||||
}
|
||||
|
||||
using d4r::diag::AdapterLuid;
|
||||
using d4r::diag::GpuCandidate;
|
||||
AdapterLuid luid_of(unsigned low, unsigned high = 0) {
|
||||
AdapterLuid luid{};
|
||||
for (int i = 0; i < 4; ++i) { luid[i] = static_cast<unsigned char>(low >> (8 * i)); luid[4 + i] = static_cast<unsigned char>(high >> (8 * i)); }
|
||||
return luid;
|
||||
}
|
||||
void write_file(const std::filesystem::path& path, const std::string& text) {
|
||||
std::filesystem::create_directories(path.parent_path());
|
||||
std::ofstream(path, std::ios::binary) << text;
|
||||
}
|
||||
bool contains(const std::string& text, const std::string& part) { return text.find(part) != std::string::npos; }
|
||||
|
||||
// Architecture selection rules that decide which NVAPI, shim and kernels a GPU gets. Pure logic, so every
|
||||
// registered target is covered here without the hardware; `builtNvapi` (optional) holds real nvapi64.dll
|
||||
// builds, one per target, to check the compiled-in target marker the same way the shim and packager do.
|
||||
void architecture_selection(const std::filesystem::path& root, const std::filesystem::path& builtNvapi) {
|
||||
namespace diag = d4r::diag;
|
||||
std::filesystem::remove_all(root);
|
||||
std::vector<std::string> archs;
|
||||
for (const auto& target : diag::gpu_targets) archs.push_back(target.architecture);
|
||||
expect(archs.size() == 11, "the registry lists the 11 Windows GPU targets");
|
||||
|
||||
// D4R_GPU_ARCH: valid, suffixed, missing, empty and unsupported values.
|
||||
for (const auto& arch : archs) {
|
||||
auto r = diag::resolve_target_arch(arch.c_str(), "gfx1101");
|
||||
expect(r.architecture == arch && r.source == "D4R_GPU_ARCH" && r.warning.empty(), "D4R_GPU_ARCH=" + arch + " selects " + arch);
|
||||
r = diag::resolve_target_arch((arch + ":xnack-").c_str(), "gfx1201");
|
||||
expect(r.architecture == arch, "feature-suffixed D4R_GPU_ARCH is normalized for " + arch);
|
||||
r = diag::resolve_target_arch(nullptr, arch.c_str());
|
||||
expect(r.architecture == arch && r.source == "build" && r.warning.empty(), "missing D4R_GPU_ARCH uses the build target " + arch);
|
||||
r = diag::resolve_target_arch("", arch.c_str());
|
||||
expect(r.architecture == arch && r.warning.empty(), "empty D4R_GPU_ARCH uses the build target " + arch);
|
||||
}
|
||||
for (const char* bad : {"gfx1030", "gfx9999", "GFX1101", "1101", "gfx1101 ", "rdna3"}) {
|
||||
const auto r = diag::resolve_target_arch(bad, "gfx1151");
|
||||
expect(r.architecture == "gfx1151" && r.source == "build" && contains(r.warning, std::string("'") + bad + "' is not a supported target"),
|
||||
std::string("unsupported D4R_GPU_ARCH='") + bad + "' is reported and the build target is kept");
|
||||
}
|
||||
|
||||
// LUID text used to hand the game's adapter to NVAPI.
|
||||
for (const auto luid : {luid_of(0), luid_of(0x1a2b3c4d, 0), luid_of(0xdeadbeef, 0x7), luid_of(0xffffffff, 0xffffffff)}) {
|
||||
AdapterLuid parsed{};
|
||||
expect(diag::parse_luid(diag::format_luid(luid).c_str(), parsed) && parsed == luid, "LUID round trip " + diag::format_luid(luid));
|
||||
}
|
||||
AdapterLuid scratch{};
|
||||
for (const char* bad : {"", "1234", "0000000000000000", "0000000:0000000a", "0000000g:00000001", "00000000:0000000100", "0x000000:00000001", "00000000:+0000001"})
|
||||
expect(!diag::parse_luid(bad, scratch), std::string("malformed LUID '") + bad + "' is rejected");
|
||||
|
||||
// Release layout: nvapi\<arch>\nvapi64.dll for all targets plus the 0.1.5-style shared copy beside nvcuda.
|
||||
const auto release = root / L"release";
|
||||
for (const auto& arch : archs) write_file(release / L"nvapi" / diag::wide(arch) / L"nvapi64.dll", "nvapi " + arch);
|
||||
write_file(release / L"zluda" / L"nvapi64.dll", "nvapi gfx1101 (shared)");
|
||||
write_file(release / L"nvapi" / L"backend" / L"nvapi64.dll", "raw backend");
|
||||
for (const auto& arch : archs) {
|
||||
const auto choice = player::select_compat_nvapi(release, arch);
|
||||
expect(choice.error.empty() && choice.path == release / L"nvapi" / diag::wide(arch) / L"nvapi64.dll" && player::read_text(choice.path) == "nvapi " + arch,
|
||||
"NVAPI for " + arch + " is its own nvapi\\" + arch + " build, not the shared gfx1101 copy");
|
||||
}
|
||||
std::vector<std::string> notes;
|
||||
player::note_stale_shared_nvapi(release, notes);
|
||||
expect(has_note(notes, "left over from an older d4r release"), "a leftover shared NVAPI is called out");
|
||||
|
||||
// A missing per-target build is an error that names the fix; no other file may stand in for it.
|
||||
for (const auto& arch : archs) {
|
||||
const auto folder = root / (L"missing-" + diag::wide(arch));
|
||||
std::filesystem::remove_all(folder);
|
||||
std::filesystem::copy(release, folder, std::filesystem::copy_options::recursive);
|
||||
std::filesystem::remove(folder / L"nvapi" / diag::wide(arch) / L"nvapi64.dll");
|
||||
const auto choice = player::select_compat_nvapi(folder, arch);
|
||||
expect(choice.path.empty() && contains(choice.error, "no NVAPI build for " + arch) && contains(choice.error, "nvapi\\" + arch + "\\nvapi64.dll"),
|
||||
"missing nvapi\\" + arch + " fails with a clear message instead of using another DLL");
|
||||
expect(!error_of([&] { player::compat_nvapi_path(folder, arch); }).empty(), "compat_nvapi_path(" + arch + ") throws when its build is missing");
|
||||
std::filesystem::remove_all(folder);
|
||||
}
|
||||
|
||||
// Single-target layouts (quick test, game package, development folders).
|
||||
const auto single = root / L"single";
|
||||
expect(contains(player::select_compat_nvapi(single, "gfx1151").error, "nvapi64.dll is missing"), "an empty folder has no NVAPI");
|
||||
write_file(single / L"zluda" / L"nvapi64.dll", "single zluda");
|
||||
expect(player::select_compat_nvapi(single, "gfx1151").path == single / L"zluda" / L"nvapi64.dll", "a single-target package may keep NVAPI beside nvcuda");
|
||||
write_file(single / L"nvapi" / L"nvapi64.dll", "single nvapi");
|
||||
expect(player::select_compat_nvapi(single, "gfx1151").path == single / L"nvapi" / L"nvapi64.dll", "nvapi\\nvapi64.dll outranks the copy beside nvcuda");
|
||||
notes.clear(); player::note_stale_shared_nvapi(single, notes);
|
||||
expect(notes.empty(), "single-target layouts are not flagged as stale");
|
||||
|
||||
// The environment the shim hands to ZLUDA, NVAPI and NGX.
|
||||
{ // target_arch() is fixed per process; the per-target matrix above covers the other architectures.
|
||||
const auto folder = root / L"apply";
|
||||
for (const auto& other : archs) write_file(folder / L"nvapi" / diag::wide(other) / L"nvapi64.dll", "nvapi " + other);
|
||||
auto s = settings("", notes); s.cacheDir = (folder / L"cache").wstring();
|
||||
player::apply(folder, s);
|
||||
const auto current = std::string(diag::HipApi::target_arch());
|
||||
expect(_wgetenv(L"D4R_GPU_ARCH") && std::wstring(_wgetenv(L"D4R_GPU_ARCH")) == diag::wide(current), "player environment publishes D4R_GPU_ARCH=" + current);
|
||||
expect(std::filesystem::path(_wgetenv(L"D4R_NVAPI_DLL")) == folder / L"nvapi" / diag::wide(current) / L"nvapi64.dll",
|
||||
"player environment points D4R_NVAPI_DLL at the build for " + current);
|
||||
std::filesystem::remove(folder / L"nvapi" / diag::wide(current) / L"nvapi64.dll");
|
||||
player::apply(folder, s);
|
||||
expect(!std::getenv("D4R_NVAPI_DLL"), "a missing build clears D4R_NVAPI_DLL instead of keeping a stale path");
|
||||
}
|
||||
|
||||
// Several GPUs: iGPU + two dGPUs of one architecture + every registered target on one machine.
|
||||
const std::vector<GpuCandidate> machine{{"AMD Radeon 780M", "gfx1103", luid_of(10)}, {"AMD Radeon RX 7700 XT", "gfx1101", luid_of(11)},
|
||||
{"AMD Radeon RX 7700 XT #2", "gfx1101", luid_of(12)}, {"Radeon 8060S", "gfx1151", luid_of(13)}};
|
||||
expect(diag::find_gpu_device(machine, "gfx1101") == 1, "without a LUID the first device of the architecture is used");
|
||||
const auto second = luid_of(12), igpu = luid_of(10), unknown = luid_of(99);
|
||||
expect(diag::find_gpu_device(machine, "gfx1101", -1, &second) == 2, "the LUID picks the second of two same-architecture GPUs");
|
||||
expect(diag::find_gpu_device(machine, "gfx1103", -1, &igpu) == 0, "the iGPU is found by LUID");
|
||||
expect(diag::find_gpu_device(machine, "gfx1101", -1, &igpu) == -1, "a LUID of another architecture does not match");
|
||||
expect(diag::find_gpu_device(machine, "gfx1101", 2) == 2 && diag::find_gpu_device(machine, "gfx1101", 3) == -1, "an explicit ordinal must also be of the target architecture");
|
||||
expect(diag::find_gpu_device(machine, "gfx1151", -1, &unknown) == -1, "an unknown adapter LUID matches nothing");
|
||||
std::vector<GpuCandidate> everything;
|
||||
for (size_t i = 0; i < archs.size(); ++i) everything.push_back({"GPU " + archs[i], archs[i], luid_of(100 + unsigned(i))});
|
||||
for (size_t i = 0; i < archs.size(); ++i) {
|
||||
const auto luid = luid_of(100 + unsigned(i));
|
||||
bool exact = diag::find_gpu_device(everything, archs[i], -1, &luid) == int(i) && diag::find_gpu_device(everything, archs[i]) == int(i);
|
||||
for (size_t j = 0; j < archs.size(); ++j) exact = exact && (j == i || diag::find_gpu_device(everything, archs[j], -1, &luid) == -1);
|
||||
expect(exact, "among all 11 architectures " + archs[i] + " resolves only to its own adapter");
|
||||
}
|
||||
|
||||
// Diagnostics for genuinely unsupported or mismatched GPUs (no crash, an actionable message).
|
||||
auto message = diag::no_device_message(machine, "gfx1101", &unknown);
|
||||
expect(contains(message, "No HIP device has the game's adapter LUID") && contains(message, "gfx1151 (Radeon 8060S)"), "unknown adapter: " + message);
|
||||
const auto strix = luid_of(13);
|
||||
message = diag::no_device_message(machine, "gfx1101", &strix);
|
||||
expect(contains(message, "The GPU is gfx1151") && contains(message, "built for gfx1101") && contains(message, "extract the d4r ZIP again"),
|
||||
"a gfx1101 file on a gfx1151 adapter is named as a mixed install: " + message);
|
||||
const std::vector<GpuCandidate> rdna2{{"AMD Radeon RX 6800", "gfx1030", luid_of(5)}};
|
||||
const auto six = luid_of(5);
|
||||
message = diag::no_device_message(rdna2, "gfx1101", &six);
|
||||
expect(contains(message, "gfx1030") && contains(message, "does not support") && contains(message, "gfx1154"), "unsupported RDNA2 GPU: " + message);
|
||||
message = diag::no_device_message(rdna2, "gfx1201");
|
||||
expect(contains(message, "none supported by d4r"), "no supported device at all: " + message);
|
||||
expect(contains(diag::no_device_message({}, "gfx1100"), "reports no GPU"), "an empty HIP device list asks for a driver update");
|
||||
|
||||
// Loader: pick the shim for the game's adapter, never a different GPU's.
|
||||
const auto has_all = [](const std::string& arch) { return diag::supported_gpu_target(arch); }; // A full release: one shim per target.
|
||||
for (size_t i = 0; i < archs.size(); ++i) {
|
||||
const auto luid = luid_of(100 + unsigned(i));
|
||||
const auto choice = diag::choose_loader_architecture(everything, &luid, has_all, "all");
|
||||
expect(choice.architecture == archs[i] && choice.error.empty(), "loader picks _nvngx_" + archs[i] + ".dll for that adapter");
|
||||
}
|
||||
auto choice = diag::choose_loader_architecture(machine, &igpu, has_all, "all");
|
||||
expect(choice.architecture == "gfx1103", "loader follows the iGPU when the game renders on it");
|
||||
choice = diag::choose_loader_architecture(machine, &second, has_all, "all");
|
||||
expect(choice.architecture == "gfx1101", "loader follows the game's dGPU among several GPUs");
|
||||
choice = diag::choose_loader_architecture(machine, nullptr, has_all, "all");
|
||||
expect(choice.architecture.empty() && contains(choice.error, "several GPUs with different architectures"), "before an adapter is known, several architectures wait for Init");
|
||||
choice = diag::choose_loader_architecture(machine, nullptr, [](const std::string& a) { return a == "gfx1151"; }, "gfx1151");
|
||||
expect(choice.architecture == "gfx1151", "before an adapter is known, the only architecture with a build is used");
|
||||
choice = diag::choose_loader_architecture(machine, &strix, [](const std::string& a) { return a == "gfx1101"; }, "gfx1101");
|
||||
expect(choice.architecture.empty() && contains(choice.error, "no build for your GPU (gfx1151)") && contains(choice.error, "supports: gfx1101"), "loader: " + choice.error);
|
||||
choice = diag::choose_loader_architecture(rdna2, &six, has_all, "all");
|
||||
expect(choice.architecture.empty() && contains(choice.error, "gfx1030") && contains(choice.error, "not supported by d4r"), "loader: " + choice.error);
|
||||
choice = diag::choose_loader_architecture(rdna2, nullptr, has_all, "all");
|
||||
expect(choice.architecture.empty() && contains(choice.error, "no supported AMD GPU") && contains(choice.error, "gfx1030"), "loader: " + choice.error);
|
||||
expect(diag::nvapi_target_error("gfx1151", "gfx1151", "p").empty() && contains(diag::nvapi_target_error("gfx1101", "gfx1151", "p"), "built for gfx1101 but the selected GPU is gfx1151"),
|
||||
"an NVAPI built for another target is refused");
|
||||
|
||||
// Real nvapi64.dll builds, one per target: the file in nvapi\<arch> names <arch> and only <arch>.
|
||||
if (builtNvapi.empty()) { std::printf("skip real NVAPI builds (no directory given)\n"); return; }
|
||||
for (const auto& arch : archs) {
|
||||
const auto layout = root / L"built";
|
||||
const auto source = builtNvapi / diag::wide(arch) / L"nvapi64.dll";
|
||||
std::filesystem::create_directories(layout / L"nvapi" / diag::wide(arch));
|
||||
std::filesystem::copy_file(source, layout / L"nvapi" / diag::wide(arch) / L"nvapi64.dll", std::filesystem::copy_options::overwrite_existing);
|
||||
}
|
||||
for (const auto& arch : archs) {
|
||||
const auto choice = player::select_compat_nvapi(root / L"built", arch);
|
||||
expect(choice.error.empty(), "built layout has nvapi\\" + arch);
|
||||
if (!choice.error.empty()) continue;
|
||||
const HMODULE module = LoadLibraryExW(choice.path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
|
||||
auto target = module ? reinterpret_cast<const char*(__cdecl*)()>(reinterpret_cast<void*>(GetProcAddress(module, "d4r_NvapiTargetArchitecture"))) : nullptr;
|
||||
const std::string built = target ? target() : "";
|
||||
expect(built == arch, "nvapi\\" + arch + "\\nvapi64.dll names " + arch + " (reports '" + built + "')");
|
||||
const auto bytes = player::read_text(choice.path);
|
||||
std::string others;
|
||||
for (const auto& other : archs) if (other != arch && contains(bytes, "D4R_NVAPI_TARGET=" + other)) others += other + " ";
|
||||
expect(contains(bytes, "D4R_NVAPI_TARGET=" + arch + '\0') && others.empty(), "nvapi\\" + arch + " carries only its own marker for the packager (" + others + ")");
|
||||
expect(diag::nvapi_target_error(built, arch, "p").empty(), "the shim accepts nvapi\\" + arch + " for " + arch);
|
||||
if (module) FreeLibrary(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int wmain(int argc, wchar_t** argv) {
|
||||
if (argc != 2) { std::printf("usage: player_config_probe FIXTURE_DIR\n"); return 2; }
|
||||
if (argc != 2 && argc != 3) { std::printf("usage: player_config_probe FIXTURE_DIR [BUILT_NVAPI_DIR]\n"); return 2; }
|
||||
const std::filesystem::path dir(argv[1]);
|
||||
std::vector<std::string> notes;
|
||||
|
||||
@@ -122,9 +306,13 @@ int wmain(int argc, wchar_t** argv) {
|
||||
std::filesystem::create_directories(dir / L"zluda");
|
||||
const auto colocatedNvapi = dir / L"zluda" / L"nvapi64.dll";
|
||||
{ std::ofstream(colocatedNvapi, std::ios::binary) << "colocated compatibility NVAPI fixture"; }
|
||||
expect(player::compat_nvapi_path(dir) == colocatedNvapi, "player mode reuses NVAPI colocated with nvcuda");
|
||||
expect(player::compat_nvapi_path(dir) == archNvapi, "a shared NVAPI beside nvcuda never outranks the architecture-specific one");
|
||||
std::filesystem::remove(colocatedNvapi);
|
||||
std::filesystem::remove(archNvapi);
|
||||
std::filesystem::remove(dir / L"nvapi" / L"nvapi64.dll");
|
||||
architecture_selection(dir / L"architecture-fixture", argc > 2 ? std::filesystem::path(argv[2]) : std::filesystem::path());
|
||||
std::filesystem::create_directories(dir / L"nvapi");
|
||||
{ std::ofstream(dir / L"nvapi" / L"nvapi64.dll", std::ios::binary) << "generic nvapi fixture"; }
|
||||
expect(player::raw_nvapi_backend_path(dir) == dir / L"nvapi" / L"backend" / L"nvapi64.dll",
|
||||
"raw ZLUDA NVAPI backend has a separate explicit path");
|
||||
const auto archNative = dir / L"native" / d4r::diag::wide(d4r::diag::HipApi::target_arch());
|
||||
|
||||
Reference in new issue
Block a user