diff --git a/README.md b/README.md index 74fb26f8..36e0838a 100644 --- a/README.md +++ b/README.md @@ -210,20 +210,23 @@ make -j$(nproc) - Wrapper backend override (what the wrapper loads underneath): `WOWEE_FSR3_WRAPPER_BACKEND_LIB=/absolute/path/to/libffx_fsr3_vk.so`. - Wrapper backend mode: - `WOWEE_FSR3_WRAPPER_BACKEND=vulkan_runtime` (default on all platforms) - - `WOWEE_FSR3_WRAPPER_BACKEND=dx12_bridge` (opt-in; runs DX12 preflight and then selects a dispatch-capable runtime export set) + - `WOWEE_FSR3_WRAPPER_BACKEND=dx12_bridge` (opt-in) - DX12 runtime override (for `dx12_bridge`): - `WOWEE_FSR3_DX12_RUNTIME_LIB=C:\\path\\to\\amd_fidelityfx_framegeneration_dx12.dll` - 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. + - Windows `dx12_bridge` preflight checks Vulkan Win32 interop funcs/extensions before enabling DX12 path. + - Linux `dx12_bridge` is enabled for wrapper runtime compatibility mode and uses Vulkan dispatch symbols in this build. - Path B wrapper libraries must export the clean wrapper ABI (`include/rendering/amd_fsr3_wrapper_abi.h`): - ABI version is currently `3` (dispatch includes external-memory/semaphore handles plus acquire/release fence values for bridge sync). - `wowee_fsr3_wrapper_get_abi_version` + - `wowee_fsr3_wrapper_get_backend` - `wowee_fsr3_wrapper_initialize` - `wowee_fsr3_wrapper_dispatch_upscale` - `wowee_fsr3_wrapper_shutdown` - Optional FG hook: `wowee_fsr3_wrapper_dispatch_framegen` + - Optional diagnostics: `wowee_fsr3_wrapper_get_last_error`, `wowee_fsr3_wrapper_get_capabilities` ### Current FSR Defaults diff --git a/docs/AMD_FSR2_INTEGRATION.md b/docs/AMD_FSR2_INTEGRATION.md index 12ac856c..3e76265e 100644 --- a/docs/AMD_FSR2_INTEGRATION.md +++ b/docs/AMD_FSR2_INTEGRATION.md @@ -56,7 +56,9 @@ Runtime note: - `WOWEE_FSR3_WRAPPER_BACKEND=vulkan_runtime` - `WOWEE_FSR3_WRAPPER_BACKEND=dx12_bridge` - Default is `vulkan_runtime` on all platforms. -- `dx12_bridge` is opt-in and now performs DX12/Vulkan preflight, then loads the first runtime library exposing the required FSR3 dispatch exports. +- `dx12_bridge` is opt-in. +- On Windows: `dx12_bridge` performs DX12/Vulkan preflight, then loads the first runtime library exposing the required FSR3 dispatch exports. +- On Linux: `dx12_bridge` is enabled for wrapper runtime compatibility mode and uses Vulkan dispatch symbols in this build. - DX12 bridge runtime override: - `WOWEE_FSR3_DX12_RUNTIME_LIB=` - DX12 bridge device preflight toggle: @@ -70,12 +72,14 @@ Runtime note: - Current wrapper ABI version: `3` (dispatch payload carries external memory/semaphore handles and acquire/release fence values for bridge synchronization). - Required wrapper exports: - `wowee_fsr3_wrapper_get_abi_version` + - `wowee_fsr3_wrapper_get_backend` - `wowee_fsr3_wrapper_initialize` - `wowee_fsr3_wrapper_dispatch_upscale` - `wowee_fsr3_wrapper_shutdown` - Optional wrapper export: - `wowee_fsr3_wrapper_dispatch_framegen` - `wowee_fsr3_wrapper_get_last_error` + - `wowee_fsr3_wrapper_get_capabilities` ## Current Status diff --git a/src/rendering/amd_fsr3_wrapper_impl.cpp b/src/rendering/amd_fsr3_wrapper_impl.cpp index 5af7e817..a5302c39 100644 --- a/src/rendering/amd_fsr3_wrapper_impl.cpp +++ b/src/rendering/amd_fsr3_wrapper_impl.cpp @@ -608,6 +608,10 @@ WOWEE_FSR3_WRAPPER_EXPORT uint32_t wowee_fsr3_wrapper_get_capabilities(WoweeFsr3 if (ctx->backend == WrapperBackend::Dx12Bridge) { caps |= WOWEE_FSR3_WRAPPER_CAP_EXTERNAL_INTEROP; } +#elif defined(__linux__) + if (ctx->backend == WrapperBackend::Dx12Bridge) { + caps |= WOWEE_FSR3_WRAPPER_CAP_EXTERNAL_INTEROP; + } #endif return caps; #else @@ -647,16 +651,16 @@ WOWEE_FSR3_WRAPPER_EXPORT int32_t wowee_fsr3_wrapper_initialize(const WoweeFsr3W const bool backendExplicit = (backendEnvRaw && *backendEnvRaw); const WrapperBackend selectedBackend = selectBackend(); if (selectedBackend == WrapperBackend::Dx12Bridge) { -#if !defined(_WIN32) - writeError(outErrorText, outErrorTextCapacity, - "dx12_bridge backend is Windows-only in current wrapper build"); - return -1; -#else +#if defined(_WIN32) std::string preflightError; if (!runDx12BridgePreflight(initDesc, preflightError)) { writeError(outErrorText, outErrorTextCapacity, preflightError.c_str()); return -1; } +#elif defined(__linux__) + // Linux bridge mode currently routes dispatch through Vulkan runtime symbols + // while preserving external interop metadata for wrapper-side diagnostics. + (void)initDesc; #endif } @@ -975,7 +979,9 @@ WOWEE_FSR3_WRAPPER_EXPORT int32_t wowee_fsr3_wrapper_dispatch_upscale(WoweeFsr3W static wchar_t kMotionName[] = L"FSR3_MotionVectors"; static wchar_t kOutputName[] = L"FSR3_Output"; FfxFsr3DispatchUpscaleDescription dispatch{}; + bool useDx12Interop = false; #if defined(_WIN32) + useDx12Interop = (ctx->backend == WrapperBackend::Dx12Bridge); ID3D12Resource* colorRes = nullptr; ID3D12Resource* depthRes = nullptr; ID3D12Resource* motionRes = nullptr; @@ -991,7 +997,7 @@ WOWEE_FSR3_WRAPPER_EXPORT int32_t wowee_fsr3_wrapper_dispatch_upscale(WoweeFsr3W safeRelease(releaseFence); }; #endif - if (ctx->backend == WrapperBackend::Dx12Bridge) { + if (useDx12Interop) { #if defined(_WIN32) if (ctx->dx12Device->OpenSharedHandle(reinterpret_cast(dispatchDesc->colorMemoryHandle), IID_PPV_ARGS(&colorRes)) != S_OK || ctx->dx12Device->OpenSharedHandle(reinterpret_cast(dispatchDesc->depthMemoryHandle), IID_PPV_ARGS(&depthRes)) != S_OK || @@ -1015,9 +1021,6 @@ WOWEE_FSR3_WRAPPER_EXPORT int32_t wowee_fsr3_wrapper_dispatch_upscale(WoweeFsr3W dispatch.depth = ctx->fns.getResourceDX12(depthRes, depthDesc, kDepthName, FFX_RESOURCE_STATE_COMPUTE_READ); dispatch.motionVectors = ctx->fns.getResourceDX12(motionRes, mvDesc, kMotionName, FFX_RESOURCE_STATE_COMPUTE_READ); dispatch.upscaleOutput = ctx->fns.getResourceDX12(outputRes, outDesc, kOutputName, FFX_RESOURCE_STATE_UNORDERED_ACCESS); -#else - setContextError(ctx, "dx12_bridge is unavailable on this platform"); - return -1; #endif } else { dispatch.commandList = ctx->fns.getCommandListVK(dispatchDesc->commandBuffer); @@ -1047,7 +1050,7 @@ WOWEE_FSR3_WRAPPER_EXPORT int32_t wowee_fsr3_wrapper_dispatch_upscale(WoweeFsr3W const bool ok = (ctx->fns.fsr3ContextDispatchUpscale(reinterpret_cast(ctx->fsr3ContextStorage), &dispatch) == FFX_OK); #if defined(_WIN32) - if (ctx->backend == WrapperBackend::Dx12Bridge) { + if (useDx12Interop) { if (ctx->dx12CommandList->Close() != S_OK) { cleanupDx12Imports(); setContextError(ctx, "dx12_bridge failed to close command list after upscale dispatch"); @@ -1150,7 +1153,9 @@ WOWEE_FSR3_WRAPPER_EXPORT int32_t wowee_fsr3_wrapper_dispatch_framegen(WoweeFsr3 static wchar_t kPresentName[] = L"FSR3_PresentColor"; static wchar_t kInterpolatedName[] = L"FSR3_InterpolatedOutput"; FfxFrameGenerationDispatchDescription fgDispatch{}; + bool useDx12Interop = false; #if defined(_WIN32) + useDx12Interop = (ctx->backend == WrapperBackend::Dx12Bridge); ID3D12Resource* presentRes = nullptr; ID3D12Resource* framegenRes = nullptr; ID3D12Fence* acquireFence = nullptr; @@ -1162,7 +1167,7 @@ WOWEE_FSR3_WRAPPER_EXPORT int32_t wowee_fsr3_wrapper_dispatch_framegen(WoweeFsr3 safeRelease(releaseFence); }; #endif - if (ctx->backend == WrapperBackend::Dx12Bridge) { + if (useDx12Interop) { #if defined(_WIN32) if (ctx->dx12Device->OpenSharedHandle(reinterpret_cast(dispatchDesc->outputMemoryHandle), IID_PPV_ARGS(&presentRes)) != S_OK || ctx->dx12Device->OpenSharedHandle(reinterpret_cast(dispatchDesc->frameGenOutputMemoryHandle), IID_PPV_ARGS(&framegenRes)) != S_OK || @@ -1182,9 +1187,6 @@ WOWEE_FSR3_WRAPPER_EXPORT int32_t wowee_fsr3_wrapper_dispatch_framegen(WoweeFsr3 fgDispatch.commandList = ctx->fns.getCommandListDX12(ctx->dx12CommandList); fgDispatch.presentColor = ctx->fns.getResourceDX12(presentRes, presentDesc, kPresentName, FFX_RESOURCE_STATE_COMPUTE_READ); fgDispatch.outputs[0] = ctx->fns.getResourceDX12(framegenRes, fgOutDesc, kInterpolatedName, FFX_RESOURCE_STATE_UNORDERED_ACCESS); -#else - setContextError(ctx, "dx12_bridge is unavailable on this platform"); - return -1; #endif } else { fgDispatch.commandList = ctx->fns.getCommandListVK(dispatchDesc->commandBuffer); @@ -1201,7 +1203,7 @@ WOWEE_FSR3_WRAPPER_EXPORT int32_t wowee_fsr3_wrapper_dispatch_framegen(WoweeFsr3 const bool ok = (ctx->fns.fsr3DispatchFrameGeneration(&fgDispatch) == FFX_OK); #if defined(_WIN32) - if (ctx->backend == WrapperBackend::Dx12Bridge) { + if (useDx12Interop) { if (ctx->dx12CommandList->Close() != S_OK) { cleanupDx12Imports(); setContextError(ctx, "dx12_bridge failed to close command list after frame generation dispatch"); diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 9590f70d..f5ce3a28 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -75,6 +75,8 @@ #include #if defined(_WIN32) #include +#elif defined(__linux__) +#include #endif namespace wowee { @@ -169,6 +171,61 @@ static uint64_t exportSemaphoreHandleWin32(VkDevice device, PFN_vkGetDeviceProcA } #endif +#if defined(__linux__) +static uint64_t exportImageMemoryHandleFd(VkDevice device, PFN_vkGetDeviceProcAddr getDeviceProcAddr, + VmaAllocator allocator, const AllocatedImage& image) { +#if !defined(VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR) || !defined(VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT) + (void)device; + (void)getDeviceProcAddr; + (void)allocator; + (void)image; + return 0; +#else + if (!device || !getDeviceProcAddr || !allocator || !image.allocation) return 0; + auto getMemFd = reinterpret_cast( + getDeviceProcAddr(device, "vkGetMemoryFdKHR")); + if (!getMemFd) return 0; + + VmaAllocationInfo allocInfo{}; + vmaGetAllocationInfo(allocator, image.allocation, &allocInfo); + if (allocInfo.deviceMemory == VK_NULL_HANDLE) return 0; + + VkMemoryGetFdInfoKHR fdInfo{}; + fdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR; + fdInfo.memory = allocInfo.deviceMemory; + fdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; + + int outFd = -1; + if (getMemFd(device, &fdInfo, &outFd) != VK_SUCCESS || outFd < 0) return 0; + return static_cast(static_cast(outFd)); +#endif +} + +static uint64_t exportSemaphoreHandleFd(VkDevice device, PFN_vkGetDeviceProcAddr getDeviceProcAddr, + VkSemaphore semaphore) { +#if !defined(VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR) || !defined(VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT) + (void)device; + (void)getDeviceProcAddr; + (void)semaphore; + return 0; +#else + if (!device || !getDeviceProcAddr || !semaphore) return 0; + auto getSemFd = reinterpret_cast( + getDeviceProcAddr(device, "vkGetSemaphoreFdKHR")); + if (!getSemFd) return 0; + + VkSemaphoreGetFdInfoKHR fdInfo{}; + fdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR; + fdInfo.semaphore = semaphore; + fdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT; + + int outFd = -1; + if (getSemFd(device, &fdInfo, &outFd) != VK_SUCCESS || outFd < 0) return 0; + return static_cast(static_cast(outFd)); +#endif +} +#endif + static std::vector parseEmoteCommands(const std::string& raw) { std::vector out; std::string cur; @@ -4494,6 +4551,8 @@ void Renderer::dispatchAmdFsr2() { void Renderer::dispatchAmdFsr3Framegen() { #if WOWEE_HAS_AMD_FSR3_FRAMEGEN + VkDevice device = vkCtx->getDevice(); + VmaAllocator alloc = vkCtx->getAllocator(); if (!fsr2_.amdFsr3FramegenEnabled) { fsr2_.amdFsr3FramegenRuntimeActive = false; return; @@ -4554,61 +4613,110 @@ void Renderer::dispatchAmdFsr3Framegen() { fgDispatch.cameraFovYRadians = camera ? glm::radians(camera->getFovDegrees()) : 1.0f; fgDispatch.reset = fsr2_.needsHistoryReset; +#if defined(_WIN32) || defined(__linux__) #if defined(_WIN32) - std::vector exportedHandles; + using ExportHandle = HANDLE; +#else + using ExportHandle = int; +#endif + std::vector exportedHandles; auto trackHandle = [&](uint64_t h) { if (!h) return; - HANDLE raw = reinterpret_cast(h); + ExportHandle raw = +#if defined(_WIN32) + reinterpret_cast(h); +#else + static_cast(static_cast(h)); +#endif exportedHandles.push_back(raw); }; auto cleanupExportedHandles = [&]() { - for (HANDLE h : exportedHandles) { + for (ExportHandle h : exportedHandles) { +#if defined(_WIN32) if (h) CloseHandle(h); +#else + if (h >= 0) close(h); +#endif } exportedHandles.clear(); }; fgDispatch.externalFlags = 0; +#if defined(_WIN32) fgDispatch.colorMemoryHandle = exportImageMemoryHandleWin32( device, vkGetDeviceProcAddr, alloc, fsr2_.sceneColor); +#else + fgDispatch.colorMemoryHandle = exportImageMemoryHandleFd( + device, vkGetDeviceProcAddr, alloc, fsr2_.sceneColor); +#endif if (fgDispatch.colorMemoryHandle) { fgDispatch.externalFlags |= WOWEE_FSR3_WRAPPER_EXTERNAL_COLOR_MEMORY; trackHandle(fgDispatch.colorMemoryHandle); } +#if defined(_WIN32) fgDispatch.depthMemoryHandle = exportImageMemoryHandleWin32( device, vkGetDeviceProcAddr, alloc, fsr2_.sceneDepth); +#else + fgDispatch.depthMemoryHandle = exportImageMemoryHandleFd( + device, vkGetDeviceProcAddr, alloc, fsr2_.sceneDepth); +#endif if (fgDispatch.depthMemoryHandle) { fgDispatch.externalFlags |= WOWEE_FSR3_WRAPPER_EXTERNAL_DEPTH_MEMORY; trackHandle(fgDispatch.depthMemoryHandle); } +#if defined(_WIN32) fgDispatch.motionVectorMemoryHandle = exportImageMemoryHandleWin32( device, vkGetDeviceProcAddr, alloc, fsr2_.motionVectors); +#else + fgDispatch.motionVectorMemoryHandle = exportImageMemoryHandleFd( + device, vkGetDeviceProcAddr, alloc, fsr2_.motionVectors); +#endif if (fgDispatch.motionVectorMemoryHandle) { fgDispatch.externalFlags |= WOWEE_FSR3_WRAPPER_EXTERNAL_MOTION_MEMORY; trackHandle(fgDispatch.motionVectorMemoryHandle); } +#if defined(_WIN32) fgDispatch.outputMemoryHandle = exportImageMemoryHandleWin32( device, vkGetDeviceProcAddr, alloc, fsr2_.history[fsr2_.currentHistory]); +#else + fgDispatch.outputMemoryHandle = exportImageMemoryHandleFd( + device, vkGetDeviceProcAddr, alloc, fsr2_.history[fsr2_.currentHistory]); +#endif if (fgDispatch.outputMemoryHandle) { fgDispatch.externalFlags |= WOWEE_FSR3_WRAPPER_EXTERNAL_OUTPUT_MEMORY; trackHandle(fgDispatch.outputMemoryHandle); } +#if defined(_WIN32) fgDispatch.frameGenOutputMemoryHandle = exportImageMemoryHandleWin32( device, vkGetDeviceProcAddr, alloc, fsr2_.framegenOutput); +#else + fgDispatch.frameGenOutputMemoryHandle = exportImageMemoryHandleFd( + device, vkGetDeviceProcAddr, alloc, fsr2_.framegenOutput); +#endif if (fgDispatch.frameGenOutputMemoryHandle) { fgDispatch.externalFlags |= WOWEE_FSR3_WRAPPER_EXTERNAL_FRAMEGEN_OUTPUT_MEMORY; trackHandle(fgDispatch.frameGenOutputMemoryHandle); } const FrameData& frameData = vkCtx->getCurrentFrameData(); +#if defined(_WIN32) fgDispatch.acquireSemaphoreHandle = exportSemaphoreHandleWin32( device, vkGetDeviceProcAddr, frameData.imageAvailableSemaphore); +#else + fgDispatch.acquireSemaphoreHandle = exportSemaphoreHandleFd( + device, vkGetDeviceProcAddr, frameData.imageAvailableSemaphore); +#endif if (fgDispatch.acquireSemaphoreHandle) { fgDispatch.externalFlags |= WOWEE_FSR3_WRAPPER_EXTERNAL_ACQUIRE_SEMAPHORE; trackHandle(fgDispatch.acquireSemaphoreHandle); } +#if defined(_WIN32) fgDispatch.releaseSemaphoreHandle = exportSemaphoreHandleWin32( device, vkGetDeviceProcAddr, frameData.renderFinishedSemaphore); +#else + fgDispatch.releaseSemaphoreHandle = exportSemaphoreHandleFd( + device, vkGetDeviceProcAddr, frameData.renderFinishedSemaphore); +#endif if (fgDispatch.releaseSemaphoreHandle) { fgDispatch.externalFlags |= WOWEE_FSR3_WRAPPER_EXTERNAL_RELEASE_SEMAPHORE; trackHandle(fgDispatch.releaseSemaphoreHandle); @@ -4629,7 +4737,7 @@ void Renderer::dispatchAmdFsr3Framegen() { fsr2_.amdFsr3RuntimeLastError = fsr2_.amdFsr3Runtime->lastError(); fsr2_.amdFsr3FallbackCount++; fsr2_.amdFsr3FramegenRuntimeActive = false; -#if defined(_WIN32) +#if defined(_WIN32) || defined(__linux__) cleanupExportedHandles(); #endif return; @@ -4639,14 +4747,14 @@ void Renderer::dispatchAmdFsr3Framegen() { if (!fsr2_.amdFsr3FramegenEnabled) { fsr2_.amdFsr3FramegenRuntimeActive = false; -#if defined(_WIN32) +#if defined(_WIN32) || defined(__linux__) cleanupExportedHandles(); #endif return; } if (!fsr2_.amdFsr3Runtime->isFrameGenerationReady()) { fsr2_.amdFsr3FramegenRuntimeActive = false; -#if defined(_WIN32) +#if defined(_WIN32) || defined(__linux__) cleanupExportedHandles(); #endif return; @@ -4660,7 +4768,7 @@ void Renderer::dispatchAmdFsr3Framegen() { fsr2_.amdFsr3RuntimeLastError = fsr2_.amdFsr3Runtime->lastError(); fsr2_.amdFsr3FallbackCount++; fsr2_.amdFsr3FramegenRuntimeActive = false; -#if defined(_WIN32) +#if defined(_WIN32) || defined(__linux__) cleanupExportedHandles(); #endif return; @@ -4669,7 +4777,7 @@ void Renderer::dispatchAmdFsr3Framegen() { fsr2_.amdFsr3FramegenDispatchCount++; fsr2_.framegenOutputValid = true; fsr2_.amdFsr3FramegenRuntimeActive = true; -#if defined(_WIN32) +#if defined(_WIN32) || defined(__linux__) cleanupExportedHandles(); #endif #else