diff --git a/README.md b/README.md index 87a432ca..fc285cb8 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,7 @@ make -j$(nproc) - DX12 device validation probe (default on): - `WOWEE_FSR3_WRAPPER_DX12_VALIDATE_DEVICE=1` - Set to `0` to skip adapter/device preflight. + - Bridge preflight also checks Vulkan Win32 interop funcs/extensions before enabling DX12 path. - Path B wrapper libraries must export the clean wrapper ABI (`include/rendering/amd_fsr3_wrapper_abi.h`): - `wowee_fsr3_wrapper_get_abi_version` - `wowee_fsr3_wrapper_initialize` diff --git a/docs/AMD_FSR2_INTEGRATION.md b/docs/AMD_FSR2_INTEGRATION.md index dcc843f8..a2446c6c 100644 --- a/docs/AMD_FSR2_INTEGRATION.md +++ b/docs/AMD_FSR2_INTEGRATION.md @@ -60,6 +60,9 @@ Runtime note: - DX12 bridge device preflight toggle: - `WOWEE_FSR3_WRAPPER_DX12_VALIDATE_DEVICE=1` (default) - `WOWEE_FSR3_WRAPPER_DX12_VALIDATE_DEVICE=0` to skip DXGI/D3D12 device creation probe +- DX12 bridge preflight also validates Vulkan Win32 interop support: + - required device functions: `vkGetMemoryWin32HandleKHR`, `vkImportSemaphoreWin32HandleKHR`, `vkGetSemaphoreWin32HandleKHR` + - required device extensions: `VK_KHR_external_memory`, `VK_KHR_external_memory_win32`, `VK_KHR_external_semaphore`, `VK_KHR_external_semaphore_win32` - Path B wrapper ABI contract is declared in: - `include/rendering/amd_fsr3_wrapper_abi.h` - Required wrapper exports: diff --git a/src/rendering/amd_fsr3_wrapper_impl.cpp b/src/rendering/amd_fsr3_wrapper_impl.cpp index ebd1db50..76f12cad 100644 --- a/src/rendering/amd_fsr3_wrapper_impl.cpp +++ b/src/rendering/amd_fsr3_wrapper_impl.cpp @@ -40,6 +40,24 @@ void writeError(char* outErrorText, uint32_t outErrorTextCapacity, const char* m } #if WOWEE_HAS_AMD_FSR3_FRAMEGEN +#if defined(_WIN32) +bool envEnabled(const char* key, bool defaultValue) { + const char* val = std::getenv(key); + if (!val || !*val) return defaultValue; + std::string s(val); + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return !(s == "0" || s == "false" || s == "off" || s == "no"); +} + +bool hasExtension(const std::vector& extensions, const char* name) { + for (const VkExtensionProperties& ext : extensions) { + if (std::strcmp(ext.extensionName, name) == 0) return true; + } + return false; +} +#endif + enum class WrapperBackend { VulkanRuntime, Dx12Bridge @@ -261,7 +279,7 @@ bool runDx12BridgePreflight(const WoweeFsr3WrapperInitDesc* initDesc, std::strin } } - if (!initDesc || !initDesc->device || !initDesc->getDeviceProcAddr) { + if (!initDesc || !initDesc->device || !initDesc->getDeviceProcAddr || !initDesc->physicalDevice) { missing.emplace_back("valid Vulkan device/getDeviceProcAddr"); } else { const char* requiredVkInteropFns[] = { @@ -275,6 +293,31 @@ bool runDx12BridgePreflight(const WoweeFsr3WrapperInitDesc* initDesc, std::strin missing.emplace_back(fn); } } + + uint32_t extCount = 0; + VkResult extErr = vkEnumerateDeviceExtensionProperties(initDesc->physicalDevice, nullptr, &extCount, nullptr); + if (extErr != VK_SUCCESS || extCount == 0) { + missing.emplace_back("vkEnumerateDeviceExtensionProperties"); + } else { + std::vector extensions(extCount); + extErr = vkEnumerateDeviceExtensionProperties( + initDesc->physicalDevice, nullptr, &extCount, extensions.data()); + if (extErr != VK_SUCCESS) { + missing.emplace_back("vkEnumerateDeviceExtensionProperties(data)"); + } else { + const char* requiredVkExtensions[] = { + VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, + VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME, + VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, + VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME + }; + for (const char* extName : requiredVkExtensions) { + if (!hasExtension(extensions, extName)) { + missing.emplace_back(extName); + } + } + } + } } std::vector runtimeCandidates; @@ -652,13 +695,3 @@ WOWEE_FSR3_WRAPPER_EXPORT void wowee_fsr3_wrapper_shutdown(WoweeFsr3WrapperConte (void)context; #endif } -#if defined(_WIN32) -bool envEnabled(const char* key, bool defaultValue) { - const char* val = std::getenv(key); - if (!val || !*val) return defaultValue; - std::string s(val); - std::transform(s.begin(), s.end(), s.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - return !(s == "0" || s == "false" || s == "off" || s == "no"); -} -#endif