From 8b42cc886f0efd901373dbd2936e08e2c0fe0042 Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Mon, 27 Jul 2026 12:39:07 +0800 Subject: [PATCH 01/19] prototype D3D12 FFX API upscaler provider --- .../core/SuperResolutionNative.java | 3 +- .../srapi/SRCreateUpscaleContextDesc.java | 25 ++ .../srapi/SRD3D12DeviceInfo.java | 46 +++ .../srapi/SRDispatchCommandBufferInfo.java | 45 ++- .../srapi/SRRenderApiType.java | 3 +- .../srapi/SRTextureResource.java | 34 +- .../srapi/SuperResolutionNativeAPI.java | 3 +- .../include/sr/fsr/ffx_api_minimal.h | 267 +++++++++++++ .../include/sr/fsr/ffx_api_upscale.h | 20 + .../cpp/SRNativeFSR/src/ffx_api_upscale.cpp | 356 ++++++++++++++++++ native/cpp/SRNativeFSR/src/sr_provider.cpp | 23 +- ...perresolution_core_SuperResolutionNative.h | 4 +- .../SRNativeMain/include/sr/sr_api_enums.h | 3 +- .../SRNativeMain/include/sr/sr_api_structs.h | 21 ++ .../cpp/SRNativeMain/include/sr/sr_modules.h | 3 +- native/cpp/SRNativeMain/src/sr_api_jni.cpp | 30 +- native/cpp/docs/add_upscale_provider.md | 4 +- native/cpp/docs/ffx_api_d3d12_prototype.md | 64 ++++ 18 files changed, 939 insertions(+), 15 deletions(-) create mode 100644 common/src/main/java/io/homo/superresolution/srapi/SRD3D12DeviceInfo.java create mode 100644 native/cpp/SRNativeFSR/include/sr/fsr/ffx_api_minimal.h create mode 100644 native/cpp/SRNativeFSR/include/sr/fsr/ffx_api_upscale.h create mode 100644 native/cpp/SRNativeFSR/src/ffx_api_upscale.cpp create mode 100644 native/cpp/docs/ffx_api_d3d12_prototype.md diff --git a/common/src/main/java/io/homo/superresolution/core/SuperResolutionNative.java b/common/src/main/java/io/homo/superresolution/core/SuperResolutionNative.java index fea0c8e9..7567375f 100644 --- a/common/src/main/java/io/homo/superresolution/core/SuperResolutionNative.java +++ b/common/src/main/java/io/homo/superresolution/core/SuperResolutionNative.java @@ -56,6 +56,7 @@ public static native int NsrCreateUpscaleContext( int renderApiType, SROpenGLDeviceInfo openglDeviceInfo, SRVulkanDeviceInfo vulkanDeviceInfo, + SRD3D12DeviceInfo d3d12DeviceInfo, int upscaledSizeX, int upscaledSizeY, int renderSizeX, @@ -74,7 +75,7 @@ public static native int NsrInitUpscaleContext( public static native int NsrDispatchUpscale( long context, int renderApiType, - long vulkanCommandBuffer, + long nativeCommandBuffer, SRTextureResource color, SRTextureResource depth, SRTextureResource motionVectors, diff --git a/common/src/main/java/io/homo/superresolution/srapi/SRCreateUpscaleContextDesc.java b/common/src/main/java/io/homo/superresolution/srapi/SRCreateUpscaleContextDesc.java index a664c1fb..c4d166f0 100644 --- a/common/src/main/java/io/homo/superresolution/srapi/SRCreateUpscaleContextDesc.java +++ b/common/src/main/java/io/homo/superresolution/srapi/SRCreateUpscaleContextDesc.java @@ -59,6 +59,10 @@ public class SRCreateUpscaleContextDesc implements AutoCloseable { * Vulkan设备信息(当renderApiType为VULKAN时使用) */ private SRVulkanDeviceInfo vulkanDeviceInfo; + /** + * Direct3D 12设备信息(当renderApiType为D3D12时使用) + */ + private SRD3D12DeviceInfo d3d12DeviceInfo; private SRCreateUpscaleContextDesc() { this.messageCallback = 0; @@ -98,6 +102,23 @@ public static SRCreateUpscaleContextDesc createVulkan( return desc; } + /** + * 创建Direct3D 12上下文描述符 + */ + public static SRCreateUpscaleContextDesc createD3D12( + SRD3D12DeviceInfo deviceInfo, + Vector2i upscaledSize, + Vector2i renderSize, + EnumSet flags) { + SRCreateUpscaleContextDesc desc = new SRCreateUpscaleContextDesc(); + desc.renderApiType = SRRenderApiType.D3D12; + desc.d3d12DeviceInfo = deviceInfo; + desc.upscaledSize = upscaledSize; + desc.renderSize = renderSize; + desc.flags = flags; + return desc; + } + public SRRenderApiType getRenderApiType() { return renderApiType; } @@ -110,6 +131,10 @@ public SRVulkanDeviceInfo getVulkanDeviceInfo() { return vulkanDeviceInfo; } + public SRD3D12DeviceInfo getD3D12DeviceInfo() { + return d3d12DeviceInfo; + } + public Vector2i getUpscaledSize() { return upscaledSize; } diff --git a/common/src/main/java/io/homo/superresolution/srapi/SRD3D12DeviceInfo.java b/common/src/main/java/io/homo/superresolution/srapi/SRD3D12DeviceInfo.java new file mode 100644 index 00000000..56eb1ca4 --- /dev/null +++ b/common/src/main/java/io/homo/superresolution/srapi/SRD3D12DeviceInfo.java @@ -0,0 +1,46 @@ +/* + * Super Resolution + * Copyright (c) 2026. 187J3X1-114514 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.homo.superresolution.srapi; + +/** + * Opaque Direct3D 12 device information passed through SRAPI. + * + *

{@code device} is the native address of an {@code ID3D12Device}. + * Keeping the value opaque lets the common Java API expose D3D12 without + * adding a platform-specific Java binding dependency.

+ */ +public class SRD3D12DeviceInfo { + public long device; + + public SRD3D12DeviceInfo() { + this(0); + } + + public SRD3D12DeviceInfo(long device) { + this.device = device; + } + + public long getDevice() { + return device; + } + + public void setDevice(long device) { + this.device = device; + } +} diff --git a/common/src/main/java/io/homo/superresolution/srapi/SRDispatchCommandBufferInfo.java b/common/src/main/java/io/homo/superresolution/srapi/SRDispatchCommandBufferInfo.java index f7875e36..86e51c57 100644 --- a/common/src/main/java/io/homo/superresolution/srapi/SRDispatchCommandBufferInfo.java +++ b/common/src/main/java/io/homo/superresolution/srapi/SRDispatchCommandBufferInfo.java @@ -24,6 +24,7 @@ public class SRDispatchCommandBufferInfo { public SRRenderApiType renderApiType; private OpenGLCommandBuffer openglCommandBuffer; private VulkanCommandBuffer vulkanCommandBuffer; + private D3D12CommandList d3d12CommandList; private SRDispatchCommandBufferInfo() { } @@ -49,6 +50,13 @@ public static SRDispatchCommandBufferInfo createVulkan(VkCommandBuffer commandBu return info; } + public static SRDispatchCommandBufferInfo createD3D12(long commandList) { + SRDispatchCommandBufferInfo info = new SRDispatchCommandBufferInfo(); + info.renderApiType = SRRenderApiType.D3D12; + info.d3d12CommandList = new D3D12CommandList(commandList); + return info; + } + public SRRenderApiType getRenderApiType() { return renderApiType; } @@ -61,13 +69,28 @@ public VulkanCommandBuffer getVulkanCommandBuffer() { return vulkanCommandBuffer; } - public long getVulkanCommandBufferAddress() { + public D3D12CommandList getD3D12CommandList() { + return d3d12CommandList; + } + + public long getNativeCommandBufferAddress() { if (renderApiType == SRRenderApiType.VULKAN && vulkanCommandBuffer != null) { return vulkanCommandBuffer.commandBuffer; } + if (renderApiType == SRRenderApiType.D3D12 && d3d12CommandList != null) { + return d3d12CommandList.commandList; + } return 0; } + /** + * @deprecated use {@link #getNativeCommandBufferAddress()}. + */ + @Deprecated + public long getVulkanCommandBufferAddress() { + return getNativeCommandBufferAddress(); + } + public static class OpenGLCommandBuffer { } @@ -94,4 +117,24 @@ public void setCommandBuffer(long commandBuffer) { this.commandBuffer = commandBuffer; } } + + public static class D3D12CommandList { + public long commandList; + + public D3D12CommandList() { + this.commandList = 0; + } + + public D3D12CommandList(long commandList) { + this.commandList = commandList; + } + + public long getCommandList() { + return commandList; + } + + public void setCommandList(long commandList) { + this.commandList = commandList; + } + } } diff --git a/common/src/main/java/io/homo/superresolution/srapi/SRRenderApiType.java b/common/src/main/java/io/homo/superresolution/srapi/SRRenderApiType.java index 0ff102b2..49674147 100644 --- a/common/src/main/java/io/homo/superresolution/srapi/SRRenderApiType.java +++ b/common/src/main/java/io/homo/superresolution/srapi/SRRenderApiType.java @@ -20,7 +20,8 @@ public enum SRRenderApiType { VULKAN(0), - OPENGL(1); + OPENGL(1), + D3D12(2); public final int value; diff --git a/common/src/main/java/io/homo/superresolution/srapi/SRTextureResource.java b/common/src/main/java/io/homo/superresolution/srapi/SRTextureResource.java index 04654716..34653da1 100644 --- a/common/src/main/java/io/homo/superresolution/srapi/SRTextureResource.java +++ b/common/src/main/java/io/homo/superresolution/srapi/SRTextureResource.java @@ -21,11 +21,14 @@ import io.homo.superresolution.core.graphics.impl.texture.ITexture; import io.homo.superresolution.core.graphics.vulkan.VulkanTexture; +import java.util.EnumSet; + public class SRTextureResource { public SRTextureResourceDescription description; public ITexture texture; public long handle; public long imageView = -1; + public int state = SRResourceStates.COMPUTE_READ.value; public SRTextureResource(ITexture texture) { @@ -37,8 +40,37 @@ public SRTextureResource(ITexture texture) { } } + /** + * Creates an SRAPI resource around an opaque native graphics resource. + * This is used by APIs such as D3D12 that do not yet implement ITexture. + */ + public SRTextureResource(long handle, SRTextureResourceDescription description) { + this(handle, description, EnumSet.of(SRResourceStates.COMPUTE_READ)); + } + + public SRTextureResource( + long handle, + SRTextureResourceDescription description, + EnumSet states) { + this.texture = null; + this.description = description; + this.handle = handle; + this.imageView = 0; + this.state = SRResourceStates.toBitmask(states); + } + public long getHandle() { - this.handle = texture.handle(); + if (texture != null) { + this.handle = texture.handle(); + } return handle; } + + public EnumSet getStates() { + return SRResourceStates.fromBitmask(state); + } + + public void setStates(EnumSet states) { + this.state = SRResourceStates.toBitmask(states); + } } diff --git a/common/src/main/java/io/homo/superresolution/srapi/SuperResolutionNativeAPI.java b/common/src/main/java/io/homo/superresolution/srapi/SuperResolutionNativeAPI.java index 83865b29..9e0b39c6 100644 --- a/common/src/main/java/io/homo/superresolution/srapi/SuperResolutionNativeAPI.java +++ b/common/src/main/java/io/homo/superresolution/srapi/SuperResolutionNativeAPI.java @@ -50,6 +50,7 @@ public static SRReturnCode srCreateUpscaleContext( desc.renderApiType.value, desc.getOpenglDeviceInfo(), desc.getVulkanDeviceInfo(), + desc.getD3D12DeviceInfo(), desc.upscaledSize.x, desc.upscaledSize.y, desc.renderSize.x, @@ -85,7 +86,7 @@ public static SRReturnCode srDispatchUpscale( int code = SuperResolutionNative.NsrDispatchUpscale( context.nativePtr, desc.commandList.renderApiType.value, - desc.commandList.getVulkanCommandBufferAddress(), + desc.commandList.getNativeCommandBufferAddress(), desc.color, desc.depth, desc.motionVectors, diff --git a/native/cpp/SRNativeFSR/include/sr/fsr/ffx_api_minimal.h b/native/cpp/SRNativeFSR/include/sr/fsr/ffx_api_minimal.h new file mode 100644 index 00000000..8aa21394 --- /dev/null +++ b/native/cpp/SRNativeFSR/include/sr/fsr/ffx_api_minimal.h @@ -0,0 +1,267 @@ +/* + * Minimal ABI declarations for the AMD FSR SDK 2.3 FFX API. + * + * These declarations intentionally cover only the signed DX12 upscaler DLL + * surface used by SRNativeFSR. They mirror AMD's MIT-licensed ffx_api.h, + * ffx_api_types.h, ffx_api_dx12.h, and ffx_upscale.h structures without + * requiring the full SDK as a build dependency. + * + * Copyright (C) 2026 Advanced Micro Devices, Inc. + * Copyright (C) 2026 Super Resolution contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include + +typedef void *ffxContext; +typedef uint32_t ffxReturnCode_t; +typedef uint64_t ffxStructType_t; + +enum FfxApiReturnCodes { + FFX_API_RETURN_OK = 0, + FFX_API_RETURN_ERROR = 1, + FFX_API_RETURN_ERROR_UNKNOWN_DESCTYPE = 2, + FFX_API_RETURN_ERROR_RUNTIME_ERROR = 3, + FFX_API_RETURN_NO_PROVIDER = 4, + FFX_API_RETURN_ERROR_MEMORY = 5, + FFX_API_RETURN_ERROR_PARAMETER = 6, + FFX_API_RETURN_PROVIDER_NO_SUPPORT_NEW_DESCTYPE = 7, +}; + +struct ffxApiHeader { + ffxStructType_t type; + ffxApiHeader *pNext; +}; + +typedef ffxApiHeader ffxCreateContextDescHeader; +typedef ffxApiHeader ffxQueryDescHeader; +typedef ffxApiHeader ffxDispatchDescHeader; +typedef void (*ffxApiMessage)(uint32_t type, const wchar_t *message); + +struct FfxApiDimensions2D { + uint32_t width; + uint32_t height; +}; + +struct FfxApiFloatCoords2D { + float x; + float y; +}; + +enum FfxApiSurfaceFormat { + FFX_API_SURFACE_FORMAT_UNKNOWN, + FFX_API_SURFACE_FORMAT_R32G32B32A32_TYPELESS, + FFX_API_SURFACE_FORMAT_R32G32B32A32_UINT, + FFX_API_SURFACE_FORMAT_R32G32B32A32_FLOAT, + FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT, + FFX_API_SURFACE_FORMAT_R32G32B32_FLOAT, + FFX_API_SURFACE_FORMAT_R32G32_FLOAT, + FFX_API_SURFACE_FORMAT_R8_UINT, + FFX_API_SURFACE_FORMAT_R32_UINT, + FFX_API_SURFACE_FORMAT_R8G8B8A8_TYPELESS, + FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM, + FFX_API_SURFACE_FORMAT_R8G8B8A8_SNORM, + FFX_API_SURFACE_FORMAT_R8G8B8A8_SRGB, + FFX_API_SURFACE_FORMAT_B8G8R8A8_TYPELESS, + FFX_API_SURFACE_FORMAT_B8G8R8A8_UNORM, + FFX_API_SURFACE_FORMAT_B8G8R8A8_SRGB, + FFX_API_SURFACE_FORMAT_R11G11B10_FLOAT, + FFX_API_SURFACE_FORMAT_R10G10B10A2_UNORM, + FFX_API_SURFACE_FORMAT_R16G16_FLOAT, + FFX_API_SURFACE_FORMAT_R16G16_UINT, + FFX_API_SURFACE_FORMAT_R16G16_SINT, + FFX_API_SURFACE_FORMAT_R16_FLOAT, + FFX_API_SURFACE_FORMAT_R16_UINT, + FFX_API_SURFACE_FORMAT_R16_UNORM, + FFX_API_SURFACE_FORMAT_R16_SNORM, + FFX_API_SURFACE_FORMAT_R8_UNORM, + FFX_API_SURFACE_FORMAT_R8G8_UNORM, + FFX_API_SURFACE_FORMAT_R8G8_UINT, + FFX_API_SURFACE_FORMAT_R32_FLOAT, + FFX_API_SURFACE_FORMAT_R9G9B9E5_SHAREDEXP, + FFX_API_SURFACE_FORMAT_R16G16B16A16_TYPELESS, + FFX_API_SURFACE_FORMAT_R32G32_TYPELESS, + FFX_API_SURFACE_FORMAT_R10G10B10A2_TYPELESS, + FFX_API_SURFACE_FORMAT_R16G16_TYPELESS, + FFX_API_SURFACE_FORMAT_R16_TYPELESS, + FFX_API_SURFACE_FORMAT_R8_TYPELESS, + FFX_API_SURFACE_FORMAT_R8G8_TYPELESS, + FFX_API_SURFACE_FORMAT_R32_TYPELESS, + FFX_API_SURFACE_FORMAT_R32G32_UINT, + FFX_API_SURFACE_FORMAT_R8_SNORM, +}; + +enum FfxApiResourceFlags { + FFX_API_RESOURCE_FLAGS_NONE = 0, +}; + +enum FfxApiResourceType { + FFX_API_RESOURCE_TYPE_BUFFER, + FFX_API_RESOURCE_TYPE_TEXTURE1D, + FFX_API_RESOURCE_TYPE_TEXTURE2D, + FFX_API_RESOURCE_TYPE_TEXTURE_CUBE, + FFX_API_RESOURCE_TYPE_TEXTURE3D, +}; + +struct FfxApiResourceDescription { + uint32_t type; + uint32_t format; + union { + uint32_t width; + uint32_t size; + }; + union { + uint32_t height; + uint32_t stride; + }; + union { + uint32_t depth; + uint32_t alignment; + }; + uint32_t mipCount; + uint32_t flags; + uint32_t usage; +}; + +struct FfxApiResource { + void *resource; + FfxApiResourceDescription description; + uint32_t state; +}; + +typedef void *(*ffxAlloc)(void *pUserData, uint64_t size); +typedef void (*ffxDealloc)(void *pUserData, void *pMem); + +struct ffxAllocationCallbacks { + void *pUserData; + ffxAlloc alloc; + ffxDealloc dealloc; +}; + +typedef ffxReturnCode_t (*PfnFfxCreateContext)( + ffxContext *context, + ffxCreateContextDescHeader *desc, + const ffxAllocationCallbacks *memCb); +typedef ffxReturnCode_t (*PfnFfxDestroyContext)( + ffxContext *context, + const ffxAllocationCallbacks *memCb); +typedef ffxReturnCode_t (*PfnFfxQuery)( + ffxContext *context, + ffxQueryDescHeader *desc); +typedef ffxReturnCode_t (*PfnFfxDispatch)( + ffxContext *context, + const ffxDispatchDescHeader *desc); + +struct FfxApiFunctions { + PfnFfxCreateContext createContext; + PfnFfxDestroyContext destroyContext; + PfnFfxQuery query; + PfnFfxDispatch dispatch; +}; + +#define FFX_API_EFFECT_MASK 0x00ff0000u +#define FFX_API_BACKEND_MASK 0xff000000u +#define FFX_API_EFFECT_ID_UPSCALE 0x00010000u +#define FFX_API_BACKEND_ID_DX12 0x00000000u +#define FFX_API_MAKE_EFFECT_SUB_ID(effectId, subversion) \ + ((effectId & FFX_API_EFFECT_MASK) | (subversion & ~FFX_API_EFFECT_MASK)) +#define FFX_API_MAKE_BACKEND_SUB_ID(backendId, subversion) \ + ((backendId & FFX_API_BACKEND_MASK) | (subversion & ~FFX_API_BACKEND_MASK)) + +#define FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE \ + FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x00) +#define FFX_API_DISPATCH_DESC_TYPE_UPSCALE \ + FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x01) +#define FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE_VERSION \ + FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x0b) +#define FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12 \ + FFX_API_MAKE_BACKEND_SUB_ID(FFX_API_BACKEND_ID_DX12, 0x02) +#define FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION 6u + +#define FFX_UPSCALER_VERSION_MAJOR 4 +#define FFX_UPSCALER_VERSION_MINOR 1 +#define FFX_UPSCALER_VERSION_PATCH 1 +#define FFX_UPSCALER_MAKE_VERSION(major, minor, patch) \ + (((major) << 22) | ((minor) << 12) | (patch)) +#define FFX_UPSCALER_VERSION \ + FFX_UPSCALER_MAKE_VERSION( \ + FFX_UPSCALER_VERSION_MAJOR, \ + FFX_UPSCALER_VERSION_MINOR, \ + FFX_UPSCALER_VERSION_PATCH) + +enum FfxApiCreateContextUpscaleFlags { + FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE = (1 << 0), + FFX_UPSCALE_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION = (1 << 2), + FFX_UPSCALE_ENABLE_DEPTH_INVERTED = (1 << 3), + FFX_UPSCALE_ENABLE_AUTO_EXPOSURE = (1 << 5), + FFX_UPSCALE_ENABLE_DEBUG_CHECKING = (1 << 7), +}; + +struct ffxCreateContextDescUpscale { + ffxCreateContextDescHeader header; + uint32_t flags; + FfxApiDimensions2D maxRenderSize; + FfxApiDimensions2D maxUpscaleSize; + ffxApiMessage fpMessage; +}; + +struct ffxCreateContextDescUpscaleVersion { + ffxCreateContextDescHeader header; + uint32_t version; +}; + +struct ffxCreateBackendDX12Desc { + ffxCreateContextDescHeader header; + void *device; +}; + +struct ffxDispatchDescUpscale { + ffxDispatchDescHeader header; + void *commandList; + FfxApiResource color; + FfxApiResource depth; + FfxApiResource motionVectors; + FfxApiResource exposure; + FfxApiResource reactive; + FfxApiResource transparencyAndComposition; + FfxApiResource output; + FfxApiFloatCoords2D jitterOffset; + FfxApiFloatCoords2D motionVectorScale; + FfxApiDimensions2D renderSize; + FfxApiDimensions2D upscaleSize; + bool enableSharpening; + float sharpness; + float frameTimeDelta; + float preExposure; + bool reset; + float cameraNear; + float cameraFar; + float cameraFovAngleVertical; + float viewSpaceToMetersFactor; + uint32_t flags; +}; + +struct ffxQueryGetProviderVersion { + ffxQueryDescHeader header; + uint64_t versionId; + const char *versionName; +}; diff --git a/native/cpp/SRNativeFSR/include/sr/fsr/ffx_api_upscale.h b/native/cpp/SRNativeFSR/include/sr/fsr/ffx_api_upscale.h new file mode 100644 index 00000000..98c299e3 --- /dev/null +++ b/native/cpp/SRNativeFSR/include/sr/fsr/ffx_api_upscale.h @@ -0,0 +1,20 @@ +#pragma once + +#include "sr/sr_api.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /** + * Extra string parameter containing the absolute path to AMD's signed + * amd_fidelityfx_upscaler_dx12.dll. If omitted, the provider attempts to + * load that filename through the process' standard DLL search path. + */ + #define SR_FFX_API_DLL_PATH_PARAM "ffxApiDllPath" + + SR_API SRUpscaleContextCallbacks srGetFfxApiUpscaleCallbacks(); + +#ifdef __cplusplus +} +#endif diff --git a/native/cpp/SRNativeFSR/src/ffx_api_upscale.cpp b/native/cpp/SRNativeFSR/src/ffx_api_upscale.cpp new file mode 100644 index 00000000..1e4ba543 --- /dev/null +++ b/native/cpp/SRNativeFSR/src/ffx_api_upscale.cpp @@ -0,0 +1,356 @@ +#include "sr/fsr/ffx_api_upscale.h" + +#if defined(ON_WIN64) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#include "sr/fsr/ffx_api_minimal.h" + +#include +#include +#include +#include + +namespace { + struct SRFfxApiPrivateData { + HMODULE module = nullptr; + FfxApiFunctions functions = {}; + ffxContext context = nullptr; + ffxCreateContextDescUpscale createDesc = {}; + ffxCreateBackendDX12Desc backendDesc = {}; + ffxCreateContextDescUpscaleVersion versionDesc = {}; + }; + + std::wstring utf8ToWide(const char *value) { + if (!value || !*value) { + return {}; + } + const int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value, -1, nullptr, 0); + if (length <= 0) { + return {}; + } + std::wstring result(static_cast(length), L'\0'); + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value, -1, result.data(), length); + result.pop_back(); + return result; + } + + void report( + const SRCreateUpscaleContextDesc *desc, + SRMessageType type, + const wchar_t *message) { + if (desc && desc->messageCallback) { + desc->messageCallback(type, message); + } + } + + bool loadFunctions(HMODULE module, FfxApiFunctions *outFunctions) { + outFunctions->createContext = reinterpret_cast( + GetProcAddress(module, "ffxCreateContext")); + outFunctions->destroyContext = reinterpret_cast( + GetProcAddress(module, "ffxDestroyContext")); + outFunctions->query = reinterpret_cast( + GetProcAddress(module, "ffxQuery")); + outFunctions->dispatch = reinterpret_cast( + GetProcAddress(module, "ffxDispatch")); + return outFunctions->createContext && + outFunctions->destroyContext && + outFunctions->query && + outFunctions->dispatch; + } + + uint32_t toFfxCreateFlags(uint32_t flags) { + uint32_t result = 0; + if (flags & SR_UPSCALE_CONTEXT_CREATE_FLAG_ENABLE_DEBUG) { + result |= FFX_UPSCALE_ENABLE_DEBUG_CHECKING; + } + if (flags & SR_UPSCALE_CONTEXT_CREATE_FLAG_ENABLE_AUTO_EXPOSURE) { + result |= FFX_UPSCALE_ENABLE_AUTO_EXPOSURE; + } + if (flags & SR_UPSCALE_CONTEXT_CREATE_FLAG_ENABLE_DEPTH_INVERTED) { + result |= FFX_UPSCALE_ENABLE_DEPTH_INVERTED; + } + if (flags & SR_UPSCALE_CONTEXT_CREATE_FLAG_ENABLE_MOTION_VECTORS_JITTERED) { + result |= FFX_UPSCALE_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION; + } + if (flags & SR_UPSCALE_CONTEXT_CREATE_FLAG_ENABLE_HDR) { + result |= FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE; + } + return result; + } + + uint32_t toFfxSurfaceFormat(SRTextureFormat format) { + // SRAPI and FFX API share values through R32_TYPELESS. + if (format >= SR_TEXTURE_FORMAT_UNKNOWN && + format <= SR_TEXTURE_FORMAT_R32_TYPELESS) { + return static_cast(format); + } + if (format == SR_TEXTURE_FORMAT_D32_SFLOAT) { + return FFX_API_SURFACE_FORMAT_R32_FLOAT; + } + return FFX_API_SURFACE_FORMAT_UNKNOWN; + } + + FfxApiResource toFfxResource( + const SRTextureResource &resource, + SRResourceStates defaultState) { + if (!resource.exist) { + return {}; + } + + FfxApiResource result = {}; + result.resource = resource.handle; + result.description.type = FFX_API_RESOURCE_TYPE_TEXTURE2D; + result.description.format = toFfxSurfaceFormat(resource.desc.format); + result.description.width = resource.desc.width; + result.description.height = resource.desc.height; + result.description.depth = 1; + result.description.mipCount = resource.desc.mipmapCount; + result.description.flags = FFX_API_RESOURCE_FLAGS_NONE; + result.description.usage = static_cast(resource.desc.usage); + result.state = resource.state != 0 + ? static_cast(resource.state) + : static_cast(defaultState); + return result; + } + + SRReturnCode fromFfxReturnCode(ffxReturnCode_t code) { + switch (code) { + case FFX_API_RETURN_OK: + return SR_RETURN_CODE_OK; + case FFX_API_RETURN_ERROR_PARAMETER: + return SR_RETURN_CODE_INVALID_ARGUMENT; + case FFX_API_RETURN_NO_PROVIDER: + case FFX_API_RETURN_PROVIDER_NO_SUPPORT_NEW_DESCTYPE: + return SR_RETURN_CODE_UNSUPPORTED; + default: + return SR_RETURN_CODE_ERROR; + } + } +} + +extern "C" { + SR_API SRReturnCode srFfxApiCreateUpscaleContext( + SRUpscaleContext *context, + const SRCreateUpscaleContextDesc *desc) { + if (!context || !desc) { + return SR_RETURN_CODE_NULL_POINTER; + } + if (desc->renderApiType != SR_RENDER_API_TYPE_D3D12) { + report(desc, SR_MESSAGE_TYPE_ERROR, L"FFX API upscaling requires D3D12."); + return SR_RETURN_CODE_UNSUPPORTED_RENDER_API; + } + if (!desc->renderDeviceInfo.d3d12.device) { + report(desc, SR_MESSAGE_TYPE_ERROR, L"FFX API upscaling requires an ID3D12Device."); + return SR_RETURN_CODE_INVALID_ARGUMENT; + } + + const char *dllPath = nullptr; + srParamsGetString( + &desc->extraParams, + SR_FFX_API_DLL_PATH_PARAM, + &dllPath, + "amd_fidelityfx_upscaler_dx12.dll"); + std::wstring wideDllPath = utf8ToWide(dllPath); + if (wideDllPath.empty()) { + report(desc, SR_MESSAGE_TYPE_ERROR, L"The FFX API DLL path is not valid UTF-8."); + return SR_RETURN_CODE_INVALID_ARGUMENT; + } + + HMODULE module = LoadLibraryExW( + wideDllPath.c_str(), + nullptr, + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + if (!module && std::wcschr(wideDllPath.c_str(), L'\\') == nullptr && + std::wcschr(wideDllPath.c_str(), L'/') == nullptr) { + // LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR requires a qualified path. + module = LoadLibraryExW( + wideDllPath.c_str(), + nullptr, + LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + } + if (!module) { + report(desc, SR_MESSAGE_TYPE_ERROR, L"Could not load amd_fidelityfx_upscaler_dx12.dll."); + return SR_RETURN_CODE_CANNOT_FIND_LIBRARY; + } + + auto *privateData = new (std::nothrow) SRFfxApiPrivateData(); + if (!privateData) { + FreeLibrary(module); + return SR_RETURN_CODE_ERROR; + } + privateData->module = module; + + if (!loadFunctions(module, &privateData->functions)) { + report(desc, SR_MESSAGE_TYPE_ERROR, L"The FFX API DLL is missing required exports."); + FreeLibrary(module); + delete privateData; + return SR_RETURN_CODE_INVALID_PROVIDER_LIBRARY; + } + + privateData->createDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE; + privateData->createDesc.header.pNext = &privateData->backendDesc.header; + privateData->createDesc.flags = toFfxCreateFlags(desc->flags); + privateData->createDesc.maxRenderSize = {desc->renderSize.x, desc->renderSize.y}; + privateData->createDesc.maxUpscaleSize = {desc->upscaledSize.x, desc->upscaledSize.y}; + privateData->createDesc.fpMessage = reinterpret_cast(desc->messageCallback); + + privateData->backendDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12; + privateData->backendDesc.header.pNext = &privateData->versionDesc.header; + privateData->backendDesc.device = desc->renderDeviceInfo.d3d12.device; + + privateData->versionDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE_VERSION; + privateData->versionDesc.header.pNext = nullptr; + privateData->versionDesc.version = FFX_UPSCALER_VERSION; + + const ffxReturnCode_t code = privateData->functions.createContext( + &privateData->context, + &privateData->createDesc.header, + nullptr); + if (code != FFX_API_RETURN_OK) { + report(desc, SR_MESSAGE_TYPE_ERROR, L"FFX API failed to create an upscaling context."); + FreeLibrary(module); + delete privateData; + return fromFfxReturnCode(code); + } + + context->desc = *desc; + context->userContext = privateData; + return SR_RETURN_CODE_OK; + } + + SR_API SRReturnCode srFfxApiInitUpscaleContext(SRUpscaleContext *context) { + if (!context || !context->userContext) { + return SR_RETURN_CODE_NULL_POINTER; + } + return SR_RETURN_CODE_OK; + } + + SR_API SRReturnCode srFfxApiDestroyUpscaleContext(SRUpscaleContext *context) { + if (!context || !context->userContext) { + return SR_RETURN_CODE_NULL_POINTER; + } + + auto *privateData = static_cast(context->userContext); + const ffxReturnCode_t code = privateData->functions.destroyContext( + &privateData->context, + nullptr); + FreeLibrary(privateData->module); + delete privateData; + context->userContext = nullptr; + return fromFfxReturnCode(code); + } + + SR_API SRReturnCode srFfxApiQueryUpscale( + SRUpscaleContext *context, + SRUpscaleContextQueryResult *result, + SRUpscaleContextQueryType queryType) { + if (!context || !context->userContext || !result) { + return SR_RETURN_CODE_NULL_POINTER; + } + auto *privateData = static_cast(context->userContext); + + switch (queryType) { + case SR_UPSCALE_CONTEXT_QUERY_VERSION_INFO: { + ffxQueryGetProviderVersion query = {}; + query.header.type = FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION; + const ffxReturnCode_t code = privateData->functions.query( + &privateData->context, + &query.header); + if (code != FFX_API_RETURN_OK) { + return fromFfxReturnCode(code); + } + static thread_local SRQueryVersionResult versionResult = {}; + versionResult.versionId = query.versionId; + versionResult.versionNumber = FFX_UPSCALER_VERSION; + result->type = queryType; + result->data = &versionResult; + return SR_RETURN_CODE_OK; + } + case SR_UPSCALE_CONTEXT_QUERY_GPU_MEMORY_INFO: { + static thread_local SRQueryGpuMemoryResult memoryResult = {}; + // The modern API exposes a pre-creation V2 memory query. Keep + // SRAPI's context query valid until that richer query surface + // is represented in SRAPI. + memoryResult.gpuMemory = 0; + result->type = queryType; + result->data = &memoryResult; + return SR_RETURN_CODE_OK; + } + case SR_UPSCALE_CONTEXT_QUERY_AVAILABLE: { + static thread_local SRQueryAvailabilityResult availabilityResult = {}; + availabilityResult.isAvailable = true; + result->type = queryType; + result->data = &availabilityResult; + return SR_RETURN_CODE_OK; + } + default: + return SR_RETURN_CODE_UNSUPPORTED; + } + } + + SR_API SRReturnCode srFfxApiDispatchUpscale( + SRUpscaleContext *context, + const SRDispatchUpscaleDesc *desc) { + if (!context || !context->userContext || !desc) { + return SR_RETURN_CODE_NULL_POINTER; + } + if (desc->commandList.renderApiType != SR_RENDER_API_TYPE_D3D12 || + !desc->commandList.apiCommandBuffer.d3d12.commandList) { + return SR_RETURN_CODE_UNSUPPORTED_RENDER_API; + } + + auto *privateData = static_cast(context->userContext); + ffxDispatchDescUpscale dispatchDesc = {}; + dispatchDesc.header.type = FFX_API_DISPATCH_DESC_TYPE_UPSCALE; + dispatchDesc.commandList = desc->commandList.apiCommandBuffer.d3d12.commandList; + dispatchDesc.color = toFfxResource(desc->color, SR_RESOURCE_STATE_COMPUTE_READ); + dispatchDesc.depth = toFfxResource(desc->depth, SR_RESOURCE_STATE_COMPUTE_READ); + dispatchDesc.motionVectors = toFfxResource(desc->motionVectors, SR_RESOURCE_STATE_COMPUTE_READ); + dispatchDesc.exposure = toFfxResource(desc->exposure, SR_RESOURCE_STATE_COMPUTE_READ); + dispatchDesc.reactive = toFfxResource(desc->reactive, SR_RESOURCE_STATE_COMPUTE_READ); + dispatchDesc.transparencyAndComposition = toFfxResource( + desc->transparencyAndComposition, + SR_RESOURCE_STATE_COMPUTE_READ); + dispatchDesc.output = toFfxResource(desc->output, SR_RESOURCE_STATE_UNORDERED_ACCESS); + dispatchDesc.jitterOffset = {desc->jitterOffset.x, desc->jitterOffset.y}; + dispatchDesc.motionVectorScale = {desc->motionVectorScale.x, desc->motionVectorScale.y}; + dispatchDesc.renderSize = {desc->renderSize.x, desc->renderSize.y}; + dispatchDesc.upscaleSize = {desc->upscaleSize.x, desc->upscaleSize.y}; + dispatchDesc.enableSharpening = desc->enableSharpening; + dispatchDesc.sharpness = desc->sharpness; + dispatchDesc.frameTimeDelta = desc->frameTimeDelta; + dispatchDesc.preExposure = desc->preExposure; + dispatchDesc.reset = desc->reset; + dispatchDesc.cameraNear = desc->cameraNear; + dispatchDesc.cameraFar = desc->cameraFar; + dispatchDesc.cameraFovAngleVertical = desc->cameraFovAngleVertical; + dispatchDesc.viewSpaceToMetersFactor = desc->viewSpaceToMetersFactor; + dispatchDesc.flags = desc->flags; + + return fromFfxReturnCode(privateData->functions.dispatch( + &privateData->context, + &dispatchDesc.header)); + } + + SR_API SRReturnCode srFfxApiShutdown() { + return SR_RETURN_CODE_OK; + } + + SR_API SRUpscaleContextCallbacks srGetFfxApiUpscaleCallbacks() { + static SRUpscaleContextCallbacks callbacks = { + .pCreate = srFfxApiCreateUpscaleContext, + .pInit = srFfxApiInitUpscaleContext, + .pDestroy = srFfxApiDestroyUpscaleContext, + .pQuery = reinterpret_cast(srFfxApiQueryUpscale), + .pDispatchUpscale = srFfxApiDispatchUpscale, + .pShutdown = srFfxApiShutdown, + }; + return callbacks; + } +} + +#endif diff --git a/native/cpp/SRNativeFSR/src/sr_provider.cpp b/native/cpp/SRNativeFSR/src/sr_provider.cpp index 2637adfe..bcdbb162 100644 --- a/native/cpp/SRNativeFSR/src/sr_provider.cpp +++ b/native/cpp/SRNativeFSR/src/sr_provider.cpp @@ -1,6 +1,15 @@ #include "sr/fsr/sr_provider.h" +#if defined(ON_WIN64) +#include "sr/fsr/ffx_api_upscale.h" +#endif -static SRUpscaleProvider g_providers[2]; +#if defined(ON_WIN64) +static constexpr uint32_t PROVIDER_COUNT = 3; +#else +static constexpr uint32_t PROVIDER_COUNT = 2; +#endif + +static SRUpscaleProvider g_providers[PROVIDER_COUNT]; static bool g_initialized = false; static void ensureInitialized() { @@ -10,6 +19,11 @@ static void ensureInitialized() { g_providers[1].providerId = SR_MODULES_FSR3_ID; g_providers[1].callbacks = srGetFfxFSR3UpscaleCallbacks(); + + #if defined(ON_WIN64) + g_providers[2].providerId = SR_MODULES_FFX_API_UPSCALE_ID; + g_providers[2].callbacks = srGetFfxApiUpscaleCallbacks(); + #endif g_initialized = true; } } @@ -19,12 +33,15 @@ extern "C" { ensureInitialized(); outProvider[0] = g_providers[0]; outProvider[1] = g_providers[1]; + #if defined(ON_WIN64) + outProvider[2] = g_providers[2]; + #endif return (SRReturnCode) SR_RETURN_CODE_OK; } SR_API SRReturnCode srGetFfxFSRUpscaleProvidersCount(uint32_t *outCount) { ensureInitialized(); - *outCount = 2; + *outCount = PROVIDER_COUNT; return (SRReturnCode) SR_RETURN_CODE_OK; } -} \ No newline at end of file +} diff --git a/native/cpp/SRNativeMain/include/io_homo_superresolution_core_SuperResolutionNative.h b/native/cpp/SRNativeMain/include/io_homo_superresolution_core_SuperResolutionNative.h index 03a06180..fb070cfa 100644 --- a/native/cpp/SRNativeMain/include/io_homo_superresolution_core_SuperResolutionNative.h +++ b/native/cpp/SRNativeMain/include/io_homo_superresolution_core_SuperResolutionNative.h @@ -50,10 +50,10 @@ JNIEXPORT jint JNICALL Java_io_homo_superresolution_core_SuperResolutionNative_d /* * Class: io_homo_superresolution_core_SuperResolutionNative * Method: NsrCreateUpscaleContext - * Signature: (Lio/homo/superresolution/srapi/SRUpscaleContext;JILio/homo/superresolution/srapi/SROpenGLDeviceInfo;Lio/homo/superresolution/srapi/SRVulkanDeviceInfo;IIIIJJI)I + * Signature: (Lio/homo/superresolution/srapi/SRUpscaleContext;JILio/homo/superresolution/srapi/SROpenGLDeviceInfo;Lio/homo/superresolution/srapi/SRVulkanDeviceInfo;Lio/homo/superresolution/srapi/SRD3D12DeviceInfo;IIIIJJI)I */ JNIEXPORT jint JNICALL Java_io_homo_superresolution_core_SuperResolutionNative_NsrCreateUpscaleContext - (JNIEnv *, jclass, jobject, jlong, jint, jobject, jobject, jint, jint, jint, jint, jlong, jlong, jint); + (JNIEnv *, jclass, jobject, jlong, jint, jobject, jobject, jobject, jint, jint, jint, jint, jlong, jlong, jint); /* * Class: io_homo_superresolution_core_SuperResolutionNative diff --git a/native/cpp/SRNativeMain/include/sr/sr_api_enums.h b/native/cpp/SRNativeMain/include/sr/sr_api_enums.h index 1566fc8a..22a3f0fc 100644 --- a/native/cpp/SRNativeMain/include/sr/sr_api_enums.h +++ b/native/cpp/SRNativeMain/include/sr/sr_api_enums.h @@ -41,6 +41,7 @@ extern "C" { typedef enum SRRenderApiType { SR_RENDER_API_TYPE_VULKAN = 0, SR_RENDER_API_TYPE_OPENGL = 1, + SR_RENDER_API_TYPE_D3D12 = 2, } SRRenderApiType; typedef enum SRParamValueType { @@ -58,4 +59,4 @@ extern "C" { #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/native/cpp/SRNativeMain/include/sr/sr_api_structs.h b/native/cpp/SRNativeMain/include/sr/sr_api_structs.h index dd828939..9bcf209b 100644 --- a/native/cpp/SRNativeMain/include/sr/sr_api_structs.h +++ b/native/cpp/SRNativeMain/include/sr/sr_api_structs.h @@ -71,6 +71,17 @@ extern "C" { SRGetFuncAddress instanceProcAddr; } SRVulkanDeviceInfo; + /** + * Direct3D 12 device information. + * + * The handle is intentionally opaque so the SRAPI headers remain + * consumable on non-Windows platforms without including d3d12.h. + * D3D12 providers reinterpret it as an ID3D12Device pointer. + */ + typedef struct SRD3D12DeviceInfo { + void *device; + } SRD3D12DeviceInfo; + typedef struct SRCommandBufferOpenGL { } SRCommandBufferOpenGL; @@ -78,12 +89,20 @@ extern "C" { VkCommandBuffer commandBuffer; } SRCommandBufferVulkan; + /** + * Opaque ID3D12GraphicsCommandList pointer. + */ + typedef struct SRCommandBufferD3D12 { + void *commandList; + } SRCommandBufferD3D12; + typedef struct SRDispatchCommandBufferInfo { SRRenderApiType renderApiType; union { SRCommandBufferOpenGL opengl; SRCommandBufferVulkan vulkan; + SRCommandBufferD3D12 d3d12; } apiCommandBuffer; } SRDispatchCommandBufferInfo; @@ -98,6 +117,7 @@ extern "C" { union { SROpenGLDeviceInfo opengl; SRVulkanDeviceInfo vulkan; + SRD3D12DeviceInfo d3d12; } renderDeviceInfo; SRVectorUint2 upscaledSize; @@ -112,6 +132,7 @@ extern "C" { SRTextureResourceDescription desc; void *handle; void *imageView; // 可选 + SRResourceStates state; } SRTextureResource; typedef struct SRDispatchUpscaleDesc { diff --git a/native/cpp/SRNativeMain/include/sr/sr_modules.h b/native/cpp/SRNativeMain/include/sr/sr_modules.h index 2b7b4807..67904b61 100644 --- a/native/cpp/SRNativeMain/include/sr/sr_modules.h +++ b/native/cpp/SRNativeMain/include/sr/sr_modules.h @@ -2,4 +2,5 @@ #define SR_MODULES_FSR2_ID 0x8000002 #define SR_MODULES_FSR3_ID 0x8000003 #define SR_MODULES_XeSS_ID 0x8000004 -#define SR_MODULES_DLSS_ID 0x8000005 \ No newline at end of file +#define SR_MODULES_DLSS_ID 0x8000005 +#define SR_MODULES_FFX_API_UPSCALE_ID 0x8000006 diff --git a/native/cpp/SRNativeMain/src/sr_api_jni.cpp b/native/cpp/SRNativeMain/src/sr_api_jni.cpp index a291b0a3..8b3a8452 100644 --- a/native/cpp/SRNativeMain/src/sr_api_jni.cpp +++ b/native/cpp/SRNativeMain/src/sr_api_jni.cpp @@ -96,6 +96,9 @@ SRTextureResource fromJavaSRTextureResourceVK(JNIEnv *env, jobject obj) { jfieldID imageViewFieldId = env->GetFieldID(cls, "imageView", JAVA_TYPE_LONG); jlong imageView = env->GetLongField(obj, imageViewFieldId); + jfieldID stateFieldId = env->GetFieldID(cls, "state", JAVA_TYPE_INT); + jint state = env->GetIntField(obj, stateFieldId); + jfieldID descFieldId = env->GetFieldID(cls, "description", "Lio/homo/superresolution/srapi/SRTextureResourceDescription;"); jobject descObj = env->GetObjectField(obj, descFieldId); @@ -107,6 +110,7 @@ SRTextureResource fromJavaSRTextureResourceVK(JNIEnv *env, jobject obj) { resource.handle = reinterpret_cast(image); resource.desc = desc; resource.imageView = reinterpret_cast(imageView); + resource.state = static_cast(state); if (descObj != nullptr) { env->DeleteLocalRef(descObj); } @@ -136,6 +140,7 @@ extern "C" { jint renderApiType, jobject openglDeviceInfo, jobject vulkanDeviceInfo, + jobject d3d12DeviceInfo, jint upscaledSizeX, jint upscaledSizeY, jint renderSizeX, @@ -203,6 +208,24 @@ extern "C" { L"Vulkan device info is required for Vulkan API type."); return SR_RETURN_CODE_INVALID_ARGUMENT; } + } else if (renderApiType == SR_RENDER_API_TYPE_D3D12) { + if (d3d12DeviceInfo != nullptr) { + jclass d3d12InfoCls = env->GetObjectClass(d3d12DeviceInfo); + jfieldID deviceField = env->GetFieldID(d3d12InfoCls, "device", "J"); + desc.renderDeviceInfo.d3d12.device = reinterpret_cast( + env->GetLongField(d3d12DeviceInfo, deviceField)); + env->DeleteLocalRef(d3d12InfoCls); + + if (desc.renderDeviceInfo.d3d12.device == nullptr) { + sr_message_callback_bridge(SR_MESSAGE_TYPE_ERROR, + L"A non-null D3D12 device is required."); + return SR_RETURN_CODE_INVALID_ARGUMENT; + } + } else { + sr_message_callback_bridge(SR_MESSAGE_TYPE_ERROR, + L"D3D12 device info is required for D3D12 API type."); + return SR_RETURN_CODE_INVALID_ARGUMENT; + } } else { sr_message_callback_bridge(SR_MESSAGE_TYPE_ERROR, L"Invalid render API type."); return SR_RETURN_CODE_INVALID_ARGUMENT; @@ -248,7 +271,7 @@ extern "C" { jclass clazz, jlong contextPtr, jint renderApiType, - jlong vulkanCommandBuffer, + jlong nativeCommandBuffer, jobject color, jobject depth, jobject motionVectors, @@ -282,7 +305,10 @@ extern "C" { desc.commandList.renderApiType = static_cast(renderApiType); if (renderApiType == SR_RENDER_API_TYPE_VULKAN) { desc.commandList.apiCommandBuffer.vulkan.commandBuffer = reinterpret_cast( - vulkanCommandBuffer); + nativeCommandBuffer); + } else if (renderApiType == SR_RENDER_API_TYPE_D3D12) { + desc.commandList.apiCommandBuffer.d3d12.commandList = reinterpret_cast( + nativeCommandBuffer); } if (extraParamsPtr != 0) { diff --git a/native/cpp/docs/add_upscale_provider.md b/native/cpp/docs/add_upscale_provider.md index e39e4d44..735f2509 100644 --- a/native/cpp/docs/add_upscale_provider.md +++ b/native/cpp/docs/add_upscale_provider.md @@ -10,7 +10,9 @@ Note: 除非你要添加的算法需要与C++本机库交互(截止0.8.2-alpha SR模组通过一个统一的 `SRAPI` 接口进行抽象。每个超分辨率算法(如 FSR, XeSS)都是一个独立的“提供器”(Provider)。SR模组通过加载这些提供器模块(动态链接库),并调用其标准化的函数来实现超分辨率功能。 -Note: `SRAPI`接口设计时完全不考虑D3D,所有提供器均假设运行在OpenGL或Vulkan环境中。 ~~*你问为什么就是懒*~~ +Note: `SRAPI`支持OpenGL、Vulkan和Direct3D 12。D3D12句柄在公共ABI中保持为不透明指针, +因此非Windows平台不需要包含`d3d12.h`。D3D12提供器应在自己的Windows实现中将这些句柄转换为 +`ID3D12Device`、`ID3D12GraphicsCommandList`和`ID3D12Resource`。 ### 关键接口和结构体 (`sr_api.h`) diff --git a/native/cpp/docs/ffx_api_d3d12_prototype.md b/native/cpp/docs/ffx_api_d3d12_prototype.md new file mode 100644 index 00000000..596db783 --- /dev/null +++ b/native/cpp/docs/ffx_api_d3d12_prototype.md @@ -0,0 +1,64 @@ +# SRAPI Direct3D 12 / AMD FFX API prototype + +This branch adds the native API foundation needed to run AMD's current +signed-DLL upscaler through Direct3D 12. + +## Why this uses FFX API + +FSR 4.1 is exposed by AMD FSR SDK 2.x through the FFX API and the signed +`amd_fidelityfx_upscaler_dx12.dll`. The older FidelityFX SDK 1.x backend +compiled shaders directly into the application. Reviving that deleted DX12 +backend would provide an FSR 2/3 implementation, but it would not be the +correct integration path for the current FSR 4.1 provider. + +The adapter in `SRNativeFSR/src/ffx_api_upscale.cpp` therefore: + +1. loads AMD's signed upscaler DLL dynamically; +2. resolves `ffxCreateContext`, `ffxDestroyContext`, `ffxQuery`, and + `ffxDispatch`; +3. translates SRAPI context and dispatch descriptions to the FFX API ABI; and +4. exposes the adapter as provider `SR_MODULES_FFX_API_UPSCALE_ID`. + +The signed AMD DLL is not copied into this repository. Obtain it from an +official AMD FSR SDK release and pass its absolute path through the +`ffxApiDllPath` context string parameter. If the parameter is omitted, the +provider looks for `amd_fidelityfx_upscaler_dx12.dll` in the process' secure +DLL search directories. + +## D3D12 SRAPI handles + +The cross-platform SRAPI ABI does not include `d3d12.h`. It carries: + +- `SRD3D12DeviceInfo.device` as an opaque `ID3D12Device*`; +- `SRCommandBufferD3D12.commandList` as an opaque + `ID3D12GraphicsCommandList*`; and +- `SRTextureResource.handle` as an opaque `ID3D12Resource*`. + +`SRTextureResource.state` describes the current resource state. It uses the +same bit values as the FFX API resource-state enum. + +The Java/JNI layer mirrors those values with `long` native addresses. Raw +D3D12 resources can be created with the `SRTextureResource(long, description, +states)` constructor. + +## Prototype boundary + +This change makes SRAPI and its FSR provider D3D12-capable, but Minecraft still +renders through OpenGL or the project's Vulkan path. A complete in-game FSR +4.1 implementation additionally needs a Windows graphics interop layer that: + +- creates a D3D12 device on the same physical adapter; +- shares the color, depth, motion-vector, exposure, and output resources with + the renderer; +- translates resource layouts/states correctly; and +- synchronizes OpenGL/Vulkan work with the D3D12 command queue and fences. + +That interop work belongs above SRAPI and should be implemented as a sibling to +the existing `VulkanInteropAlgorithm`; it is intentionally not hidden inside +the FFX provider. + +## Provider lifecycle + +All FFX API creation descriptors are stored in the provider's private context +for the full FFX context lifetime, as required by AMD's API contract. The AMD +DLL remains loaded until `srDestroyUpscaleContext` destroys the FFX context. From 0655ae00624341781ccda260a86485c066c6e244 Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Mon, 27 Jul 2026 13:35:34 +0800 Subject: [PATCH 02/19] add OpenGL D3D12 FSR 4 interop path --- .../common/upscale/AlgorithmDescriptions.java | 31 + .../common/upscale/D3D12InteropAlgorithm.java | 277 +++++++++ .../common/upscale/ffxfsr/FfxFSR4D3D12.java | 231 +++++++ .../graphics/d3d12/D3D12InteropContext.java | 268 ++++++++ .../graphics/d3d12/D3D12InteropNative.java | 53 ++ .../graphics/d3d12/D3D12InteropSemaphore.java | 98 +++ .../d3d12/GlD3D12ImportableTexture2D.java | 98 +++ native/cpp/SRNativeMain/CMakeLists.txt | 3 + native/cpp/SRNativeMain/src/d3d12_interop.cpp | 584 ++++++++++++++++++ native/cpp/docs/ffx_api_d3d12_prototype.md | 41 +- 10 files changed, 1671 insertions(+), 13 deletions(-) create mode 100644 common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java create mode 100644 common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java create mode 100644 common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropContext.java create mode 100644 common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropNative.java create mode 100644 common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropSemaphore.java create mode 100644 common/src/main/java/io/homo/superresolution/core/graphics/d3d12/GlD3D12ImportableTexture2D.java create mode 100644 native/cpp/SRNativeMain/src/d3d12_interop.cpp diff --git a/common/src/main/java/io/homo/superresolution/common/upscale/AlgorithmDescriptions.java b/common/src/main/java/io/homo/superresolution/common/upscale/AlgorithmDescriptions.java index 56235e09..d69c73a7 100644 --- a/common/src/main/java/io/homo/superresolution/common/upscale/AlgorithmDescriptions.java +++ b/common/src/main/java/io/homo/superresolution/common/upscale/AlgorithmDescriptions.java @@ -33,6 +33,7 @@ import io.homo.superresolution.common.upscale.anime4k.Anime4K; import io.homo.superresolution.common.upscale.dlss.DLSS; import io.homo.superresolution.common.upscale.ffxfsr.FfxFSR; +import io.homo.superresolution.common.upscale.ffxfsr.FfxFSR4D3D12; import io.homo.superresolution.common.upscale.fsr1.FSR1; import io.homo.superresolution.common.upscale.fsr2.FSR2; import io.homo.superresolution.common.upscale.none.None; @@ -180,6 +181,35 @@ public class AlgorithmDescriptions { .customUpscaleRatio(true) .build(); + public static final AlgorithmDescription FSR4_D3D12 = + AlgorithmDescription.builder(FfxFSR4D3D12.class) + .briefName("AMD FSR 4.1 (D3D12)") + .codeName("fsr4_d3d12") + .displayName("AMD FSR 4.1 (Direct3D 12)") + .requirement( + Requirement.nothing() + .addSupportedOS(new OperatingSystem( + SystemArchitecture.X86_64, + OperatingSystemType.WINDOWS)) + .requiredGlExtension("GL_EXT_memory_object") + .requiredGlExtension("GL_EXT_memory_object_win32") + .requiredGlExtension("GL_EXT_semaphore") + .requiredGlExtension("GL_EXT_semaphore_win32") + .glMajorVersion(4) + .glMinorVersion(6) + ) + .extraResources( + ExtraResources.builder() + .add(ExtraResource.builder( + FfxFSR4D3D12.UPSCALER_DLL_NAME) + .build()) + .build() + ) + .supportJitter(true) + .qualityPresets(FSR_QUALITY_PRESETS) + .customUpscaleRatio(true) + .build(); + public static final AlgorithmDescription XESS = AlgorithmDescription.builder(XeSS.class) .briefName("Intel XeSS") .codeName("xess") @@ -285,6 +315,7 @@ public static void registryAlgorithms() { AlgorithmRegistry.registry(FSR1); AlgorithmRegistry.registry(FSR2); AlgorithmRegistry.registry(FSR); + AlgorithmRegistry.registry(FSR4_D3D12); AlgorithmRegistry.registry(XESS); AlgorithmRegistry.registry(DLSS); AlgorithmRegistry.registry(SGSR1); diff --git a/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java b/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java new file mode 100644 index 00000000..f99ad57f --- /dev/null +++ b/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java @@ -0,0 +1,277 @@ +/* + * Super Resolution + * Copyright (c) 2026. 187J3X1-114514 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +package io.homo.superresolution.common.upscale; + +import io.homo.superresolution.api.AbstractAlgorithm; +import io.homo.superresolution.api.InitializationDescription; +import io.homo.superresolution.common.config.SuperResolutionConfig; +import io.homo.superresolution.common.minecraft.handler.RenderHandlerManager; +import io.homo.superresolution.common.workmode.SRWorkModeManager; +import io.homo.superresolution.core.RenderSystems; +import io.homo.superresolution.core.graphics.d3d12.D3D12InteropContext; +import io.homo.superresolution.core.graphics.d3d12.D3D12InteropSemaphore; +import io.homo.superresolution.core.graphics.d3d12.GlD3D12ImportableTexture2D; +import io.homo.superresolution.core.graphics.impl.framebuffer.FramebufferDescription; +import io.homo.superresolution.core.graphics.impl.framebuffer.IFrameBuffer; +import io.homo.superresolution.core.graphics.impl.texture.TextureDescription; +import io.homo.superresolution.core.graphics.impl.texture.TextureType; +import io.homo.superresolution.core.graphics.impl.texture.TextureUsages; +import io.homo.superresolution.core.graphics.opengl.texture.GlTexture2D; + +import static org.lwjgl.opengl.EXTSemaphore.GL_LAYOUT_GENERAL_EXT; +import static org.lwjgl.opengl.EXTSemaphore.GL_LAYOUT_SHADER_READ_ONLY_EXT; + +/** + * Low-latency OpenGL/Direct3D 12 interop path. + * + *

D3D12 owns the shared committed resources and a shared timeline fence. + * OpenGL imports those objects, writes the preprocessed inputs, signals the + * fence, waits for the D3D12 dispatch, and then copies the vertically flipped + * output into a regular OpenGL texture.

+ */ +public abstract class D3D12InteropAlgorithm extends AbstractAlgorithm { + protected D3D12InteropContext d3d12Interop; + protected GlD3D12ImportableTexture2D inputColor; + protected GlD3D12ImportableTexture2D inputDepth; + protected GlD3D12ImportableTexture2D inputMotionVectors; + protected GlD3D12ImportableTexture2D inputExposure; + protected GlD3D12ImportableTexture2D outputColor; + + private D3D12InteropSemaphore semaphore; + private GlTexture2D flippedOutput; + private IFrameBuffer outputFramebuffer; + private int builtRenderWidth = -1; + private int builtRenderHeight = -1; + private int builtScreenWidth = -1; + private int builtScreenHeight = -1; + + protected abstract void onD3D12InteropCreated(InitializationDescription desc); + + protected abstract void onBeforeD3D12InteropDestroyed(); + + protected abstract boolean dispatchD3D12Upscale( + long commandList, + DispatchResource dispatchResource); + + protected boolean isD3D12UpscalerReady() { + return true; + } + + @Override + public void initialize(InitializationDescription desc) { + this.initDesc = desc; + try { + createResources(); + onD3D12InteropCreated(desc); + } catch (Throwable throwable) { + try { + destroyResources(); + } catch (Throwable cleanupFailure) { + throwable.addSuppressed(cleanupFailure); + } + throw throwable; + } + } + + private void createResources() { + d3d12Interop = D3D12InteropContext.create( + RenderHandlerManager.getRenderWidth(), + RenderHandlerManager.getRenderHeight(), + RenderHandlerManager.getScreenWidth(), + RenderHandlerManager.getScreenHeight(), + SuperResolutionConfig.getInternalTextureFormat()); + + inputColor = new GlD3D12ImportableTexture2D(d3d12Interop.inputColor()); + inputDepth = new GlD3D12ImportableTexture2D(d3d12Interop.inputDepth()); + inputMotionVectors = new GlD3D12ImportableTexture2D(d3d12Interop.inputMotionVectors()); + inputExposure = new GlD3D12ImportableTexture2D(d3d12Interop.inputExposure()); + outputColor = new GlD3D12ImportableTexture2D(d3d12Interop.outputColor()); + semaphore = new D3D12InteropSemaphore(d3d12Interop.getFenceSharedHandle()); + + flippedOutput = (GlTexture2D) RenderSystems.opengl().device().createTexture( + TextureDescription.create() + .type(TextureType.Texture2D) + .usages(TextureUsages.create().sampler().storage()) + .format(SuperResolutionConfig.getInternalTextureFormat()) + .width(RenderHandlerManager.getScreenWidth()) + .height(RenderHandlerManager.getScreenHeight()) + .label("D3D12UpscaleFlippedOutput") + .build()); + outputFramebuffer = RenderSystems.opengl().device().createFramebuffer( + FramebufferDescription.create() + .colorAttachment(flippedOutput) + .label("D3D12UpscaleOutputFramebuffer") + .build()); + builtRenderWidth = RenderHandlerManager.getRenderWidth(); + builtRenderHeight = RenderHandlerManager.getRenderHeight(); + builtScreenWidth = RenderHandlerManager.getScreenWidth(); + builtScreenHeight = RenderHandlerManager.getScreenHeight(); + } + + @Override + public boolean dispatch(DispatchResource dispatchResource) { + super.dispatch(dispatchResource); + if (d3d12Interop == null || !isD3D12UpscalerReady()) { + return false; + } + + InteropResourcesConverter.processInputTextures( + dispatchResource.resources().colorTexture(), + inputColor, + dispatchResource.resources().depthTexture(), + inputDepth, + dispatchResource.resources().motionVectorsTexture(), + inputMotionVectors, + dispatchResource.resources().exposureTexture(), + inputExposure, + SRWorkModeManager.getCurrentState().motionVectorPreprocessingFunction()); + + int[] sharedTextures = sharedTextureIds(); + long openGlReadyValue = d3d12Interop.nextFenceValue(); + semaphore.signal( + openGlReadyValue, + sharedTextures, + new int[]{ + GL_LAYOUT_SHADER_READ_ONLY_EXT, + GL_LAYOUT_SHADER_READ_ONLY_EXT, + GL_LAYOUT_SHADER_READ_ONLY_EXT, + GL_LAYOUT_SHADER_READ_ONLY_EXT, + GL_LAYOUT_GENERAL_EXT + }); + + d3d12Interop.beginFrame(openGlReadyValue); + boolean dispatched; + long d3d12DoneValue = d3d12Interop.nextFenceValue(); + try { + dispatched = dispatchD3D12Upscale( + d3d12Interop.getCommandList(), + dispatchResource); + } finally { + // Always close and submit the command list, then reacquire every + // resource in OpenGL. This keeps the allocator and cross-API + // ownership usable even if a provider throws after recording part + // of a dispatch. + d3d12Interop.executeFrame(d3d12DoneValue); + semaphore.waitFor( + d3d12DoneValue, + sharedTextures, + new int[]{ + GL_LAYOUT_GENERAL_EXT, + GL_LAYOUT_GENERAL_EXT, + GL_LAYOUT_GENERAL_EXT, + GL_LAYOUT_GENERAL_EXT, + GL_LAYOUT_GENERAL_EXT + }); + } + + InteropResourcesConverter.flipY(outputColor, flippedOutput); + return dispatched; + } + + private int[] sharedTextureIds() { + return new int[]{ + Math.toIntExact(inputColor.handle()), + Math.toIntExact(inputDepth.handle()), + Math.toIntExact(inputMotionVectors.handle()), + Math.toIntExact(inputExposure.handle()), + Math.toIntExact(outputColor.handle()) + }; + } + + @Override + public void resize(int width, int height) { + if (isD3D12UpscalerReady() && + RenderHandlerManager.getRenderWidth() == builtRenderWidth && + RenderHandlerManager.getRenderHeight() == builtRenderHeight && + RenderHandlerManager.getScreenWidth() == builtScreenWidth && + RenderHandlerManager.getScreenHeight() == builtScreenHeight) { + return; + } + destroyResources(); + needsHistoryReset = true; + try { + createResources(); + onD3D12InteropCreated(initDesc); + } catch (Throwable throwable) { + try { + destroyResources(); + } catch (Throwable cleanupFailure) { + throwable.addSuppressed(cleanupFailure); + } + throw throwable; + } + } + + @Override + public void destroy() { + destroyResources(); + } + + private void destroyResources() { + if (d3d12Interop != null) { + d3d12Interop.waitIdle(); + } + onBeforeD3D12InteropDestroyed(); + + if (outputFramebuffer != null) { + outputFramebuffer.destroy(); + outputFramebuffer = null; + } + if (flippedOutput != null) { + flippedOutput.destroy(); + flippedOutput = null; + } + if (outputColor != null) { + outputColor.destroy(); + outputColor = null; + } + if (inputExposure != null) { + inputExposure.destroy(); + inputExposure = null; + } + if (inputMotionVectors != null) { + inputMotionVectors.destroy(); + inputMotionVectors = null; + } + if (inputDepth != null) { + inputDepth.destroy(); + inputDepth = null; + } + if (inputColor != null) { + inputColor.destroy(); + inputColor = null; + } + if (semaphore != null) { + semaphore.close(); + semaphore = null; + } + if (d3d12Interop != null) { + d3d12Interop.close(); + d3d12Interop = null; + } + builtRenderWidth = -1; + builtRenderHeight = -1; + builtScreenWidth = -1; + builtScreenHeight = -1; + } + + @Override + public IFrameBuffer getOutputFrameBuffer() { + return outputFramebuffer; + } + + @Override + public int getOutputTextureId() { + return flippedOutput == null + ? 0 + : Math.toIntExact(flippedOutput.handle()); + } +} diff --git a/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java b/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java new file mode 100644 index 00000000..862e905a --- /dev/null +++ b/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java @@ -0,0 +1,231 @@ +/* + * Super Resolution + * Copyright (c) 2026. 187J3X1-114514 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +package io.homo.superresolution.common.upscale.ffxfsr; + +import io.homo.superresolution.api.InitializationDescription; +import io.homo.superresolution.common.SuperResolution; +import io.homo.superresolution.common.config.SuperResolutionConfig; +import io.homo.superresolution.common.minecraft.handler.RenderHandlerManager; +import io.homo.superresolution.common.upscale.D3D12InteropAlgorithm; +import io.homo.superresolution.common.upscale.DispatchResource; +import io.homo.superresolution.core.NativeLibManager; +import io.homo.superresolution.core.SuperResolutionConstants; +import io.homo.superresolution.core.graphics.d3d12.D3D12InteropContext; +import io.homo.superresolution.srapi.*; +import org.joml.Vector2f; +import org.joml.Vector2i; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.EnumSet; + +/** + * AMD FSR 4.1 through the signed FFX API Direct3D 12 provider. + */ +public final class FfxFSR4D3D12 extends D3D12InteropAlgorithm { + public static final String UPSCALER_DLL_NAME = + "amd_fidelityfx_upscaler_dx12.dll"; + private static final long PROVIDER_ID = 0x8000006L; + + private SRUpscaleContext context; + + @Override + protected void onD3D12InteropCreated(InitializationDescription desc) { + Path providerLibrary = NativeLibManager.LIB_SUPER_RESOLUTION_FSR + .getTargetPath(SuperResolutionConstants.NATIVE_LIBRARIES_DIR.getPath()) + .toAbsolutePath(); + Path upscalerDll = SuperResolutionConstants.NATIVE_LIBRARIES_DIR + .getPath() + .resolve(UPSCALER_DLL_NAME) + .toAbsolutePath(); + if (!Files.isReadable(providerLibrary)) { + throw new IllegalStateException( + "FSR provider library is missing: " + providerLibrary); + } + if (!Files.isReadable(upscalerDll)) { + throw new IllegalStateException( + "AMD signed FFX upscaler DLL is missing: " + upscalerDll); + } + + SRReturnCode loadCode = + SuperResolutionNativeAPI.srLoadUpscaleProvidersFromLibrary( + providerLibrary.toString(), + "srGetFfxFSRUpscaleProviders", + "srGetFfxFSRUpscaleProvidersCount"); + if (loadCode != SRReturnCode.OK) { + throw new IllegalStateException( + "Could not load FSR providers: " + loadCode); + } + + try (SRUpscaleProvider provider = new SRUpscaleProvider(0)) { + SRReturnCode providerCode = + SuperResolutionNativeAPI.srGetUpscaleProvider( + provider, + PROVIDER_ID); + if (providerCode != SRReturnCode.OK) { + throw new IllegalStateException( + "Could not acquire the D3D12 FFX API provider: " + + providerCode); + } + + EnumSet flags = + EnumSet.of(SRUpscaleContextCreateFlags.ENABLE_DEBUG); + if (desc.isAutoExposure()) { + flags.add(SRUpscaleContextCreateFlags.ENABLE_AUTO_EXPOSURE); + } + if (desc.isHdrInput()) { + flags.add(SRUpscaleContextCreateFlags.ENABLE_HDR); + } + if (desc.isMotionJittered()) { + flags.add( + SRUpscaleContextCreateFlags.ENABLE_MOTION_VECTORS_JITTERED); + } + + context = new SRUpscaleContext(0); + try (SRCreateUpscaleContextDesc createDesc = + SRCreateUpscaleContextDesc.createD3D12( + new SRD3D12DeviceInfo(d3d12Interop.getDevice()), + new Vector2i( + RenderHandlerManager.getScreenWidth(), + RenderHandlerManager.getScreenHeight()), + new Vector2i( + RenderHandlerManager.getRenderWidth(), + RenderHandlerManager.getRenderHeight()), + flags)) { + SRReturnCode pathCode = createDesc + .getExtraParams() + .setString("ffxApiDllPath", upscalerDll.toString()); + if (pathCode != SRReturnCode.OK) { + throw new IllegalStateException( + "Could not configure the FFX API DLL path: " + + pathCode); + } + + SRReturnCode createCode = + SuperResolutionNativeAPI.srCreateUpscaleContext( + context, + provider, + createDesc); + if (createCode != SRReturnCode.OK) { + context = null; + throw new IllegalStateException( + "Could not create the FSR 4.1 context: " + + createCode); + } + SRReturnCode initCode = + SuperResolutionNativeAPI.srInitUpscaleContext(context); + if (initCode != SRReturnCode.OK) { + context.destroy(); + context = null; + throw new IllegalStateException( + "Could not initialize the FSR 4.1 context: " + + initCode); + } + } + } + } + + @Override + protected void onBeforeD3D12InteropDestroyed() { + if (context != null) { + if (context.nativePtr > 0) { + SRReturnCode code = context.destroy(); + if (code != SRReturnCode.OK) { + SuperResolution.LOGGER.error( + "Failed to destroy FSR 4.1 context: {}", + code); + } + } + context = null; + } + } + + @Override + protected boolean isD3D12UpscalerReady() { + return context != null && context.nativePtr > 0; + } + + @Override + protected boolean dispatchD3D12Upscale( + long commandList, + DispatchResource dispatchResource) { + try (SRDispatchUpscaleDesc desc = new SRDispatchUpscaleDesc()) { + desc.setCommandBuffer( + SRDispatchCommandBufferInfo.createD3D12(commandList)); + desc.setColor(resource( + d3d12Interop.inputColor(), + SRResourceStates.COMPUTE_READ)); + desc.setDepth(resource( + d3d12Interop.inputDepth(), + SRResourceStates.COMPUTE_READ)); + desc.setMotionVectors(resource( + d3d12Interop.inputMotionVectors(), + SRResourceStates.COMPUTE_READ)); + if (!initDesc.isAutoExposure() && + dispatchResource.resources().exposureTexture() != null) { + desc.setExposure(resource( + d3d12Interop.inputExposure(), + SRResourceStates.COMPUTE_READ)); + } + desc.setOutput(resource( + d3d12Interop.outputColor(), + SRResourceStates.UNORDERED_ACCESS)); + + desc.setJitterOffset(new Vector2f(dispatchResource.jitterOffset())); + desc.setMotionVectorScale(new Vector2f( + dispatchResource.renderWidth(), + dispatchResource.renderHeight())); + desc.setRenderSize(new Vector2i( + dispatchResource.renderWidth(), + dispatchResource.renderHeight())); + desc.setUpscaleSize(new Vector2i( + dispatchResource.screenWidth(), + dispatchResource.screenHeight())); + desc.setFrameTimeDelta(dispatchResource.frameTimeDelta()); + desc.setEnableSharpening(true); + desc.setSharpness(SuperResolutionConfig.getSharpness()); + desc.setPreExposure(dispatchResource.preExposure()); + desc.setCameraNear(dispatchResource.cameraNear()); + desc.setCameraFar(dispatchResource.cameraFar()); + desc.setCameraFovAngleVertical( + (float) Math.toRadians(dispatchResource.verticalFov())); + desc.setViewSpaceToMetersFactor(1.0f); + desc.setReset(consumeHistoryReset()); + desc.setFlags(0); + + SRReturnCode code = + SuperResolutionNativeAPI.srDispatchUpscale(context, desc); + if (code != SRReturnCode.OK) { + SuperResolution.LOGGER.error( + "FSR 4.1 D3D12 dispatch failed: {}", + code); + return false; + } + return true; + } + } + + private static SRTextureResource resource( + D3D12InteropContext.Resource resource, + SRResourceStates state) { + SRTextureResourceDescription description = + new SRTextureResourceDescription( + resource.srFormat(), + resource.textureDescription().getWidth(), + resource.textureDescription().getHeight(), + 1, + SRResourceUsage.UAV.value); + return new SRTextureResource( + resource.nativeResource(), + description, + EnumSet.of(state)); + } +} diff --git a/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropContext.java b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropContext.java new file mode 100644 index 00000000..19b203f6 --- /dev/null +++ b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropContext.java @@ -0,0 +1,268 @@ +/* + * Super Resolution + * Copyright (c) 2026. 187J3X1-114514 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +package io.homo.superresolution.core.graphics.d3d12; + +import io.homo.superresolution.core.graphics.impl.texture.TextureDescription; +import io.homo.superresolution.core.graphics.impl.texture.TextureFormat; +import io.homo.superresolution.core.graphics.impl.texture.TextureType; +import io.homo.superresolution.core.graphics.impl.texture.TextureUsages; +import io.homo.superresolution.srapi.SRSurfaceFormat; +import org.lwjgl.opengl.EXTMemoryObject; +import org.lwjgl.opengl.EXTMemoryObjectWin32; +import org.lwjgl.opengl.GL; +import org.lwjgl.system.MemoryStack; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Native owner for the D3D12 device, queue, command list, shared resources, + * and shared timeline fence used by OpenGL/Direct3D interop. + */ +public final class D3D12InteropContext implements AutoCloseable { + private long nativePtr; + private long nextFenceValue = 1; + + private final Resource inputColor; + private final Resource inputDepth; + private final Resource inputMotionVectors; + private final Resource inputExposure; + private final Resource outputColor; + + private D3D12InteropContext( + long nativePtr, + int renderWidth, + int renderHeight, + int outputWidth, + int outputHeight, + TextureFormat colorFormat) { + this.nativePtr = nativePtr; + this.inputColor = readResource( + D3D12InteropNative.RESOURCE_INPUT_COLOR, + textureDescription(renderWidth, renderHeight, colorFormat, "D3D12InputColor"), + toSrFormat(colorFormat)); + this.inputDepth = readResource( + D3D12InteropNative.RESOURCE_INPUT_DEPTH, + textureDescription(renderWidth, renderHeight, TextureFormat.R32F, "D3D12InputDepth"), + SRSurfaceFormat.R32_FLOAT); + this.inputMotionVectors = readResource( + D3D12InteropNative.RESOURCE_INPUT_MOTION_VECTORS, + textureDescription(renderWidth, renderHeight, TextureFormat.RG16F, "D3D12InputMotionVectors"), + SRSurfaceFormat.R16G16_FLOAT); + this.inputExposure = readResource( + D3D12InteropNative.RESOURCE_INPUT_EXPOSURE, + textureDescription(1, 1, TextureFormat.R16F, "D3D12InputExposure"), + SRSurfaceFormat.R16_FLOAT); + this.outputColor = readResource( + D3D12InteropNative.RESOURCE_OUTPUT_COLOR, + textureDescription(outputWidth, outputHeight, colorFormat, "D3D12OutputColor"), + toSrFormat(colorFormat)); + } + + public static D3D12InteropContext create( + int renderWidth, + int renderHeight, + int outputWidth, + int outputHeight, + TextureFormat colorFormat) { + if (!GL.getCapabilities().GL_EXT_memory_object_win32 || + !GL.getCapabilities().GL_EXT_semaphore_win32) { + throw new UnsupportedOperationException( + "D3D12 interop requires GL_EXT_memory_object_win32 and GL_EXT_semaphore_win32."); + } + + long adapterLuid = queryOpenGlAdapterLuid(); + long nativePtr = D3D12InteropNative.Nd3d12CreateContext( + adapterLuid, + renderWidth, + renderHeight, + outputWidth, + outputHeight, + toSrFormat(colorFormat).value); + if (nativePtr == 0) { + throw new IllegalStateException( + "Could not create D3D12 interop context: " + + D3D12InteropNative.Nd3d12GetLastError()); + } + try { + return new D3D12InteropContext( + nativePtr, + renderWidth, + renderHeight, + outputWidth, + outputHeight, + colorFormat); + } catch (Throwable throwable) { + D3D12InteropNative.Nd3d12DestroyContext(nativePtr); + throw throwable; + } + } + + private static long queryOpenGlAdapterLuid() { + try (MemoryStack stack = MemoryStack.stackPush()) { + ByteBuffer luid = stack.calloc(EXTMemoryObjectWin32.GL_LUID_SIZE_EXT) + .order(ByteOrder.nativeOrder()); + EXTMemoryObject.glGetUnsignedBytevEXT( + EXTMemoryObjectWin32.GL_DEVICE_LUID_EXT, + luid); + long lowPart = Integer.toUnsignedLong(luid.getInt(0)); + long highPart = Integer.toUnsignedLong(luid.getInt(4)); + long value = lowPart | (highPart << 32); + if (value == 0) { + throw new IllegalStateException("OpenGL reported a null device LUID."); + } + return value; + } + } + + private static TextureDescription textureDescription( + int width, + int height, + TextureFormat format, + String label) { + return TextureDescription.create() + .type(TextureType.Texture2D) + .width(width) + .height(height) + .format(format) + .usages(TextureUsages.create().sampler().storage()) + .label(label) + .build(); + } + + private static SRSurfaceFormat toSrFormat(TextureFormat format) { + return switch (format) { + case RGBA8 -> SRSurfaceFormat.R8G8B8A8_UNORM; + case RGBA16F -> SRSurfaceFormat.R16G16B16A16_FLOAT; + case R11G11B10F -> SRSurfaceFormat.R11G11B10_FLOAT; + default -> throw new IllegalArgumentException( + "Unsupported D3D12 interop color format: " + format); + }; + } + + private Resource readResource( + int index, + TextureDescription description, + SRSurfaceFormat srFormat) { + long resource = D3D12InteropNative.Nd3d12GetResource(nativePtr, index); + long sharedHandle = D3D12InteropNative.Nd3d12GetResourceSharedHandle(nativePtr, index); + long allocationSize = D3D12InteropNative.Nd3d12GetResourceAllocationSize(nativePtr, index); + if (resource == 0 || sharedHandle == 0 || allocationSize <= 0) { + throw new IllegalStateException( + "Native D3D12 resource " + index + " is incomplete: " + + D3D12InteropNative.Nd3d12GetLastError()); + } + return new Resource( + index, + resource, + sharedHandle, + allocationSize, + description, + srFormat); + } + + public long getDevice() { + ensureOpen(); + return D3D12InteropNative.Nd3d12GetDevice(nativePtr); + } + + public long getCommandList() { + ensureOpen(); + return D3D12InteropNative.Nd3d12GetCommandList(nativePtr); + } + + public long getFenceSharedHandle() { + ensureOpen(); + return D3D12InteropNative.Nd3d12GetFenceSharedHandle(nativePtr); + } + + public long nextFenceValue() { + ensureOpen(); + return nextFenceValue++; + } + + public void beginFrame(long openGlReadyFenceValue) { + ensureOpen(); + int code = D3D12InteropNative.Nd3d12BeginFrame( + nativePtr, + openGlReadyFenceValue); + check(code, "begin D3D12 interop frame"); + } + + public void executeFrame(long d3d12DoneFenceValue) { + ensureOpen(); + int code = D3D12InteropNative.Nd3d12ExecuteFrame( + nativePtr, + d3d12DoneFenceValue); + check(code, "execute D3D12 interop frame"); + } + + public void waitIdle() { + if (nativePtr == 0) { + return; + } + check( + D3D12InteropNative.Nd3d12WaitIdle(nativePtr), + "wait for D3D12 interop"); + } + + private static void check(int code, String operation) { + if (code != 0) { + throw new IllegalStateException( + "Could not " + operation + " (0x" + + Integer.toHexString(code) + "): " + + D3D12InteropNative.Nd3d12GetLastError()); + } + } + + private void ensureOpen() { + if (nativePtr == 0) { + throw new IllegalStateException("D3D12 interop context is closed."); + } + } + + public Resource inputColor() { + return inputColor; + } + + public Resource inputDepth() { + return inputDepth; + } + + public Resource inputMotionVectors() { + return inputMotionVectors; + } + + public Resource inputExposure() { + return inputExposure; + } + + public Resource outputColor() { + return outputColor; + } + + @Override + public void close() { + if (nativePtr != 0) { + D3D12InteropNative.Nd3d12DestroyContext(nativePtr); + nativePtr = 0; + } + } + + public record Resource( + int index, + long nativeResource, + long sharedHandle, + long allocationSize, + TextureDescription textureDescription, + SRSurfaceFormat srFormat) { + } +} diff --git a/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropNative.java b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropNative.java new file mode 100644 index 00000000..cbccd6a8 --- /dev/null +++ b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropNative.java @@ -0,0 +1,53 @@ +/* + * Super Resolution + * Copyright (c) 2026. 187J3X1-114514 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +package io.homo.superresolution.core.graphics.d3d12; + +final class D3D12InteropNative { + static final int RESOURCE_INPUT_COLOR = 0; + static final int RESOURCE_INPUT_DEPTH = 1; + static final int RESOURCE_INPUT_MOTION_VECTORS = 2; + static final int RESOURCE_INPUT_EXPOSURE = 3; + static final int RESOURCE_OUTPUT_COLOR = 4; + + private D3D12InteropNative() { + } + + static native long Nd3d12CreateContext( + long adapterLuid, + int renderWidth, + int renderHeight, + int outputWidth, + int outputHeight, + int colorFormat + ); + + static native void Nd3d12DestroyContext(long context); + + static native long Nd3d12GetDevice(long context); + + static native long Nd3d12GetCommandList(long context); + + static native long Nd3d12GetResource(long context, int index); + + static native long Nd3d12GetResourceSharedHandle(long context, int index); + + static native long Nd3d12GetResourceAllocationSize(long context, int index); + + static native long Nd3d12GetFenceSharedHandle(long context); + + static native int Nd3d12BeginFrame(long context, long waitFenceValue); + + static native int Nd3d12ExecuteFrame(long context, long signalFenceValue); + + static native int Nd3d12WaitIdle(long context); + + static native String Nd3d12GetLastError(); +} diff --git a/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropSemaphore.java b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropSemaphore.java new file mode 100644 index 00000000..13ce1399 --- /dev/null +++ b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropSemaphore.java @@ -0,0 +1,98 @@ +/* + * Super Resolution + * Copyright (c) 2026. 187J3X1-114514 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +package io.homo.superresolution.core.graphics.d3d12; + +import static org.lwjgl.opengl.EXTSemaphore.*; +import static org.lwjgl.opengl.EXTSemaphoreWin32.GL_D3D12_FENCE_VALUE_EXT; +import static org.lwjgl.opengl.EXTSemaphoreWin32.GL_HANDLE_TYPE_D3D12_FENCE_EXT; +import static org.lwjgl.opengl.EXTSemaphoreWin32.glImportSemaphoreWin32HandleEXT; +import static org.lwjgl.opengl.GL11.GL_NO_ERROR; +import static org.lwjgl.opengl.GL11.glGetError; + +/** + * OpenGL semaphore imported from the shared ID3D12Fence owned by a + * {@link D3D12InteropContext}. + */ +public final class D3D12InteropSemaphore implements AutoCloseable { + private int semaphore; + + public D3D12InteropSemaphore(long sharedFenceHandle) { + if (sharedFenceHandle == 0) { + throw new IllegalArgumentException("The D3D12 fence handle is null."); + } + clearGlErrors(); + try { + semaphore = glGenSemaphoresEXT(); + glImportSemaphoreWin32HandleEXT( + semaphore, + GL_HANDLE_TYPE_D3D12_FENCE_EXT, + sharedFenceHandle); + int error = glGetError(); + if (error != GL_NO_ERROR) { + throw new IllegalStateException( + "Could not import the D3D12 fence into OpenGL " + + "(error 0x" + Integer.toHexString(error) + ")."); + } + } catch (Throwable throwable) { + close(); + throw throwable; + } + } + + public void signal(long fenceValue, int[] textures, int[] layouts) { + validate(textures, layouts); + glSemaphoreParameterui64EXT( + semaphore, + GL_D3D12_FENCE_VALUE_EXT, + fenceValue); + glSignalSemaphoreEXT( + semaphore, + new int[0], + textures, + layouts); + } + + public void waitFor(long fenceValue, int[] textures, int[] layouts) { + validate(textures, layouts); + glSemaphoreParameterui64EXT( + semaphore, + GL_D3D12_FENCE_VALUE_EXT, + fenceValue); + glWaitSemaphoreEXT( + semaphore, + new int[0], + textures, + layouts); + } + + private static void validate(int[] textures, int[] layouts) { + if (textures == null || layouts == null || + textures.length != layouts.length) { + throw new IllegalArgumentException( + "Texture and layout arrays must be non-null and have equal length."); + } + } + + private static void clearGlErrors() { + while (glGetError() != GL_NO_ERROR) { + // Discard errors left by unrelated work so the import check below + // reports only this operation. + } + } + + @Override + public void close() { + if (semaphore != 0) { + glDeleteSemaphoresEXT(semaphore); + semaphore = 0; + } + } +} diff --git a/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/GlD3D12ImportableTexture2D.java b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/GlD3D12ImportableTexture2D.java new file mode 100644 index 00000000..e7dfd29c --- /dev/null +++ b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/GlD3D12ImportableTexture2D.java @@ -0,0 +1,98 @@ +/* + * Super Resolution + * Copyright (c) 2026. 187J3X1-114514 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +package io.homo.superresolution.core.graphics.d3d12; + +import io.homo.superresolution.core.graphics.opengl.GlState; +import io.homo.superresolution.core.graphics.opengl.texture.GlTexture2D; + +import static org.lwjgl.opengl.EXTMemoryObject.*; +import static org.lwjgl.opengl.EXTMemoryObjectWin32.GL_HANDLE_TYPE_D3D12_RESOURCE_EXT; +import static org.lwjgl.opengl.EXTMemoryObjectWin32.glImportMemoryWin32HandleEXT; +import static org.lwjgl.opengl.GL11.GL_NO_ERROR; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; +import static org.lwjgl.opengl.GL11.GL_TRUE; +import static org.lwjgl.opengl.GL11.glBindTexture; +import static org.lwjgl.opengl.GL11.glGetError; + +/** + * OpenGL texture view over a D3D12 shared committed resource. + */ +public final class GlD3D12ImportableTexture2D extends GlTexture2D { + private final D3D12InteropContext.Resource source; + private int memoryObject; + + public GlD3D12ImportableTexture2D(D3D12InteropContext.Resource source) { + super(source.textureDescription()); + this.source = source; + configureMipmap(); + try { + initializeTexture(); + } catch (Throwable throwable) { + destroy(); + throw throwable; + } + } + + @Override + protected void initializeTexture() { + try (GlState ignored = new GlState( + GlState.STATE_TEXTURE | + GlState.STATE_ACTIVE_TEXTURE | + GlState.STATE_TEXTURES)) { + clearGlErrors(); + configureTextureParameters(); + memoryObject = glCreateMemoryObjectsEXT(); + glMemoryObjectParameteriEXT( + memoryObject, + GL_DEDICATED_MEMORY_OBJECT_EXT, + GL_TRUE); + glImportMemoryWin32HandleEXT( + memoryObject, + source.allocationSize(), + GL_HANDLE_TYPE_D3D12_RESOURCE_EXT, + source.sharedHandle()); + + glBindTexture(GL_TEXTURE_2D, Math.toIntExact(handle())); + glTextureStorageMem2DEXT( + Math.toIntExact(handle()), + 1, + source.textureDescription().getFormat().gl(), + source.textureDescription().getWidth(), + source.textureDescription().getHeight(), + memoryObject, + 0); + int error = glGetError(); + if (error != GL_NO_ERROR) { + throw new IllegalStateException( + "Could not import D3D12 resource " + source.index() + + " into OpenGL (error 0x" + + Integer.toHexString(error) + ")."); + } + updateDebugLabel(source.textureDescription().getLabel()); + } + } + + private static void clearGlErrors() { + while (glGetError() != GL_NO_ERROR) { + // Discard errors left by unrelated work so the import check below + // reports only this operation. + } + } + + @Override + public void destroy() { + super.destroy(); + if (memoryObject != 0) { + glDeleteMemoryObjectsEXT(memoryObject); + memoryObject = 0; + } + } +} diff --git a/native/cpp/SRNativeMain/CMakeLists.txt b/native/cpp/SRNativeMain/CMakeLists.txt index 41829cad..be34061a 100644 --- a/native/cpp/SRNativeMain/CMakeLists.txt +++ b/native/cpp/SRNativeMain/CMakeLists.txt @@ -76,6 +76,9 @@ target_link_libraries(SR_MAIN_LIB SPIRV freetype ) +if(WIN32) + target_link_libraries(SR_MAIN_LIB d3d12 dxgi) +endif() if(NOT WIN32) elseif(CMAKE_SYSTEM_NAME MATCHES "Linux") target_link_libraries(SR_MAIN_LIB -lstdc++fs -pthread -ldl) diff --git a/native/cpp/SRNativeMain/src/d3d12_interop.cpp b/native/cpp/SRNativeMain/src/d3d12_interop.cpp new file mode 100644 index 00000000..9273e672 --- /dev/null +++ b/native/cpp/SRNativeMain/src/d3d12_interop.cpp @@ -0,0 +1,584 @@ +#include + +#if defined(ON_WIN64) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +using Microsoft::WRL::ComPtr; + +namespace { + constexpr uint32_t RESOURCE_COUNT = 5; + constexpr uint32_t RESOURCE_INPUT_COLOR = 0; + constexpr uint32_t RESOURCE_INPUT_DEPTH = 1; + constexpr uint32_t RESOURCE_INPUT_MOTION_VECTORS = 2; + constexpr uint32_t RESOURCE_INPUT_EXPOSURE = 3; + constexpr uint32_t RESOURCE_OUTPUT_COLOR = 4; + + // Values mirror SRSurfaceFormat / FfxApiSurfaceFormat. + constexpr int SR_FORMAT_R16G16B16A16_FLOAT = 4; + constexpr int SR_FORMAT_R8G8B8A8_UNORM = 10; + constexpr int SR_FORMAT_R11G11B10_FLOAT = 16; + constexpr int SR_FORMAT_R16G16_FLOAT = 18; + constexpr int SR_FORMAT_R16_FLOAT = 21; + constexpr int SR_FORMAT_R32_FLOAT = 28; + + struct SharedTexture { + ComPtr resource; + HANDLE sharedHandle = nullptr; + uint64_t allocationSize = 0; + }; + + struct D3D12InteropContext { + ComPtr adapter; + ComPtr device; + ComPtr queue; + ComPtr commandAllocator; + ComPtr commandList; + ComPtr fence; + HANDLE fenceSharedHandle = nullptr; + HANDLE fenceEvent = nullptr; + uint64_t lastSubmittedFenceValue = 0; + bool recording = false; + std::array resources; + }; + + thread_local std::string g_lastError; + + void setError(const char *message) { + g_lastError = message ? message : "Unknown D3D12 interop error."; + } + + void setHresultError(const char *operation, HRESULT hr) { + char buffer[256] = {}; + std::snprintf( + buffer, + sizeof(buffer), + "%s failed with HRESULT 0x%08lX.", + operation, + static_cast(hr)); + setError(buffer); + } + + bool sameLuid(const LUID &left, uint64_t right) { + const uint64_t leftValue = + static_cast(left.LowPart) | + (static_cast(static_cast(left.HighPart)) << 32); + return leftValue == right; + } + + DXGI_FORMAT toDxgiFormat(int format) { + switch (format) { + case SR_FORMAT_R16G16B16A16_FLOAT: + return DXGI_FORMAT_R16G16B16A16_FLOAT; + case SR_FORMAT_R8G8B8A8_UNORM: + return DXGI_FORMAT_R8G8B8A8_UNORM; + case SR_FORMAT_R11G11B10_FLOAT: + return DXGI_FORMAT_R11G11B10_FLOAT; + case SR_FORMAT_R16G16_FLOAT: + return DXGI_FORMAT_R16G16_FLOAT; + case SR_FORMAT_R16_FLOAT: + return DXGI_FORMAT_R16_FLOAT; + case SR_FORMAT_R32_FLOAT: + return DXGI_FORMAT_R32_FLOAT; + default: + return DXGI_FORMAT_UNKNOWN; + } + } + + bool createSharedTexture( + D3D12InteropContext *context, + uint32_t index, + uint32_t width, + uint32_t height, + DXGI_FORMAT format) { + if (!context || index >= RESOURCE_COUNT || width == 0 || height == 0 || + format == DXGI_FORMAT_UNKNOWN) { + setError("Invalid shared D3D12 texture description."); + return false; + } + + D3D12_HEAP_PROPERTIES heapProperties = {}; + heapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; + heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapProperties.CreationNodeMask = 1; + heapProperties.VisibleNodeMask = 1; + + D3D12_RESOURCE_DESC resourceDesc = {}; + resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + resourceDesc.Width = width; + resourceDesc.Height = height; + resourceDesc.DepthOrArraySize = 1; + resourceDesc.MipLevels = 1; + resourceDesc.Format = format; + resourceDesc.SampleDesc.Count = 1; + resourceDesc.SampleDesc.Quality = 0; + resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + + SharedTexture &texture = context->resources[index]; + HRESULT hr = context->device->CreateCommittedResource( + &heapProperties, + D3D12_HEAP_FLAG_SHARED, + &resourceDesc, + D3D12_RESOURCE_STATE_COMMON, + nullptr, + IID_PPV_ARGS(&texture.resource)); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateCommittedResource", hr); + return false; + } + + const D3D12_RESOURCE_ALLOCATION_INFO allocationInfo = + context->device->GetResourceAllocationInfo(0, 1, &resourceDesc); + if (allocationInfo.SizeInBytes == 0 || + allocationInfo.SizeInBytes == UINT64_MAX) { + setError("D3D12 returned an invalid shared resource allocation size."); + return false; + } + texture.allocationSize = allocationInfo.SizeInBytes; + + hr = context->device->CreateSharedHandle( + texture.resource.Get(), + nullptr, + GENERIC_ALL, + nullptr, + &texture.sharedHandle); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateSharedHandle(resource)", hr); + return false; + } + return true; + } + + bool waitForFence(D3D12InteropContext *context, uint64_t value) { + if (!context || value == 0 || + context->fence->GetCompletedValue() >= value) { + return true; + } + const HRESULT hr = context->fence->SetEventOnCompletion( + value, + context->fenceEvent); + if (FAILED(hr)) { + setHresultError("ID3D12Fence::SetEventOnCompletion", hr); + return false; + } + if (WaitForSingleObject(context->fenceEvent, INFINITE) != WAIT_OBJECT_0) { + setError("Waiting for the D3D12 interop fence failed."); + return false; + } + return true; + } + + void closeSharedHandles(D3D12InteropContext *context) { + if (!context) { + return; + } + for (SharedTexture &texture : context->resources) { + if (texture.sharedHandle) { + CloseHandle(texture.sharedHandle); + texture.sharedHandle = nullptr; + } + } + if (context->fenceSharedHandle) { + CloseHandle(context->fenceSharedHandle); + context->fenceSharedHandle = nullptr; + } + if (context->fenceEvent) { + CloseHandle(context->fenceEvent); + context->fenceEvent = nullptr; + } + } + + D3D12InteropContext *fromHandle(jlong handle) { + return reinterpret_cast( + static_cast(handle)); + } + + SharedTexture *getResource(D3D12InteropContext *context, jint index) { + if (!context || index < 0 || + static_cast(index) >= RESOURCE_COUNT) { + setError("Invalid D3D12 interop resource index."); + return nullptr; + } + return &context->resources[static_cast(index)]; + } +} + +extern "C" { + JNIEXPORT jlong JNICALL + Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12CreateContext( + JNIEnv *, + jclass, + jlong adapterLuid, + jint renderWidth, + jint renderHeight, + jint outputWidth, + jint outputHeight, + jint colorFormat) { + g_lastError.clear(); + if (adapterLuid == 0 || renderWidth <= 0 || renderHeight <= 0 || + outputWidth <= 0 || outputHeight <= 0) { + setError("Invalid D3D12 interop context dimensions or adapter LUID."); + return 0; + } + + const DXGI_FORMAT dxgiColorFormat = toDxgiFormat(colorFormat); + if (dxgiColorFormat == DXGI_FORMAT_UNKNOWN) { + setError("The configured internal color format is not supported by D3D12 interop."); + return 0; + } + + auto *context = new (std::nothrow) D3D12InteropContext(); + if (!context) { + setError("Could not allocate the D3D12 interop context."); + return 0; + } + + ComPtr factory; + HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory)); + if (FAILED(hr)) { + setHresultError("CreateDXGIFactory1", hr); + delete context; + return 0; + } + + for (UINT index = 0;; ++index) { + ComPtr candidate; + if (factory->EnumAdapters1(index, &candidate) == DXGI_ERROR_NOT_FOUND) { + break; + } + DXGI_ADAPTER_DESC1 desc = {}; + if (SUCCEEDED(candidate->GetDesc1(&desc)) && + (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) == 0 && + sameLuid(desc.AdapterLuid, static_cast(adapterLuid))) { + context->adapter = candidate; + break; + } + } + if (!context->adapter) { + setError("No D3D12 adapter matches the OpenGL device LUID."); + delete context; + return 0; + } + + hr = D3D12CreateDevice( + context->adapter.Get(), + D3D_FEATURE_LEVEL_12_0, + IID_PPV_ARGS(&context->device)); + if (FAILED(hr)) { + setHresultError("D3D12CreateDevice", hr); + delete context; + return 0; + } + + D3D12_COMMAND_QUEUE_DESC queueDesc = {}; + queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + queueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL; + queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + hr = context->device->CreateCommandQueue( + &queueDesc, + IID_PPV_ARGS(&context->queue)); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateCommandQueue", hr); + delete context; + return 0; + } + + hr = context->device->CreateCommandAllocator( + D3D12_COMMAND_LIST_TYPE_DIRECT, + IID_PPV_ARGS(&context->commandAllocator)); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateCommandAllocator", hr); + delete context; + return 0; + } + + hr = context->device->CreateCommandList( + 0, + D3D12_COMMAND_LIST_TYPE_DIRECT, + context->commandAllocator.Get(), + nullptr, + IID_PPV_ARGS(&context->commandList)); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateCommandList", hr); + delete context; + return 0; + } + hr = context->commandList->Close(); + if (FAILED(hr)) { + setHresultError("ID3D12GraphicsCommandList::Close(initial)", hr); + delete context; + return 0; + } + + hr = context->device->CreateFence( + 0, + D3D12_FENCE_FLAG_SHARED, + IID_PPV_ARGS(&context->fence)); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateFence", hr); + delete context; + return 0; + } + context->fenceEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if (!context->fenceEvent) { + setError("CreateEventW failed for the D3D12 interop fence."); + delete context; + return 0; + } + hr = context->device->CreateSharedHandle( + context->fence.Get(), + nullptr, + GENERIC_ALL, + nullptr, + &context->fenceSharedHandle); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateSharedHandle(fence)", hr); + closeSharedHandles(context); + delete context; + return 0; + } + + const bool resourcesCreated = + createSharedTexture( + context, + RESOURCE_INPUT_COLOR, + static_cast(renderWidth), + static_cast(renderHeight), + dxgiColorFormat) && + createSharedTexture( + context, + RESOURCE_INPUT_DEPTH, + static_cast(renderWidth), + static_cast(renderHeight), + DXGI_FORMAT_R32_FLOAT) && + createSharedTexture( + context, + RESOURCE_INPUT_MOTION_VECTORS, + static_cast(renderWidth), + static_cast(renderHeight), + DXGI_FORMAT_R16G16_FLOAT) && + createSharedTexture( + context, + RESOURCE_INPUT_EXPOSURE, + 1, + 1, + DXGI_FORMAT_R16_FLOAT) && + createSharedTexture( + context, + RESOURCE_OUTPUT_COLOR, + static_cast(outputWidth), + static_cast(outputHeight), + dxgiColorFormat); + if (!resourcesCreated) { + closeSharedHandles(context); + delete context; + return 0; + } + + return static_cast( + reinterpret_cast(context)); + } + + JNIEXPORT void JNICALL + Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12DestroyContext( + JNIEnv *, + jclass, + jlong contextHandle) { + D3D12InteropContext *context = fromHandle(contextHandle); + if (!context) { + return; + } + waitForFence(context, context->lastSubmittedFenceValue); + closeSharedHandles(context); + delete context; + } + + JNIEXPORT jlong JNICALL + Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetDevice( + JNIEnv *, + jclass, + jlong contextHandle) { + D3D12InteropContext *context = fromHandle(contextHandle); + return context + ? static_cast( + reinterpret_cast(context->device.Get())) + : 0; + } + + JNIEXPORT jlong JNICALL + Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetCommandList( + JNIEnv *, + jclass, + jlong contextHandle) { + D3D12InteropContext *context = fromHandle(contextHandle); + return context + ? static_cast( + reinterpret_cast(context->commandList.Get())) + : 0; + } + + JNIEXPORT jlong JNICALL + Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetResource( + JNIEnv *, + jclass, + jlong contextHandle, + jint index) { + SharedTexture *texture = getResource(fromHandle(contextHandle), index); + return texture + ? static_cast( + reinterpret_cast(texture->resource.Get())) + : 0; + } + + JNIEXPORT jlong JNICALL + Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetResourceSharedHandle( + JNIEnv *, + jclass, + jlong contextHandle, + jint index) { + SharedTexture *texture = getResource(fromHandle(contextHandle), index); + return texture + ? static_cast( + reinterpret_cast(texture->sharedHandle)) + : 0; + } + + JNIEXPORT jlong JNICALL + Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetResourceAllocationSize( + JNIEnv *, + jclass, + jlong contextHandle, + jint index) { + SharedTexture *texture = getResource(fromHandle(contextHandle), index); + return texture + ? static_cast(texture->allocationSize) + : 0; + } + + JNIEXPORT jlong JNICALL + Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetFenceSharedHandle( + JNIEnv *, + jclass, + jlong contextHandle) { + D3D12InteropContext *context = fromHandle(contextHandle); + return context + ? static_cast( + reinterpret_cast(context->fenceSharedHandle)) + : 0; + } + + JNIEXPORT jint JNICALL + Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12BeginFrame( + JNIEnv *, + jclass, + jlong contextHandle, + jlong waitFenceValue) { + D3D12InteropContext *context = fromHandle(contextHandle); + if (!context || waitFenceValue <= 0) { + setError("Invalid D3D12 begin-frame arguments."); + return E_INVALIDARG; + } + if (context->recording) { + setError("The D3D12 command list is already recording."); + return E_FAIL; + } + if (!waitForFence(context, context->lastSubmittedFenceValue)) { + return E_FAIL; + } + + HRESULT hr = context->commandAllocator->Reset(); + if (FAILED(hr)) { + setHresultError("ID3D12CommandAllocator::Reset", hr); + return static_cast(hr); + } + hr = context->commandList->Reset( + context->commandAllocator.Get(), + nullptr); + if (FAILED(hr)) { + setHresultError("ID3D12GraphicsCommandList::Reset", hr); + return static_cast(hr); + } + hr = context->queue->Wait( + context->fence.Get(), + static_cast(waitFenceValue)); + if (FAILED(hr)) { + setHresultError("ID3D12CommandQueue::Wait", hr); + context->commandList->Close(); + return static_cast(hr); + } + context->recording = true; + return S_OK; + } + + JNIEXPORT jint JNICALL + Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12ExecuteFrame( + JNIEnv *, + jclass, + jlong contextHandle, + jlong signalFenceValue) { + D3D12InteropContext *context = fromHandle(contextHandle); + if (!context || !context->recording || signalFenceValue <= 0) { + setError("Invalid D3D12 execute-frame state or fence value."); + return E_INVALIDARG; + } + + HRESULT hr = context->commandList->Close(); + context->recording = false; + if (FAILED(hr)) { + setHresultError("ID3D12GraphicsCommandList::Close", hr); + return static_cast(hr); + } + + ID3D12CommandList *commandLists[] = {context->commandList.Get()}; + context->queue->ExecuteCommandLists(1, commandLists); + hr = context->queue->Signal( + context->fence.Get(), + static_cast(signalFenceValue)); + if (FAILED(hr)) { + setHresultError("ID3D12CommandQueue::Signal", hr); + return static_cast(hr); + } + context->lastSubmittedFenceValue = + static_cast(signalFenceValue); + return S_OK; + } + + JNIEXPORT jint JNICALL + Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12WaitIdle( + JNIEnv *, + jclass, + jlong contextHandle) { + D3D12InteropContext *context = fromHandle(contextHandle); + if (!context) { + setError("The D3D12 interop context is null."); + return E_INVALIDARG; + } + return waitForFence(context, context->lastSubmittedFenceValue) + ? S_OK + : E_FAIL; + } + + JNIEXPORT jstring JNICALL + Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetLastError( + JNIEnv *env, + jclass) { + return env->NewStringUTF(g_lastError.c_str()); + } +} + +#endif diff --git a/native/cpp/docs/ffx_api_d3d12_prototype.md b/native/cpp/docs/ffx_api_d3d12_prototype.md index 596db783..24cc38f6 100644 --- a/native/cpp/docs/ffx_api_d3d12_prototype.md +++ b/native/cpp/docs/ffx_api_d3d12_prototype.md @@ -41,21 +41,36 @@ The Java/JNI layer mirrors those values with `long` native addresses. Raw D3D12 resources can be created with the `SRTextureResource(long, description, states)` constructor. -## Prototype boundary - -This change makes SRAPI and its FSR provider D3D12-capable, but Minecraft still -renders through OpenGL or the project's Vulkan path. A complete in-game FSR -4.1 implementation additionally needs a Windows graphics interop layer that: +## Renderer interop + +`D3D12InteropAlgorithm` implements the renderer-facing half as a sibling to +`VulkanInteropAlgorithm`. The initial implementation deliberately uses a +single serial resource set: + +1. query OpenGL's `GL_DEVICE_LUID_EXT` and create D3D12 on the matching DXGI + adapter; +2. create five D3D12-owned shared committed textures for color, depth, motion + vectors, exposure, and output; +3. import the resource handles into OpenGL with + `GL_EXT_memory_object_win32`; +4. import a shared D3D12 timeline fence with `GL_EXT_semaphore_win32`; +5. preprocess the Minecraft inputs in OpenGL, signal ownership to D3D12, + dispatch FFX API, signal ownership back to OpenGL, and flip the output into + the normal renderer texture. + +`FfxFSR4D3D12` supplies the FSR-specific context and dispatch descriptions. +The algorithm is registered as `fsr4_d3d12` on Windows when the required +OpenGL extensions are available. The signed DLL remains an explicit external +resource and must be selected by the user. -- creates a D3D12 device on the same physical adapter; -- shares the color, depth, motion-vector, exposure, and output resources with - the renderer; -- translates resource layouts/states correctly; and -- synchronizes OpenGL/Vulkan work with the D3D12 command queue and fences. +## Prototype boundary -That interop work belongs above SRAPI and should be implemented as a sibling to -the existing `VulkanInteropAlgorithm`; it is intentionally not hidden inside -the FFX provider. +The resource import, fence round trip, and FFX command recording/execution have +been validated in standalone smoke tests on an AMD Radeon RX 7900 XT. The +remaining work is integration testing inside Minecraft, including validation +of motion-vector/depth conventions and rendered image quality. A future +high-performance mode can add multiple in-flight resource sets after the +serial path is proven in game. ## Provider lifecycle From cd64b3ff5c07f691c0d61f2ad67330f1be55d5ac Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Mon, 27 Jul 2026 13:57:28 +0800 Subject: [PATCH 03/19] add D3D12-only FFX provider build mode --- native/cpp/SRNativeFSR/CMakeLists.txt | 49 ++++++++++++++----- .../SRNativeFSR/include/sr/fsr/sr_provider.h | 5 +- native/cpp/SRNativeFSR/src/sr_provider.cpp | 15 +++++- native/cpp/docs/ffx_api_d3d12_prototype.md | 17 +++++++ 4 files changed, 70 insertions(+), 16 deletions(-) diff --git a/native/cpp/SRNativeFSR/CMakeLists.txt b/native/cpp/SRNativeFSR/CMakeLists.txt index 5893c1dd..95ad83ad 100644 --- a/native/cpp/SRNativeFSR/CMakeLists.txt +++ b/native/cpp/SRNativeFSR/CMakeLists.txt @@ -6,7 +6,18 @@ if (NOT (ON_LINUX OR ON_WINDOWS)) message(FATAL_ERROR "${LIB_PLATFORM} 平台不支持FSR") endif() -find_package(Vulkan REQUIRED) +option( + SR_FSR_FFX_API_ONLY + "Only build the signed FFX API D3D12 provider (Windows only)" + OFF +) +if(SR_FSR_FFX_API_ONLY AND NOT ON_WINDOWS) + message(FATAL_ERROR "SR_FSR_FFX_API_ONLY is only supported on Windows") +endif() + +if(NOT SR_FSR_FFX_API_ONLY) + find_package(Vulkan REQUIRED) +endif() # ============================================================ # FidelityFX SDK: 从子模块源码构建 / 使用预编译库 @@ -19,7 +30,9 @@ option(SR_FSR_BUILD_SDK "从源码编译 FidelityFX SDK(需要 SDK 子模块 # SDK 编译产物输出目录(同时也是预编译库查找目录) set(FFX_SDK_LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/${LIB_PLATFORM}") -if(SR_FSR_BUILD_SDK) +if(SR_FSR_FFX_API_ONLY) + message(STATUS "[FSR] 仅构建签名 FFX API D3D12 provider") +elseif(SR_FSR_BUILD_SDK) # --- 检查子模块是否已初始化 --- if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/SDK/CMakeLists.txt") message(FATAL_ERROR @@ -156,13 +169,23 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/src ) -aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src ALL_SRC) +if(SR_FSR_FFX_API_ONLY) + set(ALL_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/src/ffx_api_upscale.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/sr_provider.cpp + ) +else() + aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src ALL_SRC) +endif() add_library(SR_FSR_LIB SHARED ${ALL_SRC}) target_compile_definitions(SR_FSR_LIB PRIVATE ${_SR_FSR_PLATFORM_DEFS} SRLIB_VERSION="${SRLIB_VERSION}" ) +if(SR_FSR_FFX_API_ONLY) + target_compile_definitions(SR_FSR_LIB PRIVATE SR_FSR_FFX_API_ONLY) +endif() if(CMAKE_SYSTEM_NAME MATCHES "Linux") set_target_properties(SR_FSR_LIB PROPERTIES OUTPUT_NAME "SuperResolutionFSR+${LIB_PLATFORM}+${SR_BUILD_TYPE}") @@ -170,11 +193,15 @@ elseif(CMAKE_SYSTEM_NAME MATCHES "Windows") set_target_properties(SR_FSR_LIB PROPERTIES OUTPUT_NAME "libSuperResolutionFSR+${LIB_PLATFORM}+${SR_BUILD_TYPE}") endif() -target_link_libraries(SR_FSR_LIB - SR_MAIN_LIB - ${FFX_LINK_BACKEND} - ${FFX_LINK_FSR2} - ${FFX_LINK_FSR3UP} - ${FFX_LINK_FSR3} - ${Vulkan_LIBRARIES} -) \ No newline at end of file +if(SR_FSR_FFX_API_ONLY) + target_link_libraries(SR_FSR_LIB SR_MAIN_LIB) +else() + target_link_libraries(SR_FSR_LIB + SR_MAIN_LIB + ${FFX_LINK_BACKEND} + ${FFX_LINK_FSR2} + ${FFX_LINK_FSR3UP} + ${FFX_LINK_FSR3} + ${Vulkan_LIBRARIES} + ) +endif() diff --git a/native/cpp/SRNativeFSR/include/sr/fsr/sr_provider.h b/native/cpp/SRNativeFSR/include/sr/fsr/sr_provider.h index 86e014db..06ccb10b 100644 --- a/native/cpp/SRNativeFSR/include/sr/fsr/sr_provider.h +++ b/native/cpp/SRNativeFSR/include/sr/fsr/sr_provider.h @@ -1,11 +1,8 @@ #pragma once -#include #include "sr/sr_api.h" #include "sr/sr_modules.h" -#include "fsr2.h" -#include "fsr3.h" extern "C" { SR_API SRReturnCode srGetFfxFSRUpscaleProviders(SRUpscaleProvider * outProvider); SR_API SRReturnCode srGetFfxFSRUpscaleProvidersCount(uint32_t * outCount); -} \ No newline at end of file +} diff --git a/native/cpp/SRNativeFSR/src/sr_provider.cpp b/native/cpp/SRNativeFSR/src/sr_provider.cpp index bcdbb162..dc109fab 100644 --- a/native/cpp/SRNativeFSR/src/sr_provider.cpp +++ b/native/cpp/SRNativeFSR/src/sr_provider.cpp @@ -2,8 +2,14 @@ #if defined(ON_WIN64) #include "sr/fsr/ffx_api_upscale.h" #endif +#if !defined(SR_FSR_FFX_API_ONLY) +#include "sr/fsr/fsr2.h" +#include "sr/fsr/fsr3.h" +#endif -#if defined(ON_WIN64) +#if defined(SR_FSR_FFX_API_ONLY) +static constexpr uint32_t PROVIDER_COUNT = 1; +#elif defined(ON_WIN64) static constexpr uint32_t PROVIDER_COUNT = 3; #else static constexpr uint32_t PROVIDER_COUNT = 2; @@ -14,6 +20,10 @@ static bool g_initialized = false; static void ensureInitialized() { if (!g_initialized) { + #if defined(SR_FSR_FFX_API_ONLY) + g_providers[0].providerId = SR_MODULES_FFX_API_UPSCALE_ID; + g_providers[0].callbacks = srGetFfxApiUpscaleCallbacks(); + #else g_providers[0].providerId = SR_MODULES_FSR2_ID; g_providers[0].callbacks = srGetFfxFSR2UpscaleCallbacks(); @@ -24,6 +34,7 @@ static void ensureInitialized() { g_providers[2].providerId = SR_MODULES_FFX_API_UPSCALE_ID; g_providers[2].callbacks = srGetFfxApiUpscaleCallbacks(); #endif + #endif g_initialized = true; } } @@ -32,10 +43,12 @@ extern "C" { SR_API SRReturnCode srGetFfxFSRUpscaleProviders(SRUpscaleProvider *outProvider) { ensureInitialized(); outProvider[0] = g_providers[0]; + #if !defined(SR_FSR_FFX_API_ONLY) outProvider[1] = g_providers[1]; #if defined(ON_WIN64) outProvider[2] = g_providers[2]; #endif + #endif return (SRReturnCode) SR_RETURN_CODE_OK; } diff --git a/native/cpp/docs/ffx_api_d3d12_prototype.md b/native/cpp/docs/ffx_api_d3d12_prototype.md index 24cc38f6..841a1c75 100644 --- a/native/cpp/docs/ffx_api_d3d12_prototype.md +++ b/native/cpp/docs/ffx_api_d3d12_prototype.md @@ -25,6 +25,23 @@ official AMD FSR SDK release and pass its absolute path through the provider looks for `amd_fidelityfx_upscaler_dx12.dll` in the process' secure DLL search directories. +For a D3D12-only development build on Windows, configure CMake with +`-DSR_FSR_FFX_API_ONLY=ON`. This builds the signed FFX API provider without +the legacy Vulkan providers or their Vulkan SDK/build-time dependencies. The +option is disabled by default, so normal release builds retain the complete +FSR 2/3 provider set. + +```powershell +cmake -S . -B buildWindowsD3D12Dev ` + -DSR_FSR=ON ` + -DSR_FSR_FFX_API_ONLY=ON ` + -DSR_XESS=OFF ` + -DSR_NGX=OFF ` + -DSR_STREAMLINE=OFF ` + -DENABLE_OPT=OFF +cmake --build buildWindowsD3D12Dev --config Debug --target SR_FSR_LIB +``` + ## D3D12 SRAPI handles The cross-platform SRAPI ABI does not include `d3d12.h`. It carries: From 6a3213caf10f728599c316cca6d3fed8b9f0ec15 Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Mon, 27 Jul 2026 14:51:44 +0800 Subject: [PATCH 04/19] fix FSR 4 dispatch without exposure input --- .../common/upscale/ffxfsr/FfxFSR4D3D12.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java b/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java index 862e905a..b617f619 100644 --- a/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java +++ b/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java @@ -77,10 +77,9 @@ protected void onD3D12InteropCreated(InitializationDescription desc) { } EnumSet flags = - EnumSet.of(SRUpscaleContextCreateFlags.ENABLE_DEBUG); - if (desc.isAutoExposure()) { - flags.add(SRUpscaleContextCreateFlags.ENABLE_AUTO_EXPOSURE); - } + EnumSet.of( + SRUpscaleContextCreateFlags.ENABLE_DEBUG, + SRUpscaleContextCreateFlags.ENABLE_AUTO_EXPOSURE); if (desc.isHdrInput()) { flags.add(SRUpscaleContextCreateFlags.ENABLE_HDR); } @@ -169,12 +168,9 @@ protected boolean dispatchD3D12Upscale( desc.setMotionVectors(resource( d3d12Interop.inputMotionVectors(), SRResourceStates.COMPUTE_READ)); - if (!initDesc.isAutoExposure() && - dispatchResource.resources().exposureTexture() != null) { - desc.setExposure(resource( - d3d12Interop.inputExposure(), - SRResourceStates.COMPUTE_READ)); - } + // Minecraft does not currently provide a valid exposure texture. + // The context therefore always uses FFX auto exposure rather than + // binding the uninitialized 1x1 interop exposure resource. desc.setOutput(resource( d3d12Interop.outputColor(), SRResourceStates.UNORDERED_ACCESS)); From 82ec67ca0c43c73fe126338fd5cf4e67c14d7100 Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Wed, 29 Jul 2026 11:53:45 +0800 Subject: [PATCH 05/19] fixed neoforge runClient warning of fabric mod --- forge/build.gradle.kts | 3 ++- neoforge/build.gradle.kts | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index f078cd27..bffedd33 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -146,7 +146,8 @@ dependencies { implementation(files(mergeVmaNatives.get().archiveFile)) add("jarJar", files(mergeVmaNatives.get().archiveFile)) //modImplementation("dev.architectury:architectury-forge:${versionConfig.common.architecturyApiVersion}") - implementation("net.fabricmc.fabric-api:fabric-api-base:0.4.39+80f8cf51bb") + // Sodium's Forge API exposes Fabric's Event type, but Fabric API is not a Forge runtime mod. + compileOnly("net.fabricmc.fabric-api:fabric-api-base:0.4.39+80f8cf51bb") val busDep = implementation("net.neoforged:bus:8.0.5") if (busDep != null) jarJar(busDep) diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index 77a0dad0..422ad7b5 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -157,7 +157,9 @@ dependencies { if (versionConfig.common.architecturyApiVersion != null) { implementation("dev.architectury:architectury-neoforge:${versionConfig.common.architecturyApiVersion}") } - implementation("net.fabricmc.fabric-api:fabric-api-base:0.4.64+9ec45cd8e8") + // Sodium's NeoForge API exposes Fabric's Event type, but this Fabric mod must not be + // placed directly on NeoForge's runtime classpath. Sodium provides it at runtime. + compileOnly("net.fabricmc.fabric-api:fabric-api-base:0.4.64+9ec45cd8e8") for (lib in versionConfig.neoforge.dependencies.modrinth) { var depName = "maven.modrinth:${lib.name}:${lib.version}-neoforge,${lib.minecraftVersion ?: versionConfig.common.minecraftVersion}" From 0555f83ae7b9bb6c669448010b802a4f848a7704 Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Wed, 29 Jul 2026 14:14:09 +0800 Subject: [PATCH 06/19] commit --- .../common/upscale/D3D12InteropAlgorithm.java | 13 +- .../common/upscale/ffxfsr/FfxFSR4D3D12.java | 2 +- gradlew.bat | 1 - native/cpp/SRNativeMain/src/d3d12_interop.cpp | 1120 +++++++++-------- 4 files changed, 596 insertions(+), 540 deletions(-) diff --git a/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java b/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java index f99ad57f..7da83e39 100644 --- a/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java +++ b/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java @@ -164,10 +164,10 @@ public boolean dispatch(DispatchResource dispatchResource) { d3d12DoneValue, sharedTextures, new int[]{ - GL_LAYOUT_GENERAL_EXT, - GL_LAYOUT_GENERAL_EXT, - GL_LAYOUT_GENERAL_EXT, - GL_LAYOUT_GENERAL_EXT, + GL_LAYOUT_SHADER_READ_ONLY_EXT, + GL_LAYOUT_SHADER_READ_ONLY_EXT, + GL_LAYOUT_SHADER_READ_ONLY_EXT, + GL_LAYOUT_SHADER_READ_ONLY_EXT, GL_LAYOUT_GENERAL_EXT }); } @@ -217,6 +217,11 @@ public void destroy() { private void destroyResources() { if (d3d12Interop != null) { + // OpenGL command buffers are submitted asynchronously and their + // waitForFence() implementation is a no-op. Drain the GL queue + // before deleting imported memory objects or releasing their + // owning D3D12 resources during resize. + RenderSystems.opengl().finish(); d3d12Interop.waitIdle(); } onBeforeD3D12InteropDestroyed(); diff --git a/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java b/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java index b617f619..2615f6b7 100644 --- a/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java +++ b/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java @@ -173,7 +173,7 @@ protected boolean dispatchD3D12Upscale( // binding the uninitialized 1x1 interop exposure resource. desc.setOutput(resource( d3d12Interop.outputColor(), - SRResourceStates.UNORDERED_ACCESS)); + SRResourceStates.COMMON)); desc.setJitterOffset(new Vector2f(dispatchResource.jitterOffset())); desc.setMotionVectorScale(new Vector2f( diff --git a/gradlew.bat b/gradlew.bat index 4a5c2f42..7084e9ef 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -74,7 +74,6 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* -pause :end @rem End local scope for the variables with windows NT shell if %ERRORLEVEL% equ 0 goto mainEnd diff --git a/native/cpp/SRNativeMain/src/d3d12_interop.cpp b/native/cpp/SRNativeMain/src/d3d12_interop.cpp index 9273e672..63f4e628 100644 --- a/native/cpp/SRNativeMain/src/d3d12_interop.cpp +++ b/native/cpp/SRNativeMain/src/d3d12_interop.cpp @@ -14,571 +14,623 @@ #include #include #include +#include #include #include using Microsoft::WRL::ComPtr; namespace { - constexpr uint32_t RESOURCE_COUNT = 5; - constexpr uint32_t RESOURCE_INPUT_COLOR = 0; - constexpr uint32_t RESOURCE_INPUT_DEPTH = 1; - constexpr uint32_t RESOURCE_INPUT_MOTION_VECTORS = 2; - constexpr uint32_t RESOURCE_INPUT_EXPOSURE = 3; - constexpr uint32_t RESOURCE_OUTPUT_COLOR = 4; - - // Values mirror SRSurfaceFormat / FfxApiSurfaceFormat. - constexpr int SR_FORMAT_R16G16B16A16_FLOAT = 4; - constexpr int SR_FORMAT_R8G8B8A8_UNORM = 10; - constexpr int SR_FORMAT_R11G11B10_FLOAT = 16; - constexpr int SR_FORMAT_R16G16_FLOAT = 18; - constexpr int SR_FORMAT_R16_FLOAT = 21; - constexpr int SR_FORMAT_R32_FLOAT = 28; - - struct SharedTexture { - ComPtr resource; - HANDLE sharedHandle = nullptr; - uint64_t allocationSize = 0; - }; - - struct D3D12InteropContext { - ComPtr adapter; - ComPtr device; - ComPtr queue; - ComPtr commandAllocator; - ComPtr commandList; - ComPtr fence; - HANDLE fenceSharedHandle = nullptr; - HANDLE fenceEvent = nullptr; - uint64_t lastSubmittedFenceValue = 0; - bool recording = false; - std::array resources; - }; - - thread_local std::string g_lastError; - - void setError(const char *message) { - g_lastError = message ? message : "Unknown D3D12 interop error."; - } - void setHresultError(const char *operation, HRESULT hr) { - char buffer[256] = {}; - std::snprintf( - buffer, - sizeof(buffer), - "%s failed with HRESULT 0x%08lX.", - operation, - static_cast(hr)); - setError(buffer); - } +constexpr uint32_t RESOURCE_COUNT = 5; +constexpr uint32_t RESOURCE_INPUT_COLOR = 0; +constexpr uint32_t RESOURCE_INPUT_DEPTH = 1; +constexpr uint32_t RESOURCE_INPUT_MOTION_VECTORS = 2; +constexpr uint32_t RESOURCE_INPUT_EXPOSURE = 3; +constexpr uint32_t RESOURCE_OUTPUT_COLOR = 4; + +// Values mirror SRSurfaceFormat / FfxApiSurfaceFormat. +constexpr int SR_FORMAT_R16G16B16A16_FLOAT = 4; +constexpr int SR_FORMAT_R8G8B8A8_UNORM = 10; +constexpr int SR_FORMAT_R11G11B10_FLOAT = 16; +constexpr int SR_FORMAT_R16G16_FLOAT = 18; +constexpr int SR_FORMAT_R16_FLOAT = 21; +constexpr int SR_FORMAT_R32_FLOAT = 28; + +struct SharedTexture { + ComPtr resource; + HANDLE sharedHandle = nullptr; + uint64_t allocationSize = 0; +}; + +struct D3D12InteropContext { + ComPtr adapter; + ComPtr device; + ComPtr queue; + ComPtr commandAllocator; + ComPtr commandList; + ComPtr fence; + HANDLE fenceSharedHandle = nullptr; + HANDLE fenceEvent = nullptr; + uint64_t lastSubmittedFenceValue = 0; + bool recording = false; + std::array resources; +}; + +thread_local std::string g_lastError; +std::mutex g_deviceMutex; +ComPtr g_sharedDevice; +uint64_t g_sharedDeviceLuid = 0; + +/** + * Records an error message in the thread-local error string, which can later + * be retrieved from Java via Nd3d12GetLastError. A null message is replaced + * with a generic fallback. + */ +void setError(const char *message) { + g_lastError = message ? message : "Unknown D3D12 interop error."; +} - bool sameLuid(const LUID &left, uint64_t right) { - const uint64_t leftValue = - static_cast(left.LowPart) | - (static_cast(static_cast(left.HighPart)) << 32); - return leftValue == right; - } +/** + * Formats " failed with HRESULT 0x........" and stores it as the + * thread-local last-error string (see setError). + */ +void setHresultError(const char *operation, HRESULT hr) { + char buffer[256] = {}; + std::snprintf(buffer, sizeof(buffer), "%s failed with HRESULT 0x%08lX.", + operation, static_cast(hr)); + setError(buffer); +} - DXGI_FORMAT toDxgiFormat(int format) { - switch (format) { - case SR_FORMAT_R16G16B16A16_FLOAT: - return DXGI_FORMAT_R16G16B16A16_FLOAT; - case SR_FORMAT_R8G8B8A8_UNORM: - return DXGI_FORMAT_R8G8B8A8_UNORM; - case SR_FORMAT_R11G11B10_FLOAT: - return DXGI_FORMAT_R11G11B10_FLOAT; - case SR_FORMAT_R16G16_FLOAT: - return DXGI_FORMAT_R16G16_FLOAT; - case SR_FORMAT_R16_FLOAT: - return DXGI_FORMAT_R16_FLOAT; - case SR_FORMAT_R32_FLOAT: - return DXGI_FORMAT_R32_FLOAT; - default: - return DXGI_FORMAT_UNKNOWN; - } - } +/** + * Compares a DXGI LUID against its packed 64-bit representation + * (LowPart in the low 32 bits, HighPart in the high 32 bits), which is how + * the adapter LUID is passed across the JNI boundary. + */ +bool sameLuid(const LUID &left, uint64_t right) { + const uint64_t leftValue = + static_cast(left.LowPart) | + (static_cast(static_cast(left.HighPart)) << 32); + return leftValue == right; +} - bool createSharedTexture( - D3D12InteropContext *context, - uint32_t index, - uint32_t width, - uint32_t height, - DXGI_FORMAT format) { - if (!context || index >= RESOURCE_COUNT || width == 0 || height == 0 || - format == DXGI_FORMAT_UNKNOWN) { - setError("Invalid shared D3D12 texture description."); - return false; - } - - D3D12_HEAP_PROPERTIES heapProperties = {}; - heapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; - heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; - heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; - heapProperties.CreationNodeMask = 1; - heapProperties.VisibleNodeMask = 1; - - D3D12_RESOURCE_DESC resourceDesc = {}; - resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; - resourceDesc.Width = width; - resourceDesc.Height = height; - resourceDesc.DepthOrArraySize = 1; - resourceDesc.MipLevels = 1; - resourceDesc.Format = format; - resourceDesc.SampleDesc.Count = 1; - resourceDesc.SampleDesc.Quality = 0; - resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; - resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; - - SharedTexture &texture = context->resources[index]; - HRESULT hr = context->device->CreateCommittedResource( - &heapProperties, - D3D12_HEAP_FLAG_SHARED, - &resourceDesc, - D3D12_RESOURCE_STATE_COMMON, - nullptr, - IID_PPV_ARGS(&texture.resource)); - if (FAILED(hr)) { - setHresultError("ID3D12Device::CreateCommittedResource", hr); - return false; - } - - const D3D12_RESOURCE_ALLOCATION_INFO allocationInfo = - context->device->GetResourceAllocationInfo(0, 1, &resourceDesc); - if (allocationInfo.SizeInBytes == 0 || - allocationInfo.SizeInBytes == UINT64_MAX) { - setError("D3D12 returned an invalid shared resource allocation size."); - return false; - } - texture.allocationSize = allocationInfo.SizeInBytes; - - hr = context->device->CreateSharedHandle( - texture.resource.Get(), - nullptr, - GENERIC_ALL, - nullptr, - &texture.sharedHandle); - if (FAILED(hr)) { - setHresultError("ID3D12Device::CreateSharedHandle(resource)", hr); - return false; - } - return true; - } +/** + * Maps an SRSurfaceFormat/FfxApiSurfaceFormat value (as passed from Java) to + * the matching DXGI_FORMAT. Returns DXGI_FORMAT_UNKNOWN for unsupported + * formats so callers can reject them. + */ +DXGI_FORMAT toDxgiFormat(int format) { + switch (format) { + case SR_FORMAT_R16G16B16A16_FLOAT: + return DXGI_FORMAT_R16G16B16A16_FLOAT; + case SR_FORMAT_R8G8B8A8_UNORM: + return DXGI_FORMAT_R8G8B8A8_UNORM; + case SR_FORMAT_R11G11B10_FLOAT: + return DXGI_FORMAT_R11G11B10_FLOAT; + case SR_FORMAT_R16G16_FLOAT: + return DXGI_FORMAT_R16G16_FLOAT; + case SR_FORMAT_R16_FLOAT: + return DXGI_FORMAT_R16_FLOAT; + case SR_FORMAT_R32_FLOAT: + return DXGI_FORMAT_R32_FLOAT; + default: + return DXGI_FORMAT_UNKNOWN; + } +} - bool waitForFence(D3D12InteropContext *context, uint64_t value) { - if (!context || value == 0 || - context->fence->GetCompletedValue() >= value) { - return true; - } - const HRESULT hr = context->fence->SetEventOnCompletion( - value, - context->fenceEvent); - if (FAILED(hr)) { - setHresultError("ID3D12Fence::SetEventOnCompletion", hr); - return false; - } - if (WaitForSingleObject(context->fenceEvent, INFINITE) != WAIT_OBJECT_0) { - setError("Waiting for the D3D12 interop fence failed."); - return false; - } - return true; - } +/** + * Creates one of the shared interop textures (index RESOURCE_INPUT_* / + * RESOURCE_OUTPUT_COLOR) as a committed UAV-capable Texture2D on the DEFAULT + * heap with D3D12_HEAP_FLAG_SHARED, records its driver allocation size, and + * exports an NT shared handle so the resource can be opened from OpenGL + * (e.g. via GL_EXT_memory_object). On failure the thread-local error string + * is set and false is returned. + */ +bool createSharedTexture(D3D12InteropContext *context, uint32_t index, + uint32_t width, uint32_t height, DXGI_FORMAT format) { + if (!context || index >= RESOURCE_COUNT || width == 0 || height == 0 || + format == DXGI_FORMAT_UNKNOWN) { + setError("Invalid shared D3D12 texture description."); + return false; + } + + D3D12_HEAP_PROPERTIES heapProperties = {}; + heapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; + heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapProperties.CreationNodeMask = 1; + heapProperties.VisibleNodeMask = 1; + + D3D12_RESOURCE_DESC resourceDesc = {}; + resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + resourceDesc.Width = width; + resourceDesc.Height = height; + resourceDesc.DepthOrArraySize = 1; + resourceDesc.MipLevels = 1; + resourceDesc.Format = format; + resourceDesc.SampleDesc.Count = 1; + resourceDesc.SampleDesc.Quality = 0; + resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + + SharedTexture &texture = context->resources[index]; + HRESULT hr = context->device->CreateCommittedResource( + &heapProperties, D3D12_HEAP_FLAG_SHARED, &resourceDesc, + D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&texture.resource)); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateCommittedResource", hr); + return false; + } + + const D3D12_RESOURCE_ALLOCATION_INFO allocationInfo = + context->device->GetResourceAllocationInfo(0, 1, &resourceDesc); + if (allocationInfo.SizeInBytes == 0 || + allocationInfo.SizeInBytes == UINT64_MAX) { + setError("D3D12 returned an invalid shared resource allocation size."); + return false; + } + texture.allocationSize = allocationInfo.SizeInBytes; + + hr = context->device->CreateSharedHandle(texture.resource.Get(), nullptr, + GENERIC_ALL, nullptr, + &texture.sharedHandle); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateSharedHandle(resource)", hr); + return false; + } + return true; +} - void closeSharedHandles(D3D12InteropContext *context) { - if (!context) { - return; - } - for (SharedTexture &texture : context->resources) { - if (texture.sharedHandle) { - CloseHandle(texture.sharedHandle); - texture.sharedHandle = nullptr; - } - } - if (context->fenceSharedHandle) { - CloseHandle(context->fenceSharedHandle); - context->fenceSharedHandle = nullptr; - } - if (context->fenceEvent) { - CloseHandle(context->fenceEvent); - context->fenceEvent = nullptr; - } - } +/** + * Blocks the calling thread until the interop fence reaches the given value. + * Returns immediately (success) when the context is null, the value is 0, or + * the fence has already completed it; otherwise arms the fence event and + * waits on it. Returns false and sets the error string on failure. + */ +bool waitForFence(D3D12InteropContext *context, uint64_t value) { + if (!context || value == 0 || context->fence->GetCompletedValue() >= value) { + return true; + } + const HRESULT hr = + context->fence->SetEventOnCompletion(value, context->fenceEvent); + if (FAILED(hr)) { + setHresultError("ID3D12Fence::SetEventOnCompletion", hr); + return false; + } + if (WaitForSingleObject(context->fenceEvent, INFINITE) != WAIT_OBJECT_0) { + setError("Waiting for the D3D12 interop fence failed."); + return false; + } + return true; +} - D3D12InteropContext *fromHandle(jlong handle) { - return reinterpret_cast( - static_cast(handle)); +/** + * Closes every Win32 handle owned by the context: the shared handle of each + * interop texture, the shared fence handle, and the fence wait event. + * COM resources are released separately via ComPtr when the context is + * destroyed. + */ +void closeSharedHandles(D3D12InteropContext *context) { + if (!context) { + return; + } + for (SharedTexture &texture : context->resources) { + if (texture.sharedHandle) { + CloseHandle(texture.sharedHandle); + texture.sharedHandle = nullptr; } + } + if (context->fenceSharedHandle) { + CloseHandle(context->fenceSharedHandle); + context->fenceSharedHandle = nullptr; + } + if (context->fenceEvent) { + CloseHandle(context->fenceEvent); + context->fenceEvent = nullptr; + } +} - SharedTexture *getResource(D3D12InteropContext *context, jint index) { - if (!context || index < 0 || - static_cast(index) >= RESOURCE_COUNT) { - setError("Invalid D3D12 interop resource index."); - return nullptr; - } - return &context->resources[static_cast(index)]; - } +/** + * Reinterprets the opaque jlong handle returned by Nd3d12CreateContext back + * into the native context pointer. Returns null for a 0 handle. + */ +D3D12InteropContext *fromHandle(jlong handle) { + return reinterpret_cast(static_cast(handle)); +} + +/** + * Looks up one of the shared interop textures by index + * (RESOURCE_INPUT_COLOR .. RESOURCE_OUTPUT_COLOR). Returns null and sets the + * error string when the context is null or the index is out of range. + */ +SharedTexture *getResource(D3D12InteropContext *context, jint index) { + if (!context || index < 0 || static_cast(index) >= RESOURCE_COUNT) { + setError("Invalid D3D12 interop resource index."); + return nullptr; + } + return &context->resources[static_cast(index)]; } +} // namespace extern "C" { - JNIEXPORT jlong JNICALL - Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12CreateContext( - JNIEnv *, - jclass, - jlong adapterLuid, - jint renderWidth, - jint renderHeight, - jint outputWidth, - jint outputHeight, - jint colorFormat) { - g_lastError.clear(); - if (adapterLuid == 0 || renderWidth <= 0 || renderHeight <= 0 || - outputWidth <= 0 || outputHeight <= 0) { - setError("Invalid D3D12 interop context dimensions or adapter LUID."); - return 0; - } - - const DXGI_FORMAT dxgiColorFormat = toDxgiFormat(colorFormat); - if (dxgiColorFormat == DXGI_FORMAT_UNKNOWN) { - setError("The configured internal color format is not supported by D3D12 interop."); - return 0; - } - - auto *context = new (std::nothrow) D3D12InteropContext(); - if (!context) { - setError("Could not allocate the D3D12 interop context."); - return 0; - } - - ComPtr factory; - HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory)); - if (FAILED(hr)) { - setHresultError("CreateDXGIFactory1", hr); - delete context; - return 0; - } - - for (UINT index = 0;; ++index) { - ComPtr candidate; - if (factory->EnumAdapters1(index, &candidate) == DXGI_ERROR_NOT_FOUND) { - break; - } - DXGI_ADAPTER_DESC1 desc = {}; - if (SUCCEEDED(candidate->GetDesc1(&desc)) && - (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) == 0 && - sameLuid(desc.AdapterLuid, static_cast(adapterLuid))) { - context->adapter = candidate; - break; - } - } - if (!context->adapter) { - setError("No D3D12 adapter matches the OpenGL device LUID."); - delete context; - return 0; - } - - hr = D3D12CreateDevice( - context->adapter.Get(), - D3D_FEATURE_LEVEL_12_0, - IID_PPV_ARGS(&context->device)); - if (FAILED(hr)) { - setHresultError("D3D12CreateDevice", hr); - delete context; - return 0; - } - - D3D12_COMMAND_QUEUE_DESC queueDesc = {}; - queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; - queueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL; - queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; - hr = context->device->CreateCommandQueue( - &queueDesc, - IID_PPV_ARGS(&context->queue)); - if (FAILED(hr)) { - setHresultError("ID3D12Device::CreateCommandQueue", hr); - delete context; - return 0; - } - - hr = context->device->CreateCommandAllocator( - D3D12_COMMAND_LIST_TYPE_DIRECT, - IID_PPV_ARGS(&context->commandAllocator)); - if (FAILED(hr)) { - setHresultError("ID3D12Device::CreateCommandAllocator", hr); - delete context; - return 0; - } - - hr = context->device->CreateCommandList( - 0, - D3D12_COMMAND_LIST_TYPE_DIRECT, - context->commandAllocator.Get(), - nullptr, - IID_PPV_ARGS(&context->commandList)); - if (FAILED(hr)) { - setHresultError("ID3D12Device::CreateCommandList", hr); - delete context; - return 0; - } - hr = context->commandList->Close(); - if (FAILED(hr)) { - setHresultError("ID3D12GraphicsCommandList::Close(initial)", hr); - delete context; - return 0; - } - - hr = context->device->CreateFence( - 0, - D3D12_FENCE_FLAG_SHARED, - IID_PPV_ARGS(&context->fence)); - if (FAILED(hr)) { - setHresultError("ID3D12Device::CreateFence", hr); - delete context; - return 0; - } - context->fenceEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if (!context->fenceEvent) { - setError("CreateEventW failed for the D3D12 interop fence."); - delete context; - return 0; - } - hr = context->device->CreateSharedHandle( - context->fence.Get(), - nullptr, - GENERIC_ALL, - nullptr, - &context->fenceSharedHandle); - if (FAILED(hr)) { - setHresultError("ID3D12Device::CreateSharedHandle(fence)", hr); - closeSharedHandles(context); - delete context; - return 0; - } - - const bool resourcesCreated = - createSharedTexture( - context, - RESOURCE_INPUT_COLOR, - static_cast(renderWidth), - static_cast(renderHeight), - dxgiColorFormat) && - createSharedTexture( - context, - RESOURCE_INPUT_DEPTH, - static_cast(renderWidth), - static_cast(renderHeight), - DXGI_FORMAT_R32_FLOAT) && - createSharedTexture( - context, - RESOURCE_INPUT_MOTION_VECTORS, - static_cast(renderWidth), - static_cast(renderHeight), - DXGI_FORMAT_R16G16_FLOAT) && - createSharedTexture( - context, - RESOURCE_INPUT_EXPOSURE, - 1, - 1, - DXGI_FORMAT_R16_FLOAT) && - createSharedTexture( - context, - RESOURCE_OUTPUT_COLOR, - static_cast(outputWidth), - static_cast(outputHeight), - dxgiColorFormat); - if (!resourcesCreated) { - closeSharedHandles(context); - delete context; - return 0; - } - - return static_cast( - reinterpret_cast(context)); +/** + * Creates a D3D12 interop context for the GPU identified by adapterLuid + * (matching the OpenGL device so both APIs share one adapter). + * + * Initializes (or reuses, when the LUID matches and the device is not + * removed) a process-wide shared ID3D12Device, then creates a direct command + * queue, allocator, and command list, a shared fence with its wait event and + * NT handle, and the five shared interop textures: input color (render size, + * colorFormat), input depth (R32_FLOAT), input motion vectors (R16G16_FLOAT), + * input exposure (1x1 R16_FLOAT), and output color (output size, colorFormat). + * + * Returns an opaque context handle (pointer as jlong), or 0 on failure with + * the reason available via Nd3d12GetLastError. + */ +JNIEXPORT jlong JNICALL +Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12CreateContext( + JNIEnv *, jclass, jlong adapterLuid, jint renderWidth, jint renderHeight, + jint outputWidth, jint outputHeight, jint colorFormat) { + g_lastError.clear(); + if (adapterLuid == 0 || renderWidth <= 0 || renderHeight <= 0 || + outputWidth <= 0 || outputHeight <= 0) { + setError("Invalid D3D12 interop context dimensions or adapter LUID."); + return 0; + } + + const DXGI_FORMAT dxgiColorFormat = toDxgiFormat(colorFormat); + if (dxgiColorFormat == DXGI_FORMAT_UNKNOWN) { + setError("The configured internal color format is not supported by D3D12 " + "interop."); + return 0; + } + + auto *context = new (std::nothrow) D3D12InteropContext(); + if (!context) { + setError("Could not allocate the D3D12 interop context."); + return 0; + } + + ComPtr factory; + HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory)); + if (FAILED(hr)) { + setHresultError("CreateDXGIFactory1", hr); + delete context; + return 0; + } + + for (UINT index = 0;; ++index) { + ComPtr candidate; + if (factory->EnumAdapters1(index, &candidate) == DXGI_ERROR_NOT_FOUND) { + break; } - - JNIEXPORT void JNICALL - Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12DestroyContext( - JNIEnv *, - jclass, - jlong contextHandle) { - D3D12InteropContext *context = fromHandle(contextHandle); - if (!context) { - return; - } - waitForFence(context, context->lastSubmittedFenceValue); - closeSharedHandles(context); + DXGI_ADAPTER_DESC1 desc = {}; + if (SUCCEEDED(candidate->GetDesc1(&desc)) && + (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) == 0 && + sameLuid(desc.AdapterLuid, static_cast(adapterLuid))) { + context->adapter = candidate; + break; + } + } + if (!context->adapter) { + setError("No D3D12 adapter matches the OpenGL device LUID."); + delete context; + return 0; + } + + { + std::lock_guard lock(g_deviceMutex); + if (g_sharedDevice && + g_sharedDeviceLuid == static_cast(adapterLuid) && + SUCCEEDED(g_sharedDevice->GetDeviceRemovedReason())) { + context->device = g_sharedDevice; + } else { + g_sharedDevice.Reset(); + g_sharedDeviceLuid = 0; + hr = D3D12CreateDevice(context->adapter.Get(), D3D_FEATURE_LEVEL_12_0, + IID_PPV_ARGS(&context->device)); + if (FAILED(hr)) { + setHresultError("D3D12CreateDevice", hr); delete context; + return 0; + } + g_sharedDevice = context->device; + g_sharedDeviceLuid = static_cast(adapterLuid); } + } + + D3D12_COMMAND_QUEUE_DESC queueDesc = {}; + queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + queueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL; + queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + hr = context->device->CreateCommandQueue(&queueDesc, + IID_PPV_ARGS(&context->queue)); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateCommandQueue", hr); + delete context; + return 0; + } + + hr = context->device->CreateCommandAllocator( + D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&context->commandAllocator)); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateCommandAllocator", hr); + delete context; + return 0; + } + + hr = context->device->CreateCommandList( + 0, D3D12_COMMAND_LIST_TYPE_DIRECT, context->commandAllocator.Get(), + nullptr, IID_PPV_ARGS(&context->commandList)); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateCommandList", hr); + delete context; + return 0; + } + hr = context->commandList->Close(); + if (FAILED(hr)) { + setHresultError("ID3D12GraphicsCommandList::Close(initial)", hr); + delete context; + return 0; + } + + hr = context->device->CreateFence(0, D3D12_FENCE_FLAG_SHARED, + IID_PPV_ARGS(&context->fence)); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateFence", hr); + delete context; + return 0; + } + context->fenceEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if (!context->fenceEvent) { + setError("CreateEventW failed for the D3D12 interop fence."); + delete context; + return 0; + } + hr = context->device->CreateSharedHandle(context->fence.Get(), nullptr, + GENERIC_ALL, nullptr, + &context->fenceSharedHandle); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateSharedHandle(fence)", hr); + closeSharedHandles(context); + delete context; + return 0; + } + + const bool resourcesCreated = + createSharedTexture( + context, RESOURCE_INPUT_COLOR, static_cast(renderWidth), + static_cast(renderHeight), dxgiColorFormat) && + createSharedTexture( + context, RESOURCE_INPUT_DEPTH, static_cast(renderWidth), + static_cast(renderHeight), DXGI_FORMAT_R32_FLOAT) && + createSharedTexture(context, RESOURCE_INPUT_MOTION_VECTORS, + static_cast(renderWidth), + static_cast(renderHeight), + DXGI_FORMAT_R16G16_FLOAT) && + createSharedTexture(context, RESOURCE_INPUT_EXPOSURE, 1, 1, + DXGI_FORMAT_R16_FLOAT) && + createSharedTexture(context, RESOURCE_OUTPUT_COLOR, + static_cast(outputWidth), + static_cast(outputHeight), dxgiColorFormat); + if (!resourcesCreated) { + closeSharedHandles(context); + delete context; + return 0; + } + + return static_cast(reinterpret_cast(context)); +} - JNIEXPORT jlong JNICALL - Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetDevice( - JNIEnv *, - jclass, - jlong contextHandle) { - D3D12InteropContext *context = fromHandle(contextHandle); - return context - ? static_cast( - reinterpret_cast(context->device.Get())) - : 0; - } +/** + * Destroys a context created by Nd3d12CreateContext: waits for the last + * submitted fence value so no in-flight GPU work references the resources, + * closes all shared Win32 handles, and frees the context. A null handle is + * a no-op. + */ +JNIEXPORT void JNICALL +Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12DestroyContext( + JNIEnv *, jclass, jlong contextHandle) { + D3D12InteropContext *context = fromHandle(contextHandle); + if (!context) { + return; + } + waitForFence(context, context->lastSubmittedFenceValue); + closeSharedHandles(context); + delete context; +} - JNIEXPORT jlong JNICALL - Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetCommandList( - JNIEnv *, - jclass, - jlong contextHandle) { - D3D12InteropContext *context = fromHandle(contextHandle); - return context - ? static_cast( - reinterpret_cast(context->commandList.Get())) - : 0; - } +/** + * Returns the raw ID3D12Device pointer of the context as a jlong, so Java can + * hand it to the FidelityFX FSR backend. Returns 0 for an invalid handle. + */ +JNIEXPORT jlong JNICALL +Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetDevice( + JNIEnv *, jclass, jlong contextHandle) { + D3D12InteropContext *context = fromHandle(contextHandle); + return context ? static_cast( + reinterpret_cast(context->device.Get())) + : 0; +} - JNIEXPORT jlong JNICALL - Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetResource( - JNIEnv *, - jclass, - jlong contextHandle, - jint index) { - SharedTexture *texture = getResource(fromHandle(contextHandle), index); - return texture - ? static_cast( - reinterpret_cast(texture->resource.Get())) - : 0; - } +/** + * Returns the raw ID3D12GraphicsCommandList pointer of the context as a + * jlong, so Java can record upscaling commands into it between + * Nd3d12BeginFrame and Nd3d12ExecuteFrame. Returns 0 for an invalid handle. + */ +JNIEXPORT jlong JNICALL +Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetCommandList( + JNIEnv *, jclass, jlong contextHandle) { + D3D12InteropContext *context = fromHandle(contextHandle); + return context ? static_cast( + reinterpret_cast(context->commandList.Get())) + : 0; +} - JNIEXPORT jlong JNICALL - Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetResourceSharedHandle( - JNIEnv *, - jclass, - jlong contextHandle, - jint index) { - SharedTexture *texture = getResource(fromHandle(contextHandle), index); - return texture - ? static_cast( - reinterpret_cast(texture->sharedHandle)) - : 0; - } +/** + * Returns the raw ID3D12Resource pointer of the interop texture at the given + * index as a jlong, so Java can bind it as an FSR input/output. Returns 0 for + * an invalid handle or index (with the error string set in the latter case). + */ +JNIEXPORT jlong JNICALL +Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetResource( + JNIEnv *, jclass, jlong contextHandle, jint index) { + SharedTexture *texture = getResource(fromHandle(contextHandle), index); + return texture ? static_cast( + reinterpret_cast(texture->resource.Get())) + : 0; +} - JNIEXPORT jlong JNICALL - Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetResourceAllocationSize( - JNIEnv *, - jclass, - jlong contextHandle, - jint index) { - SharedTexture *texture = getResource(fromHandle(contextHandle), index); - return texture - ? static_cast(texture->allocationSize) - : 0; - } +/** + * Returns the NT shared handle (as a jlong) of the interop texture at the + * given index, used to import the texture into OpenGL. Returns 0 for an + * invalid handle or index. + */ +JNIEXPORT jlong JNICALL +Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetResourceSharedHandle( + JNIEnv *, jclass, jlong contextHandle, jint index) { + SharedTexture *texture = getResource(fromHandle(contextHandle), index); + return texture ? static_cast( + reinterpret_cast(texture->sharedHandle)) + : 0; +} - JNIEXPORT jlong JNICALL - Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetFenceSharedHandle( - JNIEnv *, - jclass, - jlong contextHandle) { - D3D12InteropContext *context = fromHandle(contextHandle); - return context - ? static_cast( - reinterpret_cast(context->fenceSharedHandle)) - : 0; - } +/** + * Returns the driver-reported allocation size in bytes of the interop texture + * at the given index. OpenGL needs this when importing the shared memory + * object. Returns 0 for an invalid handle or index. + */ +JNIEXPORT jlong JNICALL +Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetResourceAllocationSize( + JNIEnv *, jclass, jlong contextHandle, jint index) { + SharedTexture *texture = getResource(fromHandle(contextHandle), index); + return texture ? static_cast(texture->allocationSize) : 0; +} - JNIEXPORT jint JNICALL - Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12BeginFrame( - JNIEnv *, - jclass, - jlong contextHandle, - jlong waitFenceValue) { - D3D12InteropContext *context = fromHandle(contextHandle); - if (!context || waitFenceValue <= 0) { - setError("Invalid D3D12 begin-frame arguments."); - return E_INVALIDARG; - } - if (context->recording) { - setError("The D3D12 command list is already recording."); - return E_FAIL; - } - if (!waitForFence(context, context->lastSubmittedFenceValue)) { - return E_FAIL; - } - - HRESULT hr = context->commandAllocator->Reset(); - if (FAILED(hr)) { - setHresultError("ID3D12CommandAllocator::Reset", hr); - return static_cast(hr); - } - hr = context->commandList->Reset( - context->commandAllocator.Get(), - nullptr); - if (FAILED(hr)) { - setHresultError("ID3D12GraphicsCommandList::Reset", hr); - return static_cast(hr); - } - hr = context->queue->Wait( - context->fence.Get(), - static_cast(waitFenceValue)); - if (FAILED(hr)) { - setHresultError("ID3D12CommandQueue::Wait", hr); - context->commandList->Close(); - return static_cast(hr); - } - context->recording = true; - return S_OK; - } +/** + * Returns the NT shared handle (as a jlong) of the context's fence, used to + * import the fence into OpenGL as a semaphore for cross-API synchronization. + * Returns 0 for an invalid handle. + */ +JNIEXPORT jlong JNICALL +Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetFenceSharedHandle( + JNIEnv *, jclass, jlong contextHandle) { + D3D12InteropContext *context = fromHandle(contextHandle); + return context ? static_cast( + reinterpret_cast(context->fenceSharedHandle)) + : 0; +} - JNIEXPORT jint JNICALL - Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12ExecuteFrame( - JNIEnv *, - jclass, - jlong contextHandle, - jlong signalFenceValue) { - D3D12InteropContext *context = fromHandle(contextHandle); - if (!context || !context->recording || signalFenceValue <= 0) { - setError("Invalid D3D12 execute-frame state or fence value."); - return E_INVALIDARG; - } - - HRESULT hr = context->commandList->Close(); - context->recording = false; - if (FAILED(hr)) { - setHresultError("ID3D12GraphicsCommandList::Close", hr); - return static_cast(hr); - } - - ID3D12CommandList *commandLists[] = {context->commandList.Get()}; - context->queue->ExecuteCommandLists(1, commandLists); - hr = context->queue->Signal( - context->fence.Get(), - static_cast(signalFenceValue)); - if (FAILED(hr)) { - setHresultError("ID3D12CommandQueue::Signal", hr); - return static_cast(hr); - } - context->lastSubmittedFenceValue = - static_cast(signalFenceValue); - return S_OK; - } +/** + * Starts a D3D12 frame: CPU-waits for the previous frame's fence value, + * resets the command allocator and list into recording state, then makes the + * queue GPU-wait on waitFenceValue — the fence value signaled by OpenGL once + * the input textures are ready. Java may then record commands into the list + * obtained from Nd3d12GetCommandList. + * + * Returns S_OK on success, E_INVALIDARG/E_FAIL for invalid state, or the + * failing HRESULT (as jint) otherwise; the error string holds details. + */ +JNIEXPORT jint JNICALL +Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12BeginFrame( + JNIEnv *, jclass, jlong contextHandle, jlong waitFenceValue) { + D3D12InteropContext *context = fromHandle(contextHandle); + if (!context || waitFenceValue <= 0) { + setError("Invalid D3D12 begin-frame arguments."); + return E_INVALIDARG; + } + if (context->recording) { + setError("The D3D12 command list is already recording."); + return E_FAIL; + } + if (!waitForFence(context, context->lastSubmittedFenceValue)) { + return E_FAIL; + } + + HRESULT hr = context->commandAllocator->Reset(); + if (FAILED(hr)) { + setHresultError("ID3D12CommandAllocator::Reset", hr); + return static_cast(hr); + } + hr = context->commandList->Reset(context->commandAllocator.Get(), nullptr); + if (FAILED(hr)) { + setHresultError("ID3D12GraphicsCommandList::Reset", hr); + return static_cast(hr); + } + hr = context->queue->Wait(context->fence.Get(), + static_cast(waitFenceValue)); + if (FAILED(hr)) { + setHresultError("ID3D12CommandQueue::Wait", hr); + context->commandList->Close(); + return static_cast(hr); + } + context->recording = true; + return S_OK; +} - JNIEXPORT jint JNICALL - Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12WaitIdle( - JNIEnv *, - jclass, - jlong contextHandle) { - D3D12InteropContext *context = fromHandle(contextHandle); - if (!context) { - setError("The D3D12 interop context is null."); - return E_INVALIDARG; - } - return waitForFence(context, context->lastSubmittedFenceValue) - ? S_OK - : E_FAIL; - } +/** + * Ends a D3D12 frame started by Nd3d12BeginFrame: closes the command list, + * submits it to the queue, and signals signalFenceValue on the shared fence + * so OpenGL can wait for the output texture to be ready. The value is + * remembered as lastSubmittedFenceValue for later idle waits. + * + * Returns S_OK on success, E_INVALIDARG when not recording or the fence value + * is invalid, or the failing HRESULT (as jint) otherwise. + */ +JNIEXPORT jint JNICALL +Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12ExecuteFrame( + JNIEnv *, jclass, jlong contextHandle, jlong signalFenceValue) { + D3D12InteropContext *context = fromHandle(contextHandle); + if (!context || !context->recording || signalFenceValue <= 0) { + setError("Invalid D3D12 execute-frame state or fence value."); + return E_INVALIDARG; + } + + HRESULT hr = context->commandList->Close(); + context->recording = false; + if (FAILED(hr)) { + setHresultError("ID3D12GraphicsCommandList::Close", hr); + return static_cast(hr); + } + + ID3D12CommandList *commandLists[] = {context->commandList.Get()}; + context->queue->ExecuteCommandLists(1, commandLists); + hr = context->queue->Signal(context->fence.Get(), + static_cast(signalFenceValue)); + if (FAILED(hr)) { + setHresultError("ID3D12CommandQueue::Signal", hr); + return static_cast(hr); + } + context->lastSubmittedFenceValue = static_cast(signalFenceValue); + return S_OK; +} - JNIEXPORT jstring JNICALL - Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetLastError( - JNIEnv *env, - jclass) { - return env->NewStringUTF(g_lastError.c_str()); - } +/** + * Blocks until all GPU work submitted so far (up to the last value signaled + * by Nd3d12ExecuteFrame) has completed. Returns S_OK, E_INVALIDARG for a + * null context, or E_FAIL if the fence wait itself failed. + */ +JNIEXPORT jint JNICALL +Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12WaitIdle( + JNIEnv *, jclass, jlong contextHandle) { + D3D12InteropContext *context = fromHandle(contextHandle); + if (!context) { + setError("The D3D12 interop context is null."); + return E_INVALIDARG; + } + return waitForFence(context, context->lastSubmittedFenceValue) ? S_OK + : E_FAIL; +} + +/** + * Returns the thread-local error message recorded by the last failed interop + * call on this thread, or an empty string if none occurred. + */ +JNIEXPORT jstring JNICALL +Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetLastError( + JNIEnv *env, jclass) { + return env->NewStringUTF(g_lastError.c_str()); +} } #endif From 267aca3bc4e5b348d881c900554cca578320c8b6 Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Wed, 29 Jul 2026 15:15:49 +0800 Subject: [PATCH 07/19] fsr4 dedicated cmake target --- .../common/upscale/ffxfsr/FfxFSR4D3D12.java | 6 +- .../core/NativeLibManager.java | 3 + native/cpp/CMakeLists.txt | 5 + native/cpp/SRNativeFSR/CMakeLists.txt | 49 +- native/cpp/SRNativeFSR/src/sr_provider.cpp | 26 - native/cpp/SRNativeFSR4/CMakeLists.txt | 39 ++ .../include/sr/fsr4}/ffx_api_minimal.h | 0 .../include/sr/fsr4}/ffx_api_upscale.h | 0 .../include/sr/fsr4/sr_provider.h | 8 + .../src/ffx_api_upscale.cpp | 4 +- native/cpp/SRNativeFSR4/src/sr_provider.cpp | 29 + .../cpp/SRNativeMain/include/sr/sr_modules.h | 2 +- native/cpp/SRNativeMain/src/d3d12_interop.cpp | 513 ++++++++++++------ 13 files changed, 437 insertions(+), 247 deletions(-) create mode 100644 native/cpp/SRNativeFSR4/CMakeLists.txt rename native/cpp/{SRNativeFSR/include/sr/fsr => SRNativeFSR4/include/sr/fsr4}/ffx_api_minimal.h (100%) rename native/cpp/{SRNativeFSR/include/sr/fsr => SRNativeFSR4/include/sr/fsr4}/ffx_api_upscale.h (100%) create mode 100644 native/cpp/SRNativeFSR4/include/sr/fsr4/sr_provider.h rename native/cpp/{SRNativeFSR => SRNativeFSR4}/src/ffx_api_upscale.cpp (99%) create mode 100644 native/cpp/SRNativeFSR4/src/sr_provider.cpp diff --git a/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java b/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java index 2615f6b7..46d07335 100644 --- a/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java +++ b/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java @@ -39,7 +39,7 @@ public final class FfxFSR4D3D12 extends D3D12InteropAlgorithm { @Override protected void onD3D12InteropCreated(InitializationDescription desc) { - Path providerLibrary = NativeLibManager.LIB_SUPER_RESOLUTION_FSR + Path providerLibrary = NativeLibManager.LIB_SUPER_RESOLUTION_FSR4 .getTargetPath(SuperResolutionConstants.NATIVE_LIBRARIES_DIR.getPath()) .toAbsolutePath(); Path upscalerDll = SuperResolutionConstants.NATIVE_LIBRARIES_DIR @@ -58,8 +58,8 @@ protected void onD3D12InteropCreated(InitializationDescription desc) { SRReturnCode loadCode = SuperResolutionNativeAPI.srLoadUpscaleProvidersFromLibrary( providerLibrary.toString(), - "srGetFfxFSRUpscaleProviders", - "srGetFfxFSRUpscaleProvidersCount"); + "srGetFfxFSR4UpscaleProviders", + "srGetFfxFSR4UpscaleProvidersCount"); if (loadCode != SRReturnCode.OK) { throw new IllegalStateException( "Could not load FSR providers: " + loadCode); diff --git a/common/src/main/java/io/homo/superresolution/core/NativeLibManager.java b/common/src/main/java/io/homo/superresolution/core/NativeLibManager.java index fd40562b..443c9bb8 100644 --- a/common/src/main/java/io/homo/superresolution/core/NativeLibManager.java +++ b/common/src/main/java/io/homo/superresolution/core/NativeLibManager.java @@ -48,6 +48,7 @@ public class NativeLibManager { private static final List libs = new ArrayList<>(); public static NativeLib LIB_SUPER_RESOLUTION = null; public static NativeLib LIB_SUPER_RESOLUTION_FSR = null; + public static NativeLib LIB_SUPER_RESOLUTION_FSR4 = null; public static NativeLib LIB_SUPER_RESOLUTION_XESS = null; public static NativeLib LIB_SUPER_RESOLUTION_NGX = null; public static NativeLib LIB_SUPER_RESOLUTION_STREAMLINE = null; @@ -67,6 +68,7 @@ public class NativeLibManager { boolean presentation = VulkanPresentationFeature.shouldInitializeStreamline(); LIB_SUPER_RESOLUTION = new NativeLib("SuperResolution", true, true); LIB_SUPER_RESOLUTION_FSR = new NativeLib("SuperResolutionFSR", false, false); + LIB_SUPER_RESOLUTION_FSR4 = new NativeLib("SuperResolutionFSR4", false, false); LIB_SUPER_RESOLUTION_XESS = new NativeLib("SuperResolutionXeSS", false, false); LIB_SUPER_RESOLUTION_NGX = new NativeLib("SuperResolutionNGX", false, false); LIB_SUPER_RESOLUTION_STREAMLINE = new NativeLib("SuperResolutionStreamline", presentation, presentation); @@ -78,6 +80,7 @@ public class NativeLibManager { LIB_STREAMLINE_NVNGX_REFLEX = new NativeLib("NvLowLatencyVk", false, presentation, true); libs.add(LIB_SUPER_RESOLUTION); libs.add(LIB_SUPER_RESOLUTION_FSR); + libs.add(LIB_SUPER_RESOLUTION_FSR4); libs.add(LIB_SUPER_RESOLUTION_XESS); libs.add(LIB_SUPER_RESOLUTION_NGX); libs.add(LIB_STREAMLINE_COMMON); diff --git a/native/cpp/CMakeLists.txt b/native/cpp/CMakeLists.txt index dc8db06c..80fbb150 100644 --- a/native/cpp/CMakeLists.txt +++ b/native/cpp/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.15) project(SuperResolution) option(SR_FSR "启用以支持FSR" ON) +option(SR_FSR4 "启用以支持FSR4(FFX API,仅Windows D3D12)" ON) option(SR_XESS "启用以支持XESS" ON) option(SR_NGX "启用以支持NGX绑定" ON) option(SR_STREAMLINE "启用以支持Streamline" ON) @@ -88,6 +89,10 @@ if(SR_NGX) add_subdirectory("SRNativeNGX") endif() +if(SR_FSR4 AND ON_WINDOWS) + add_subdirectory("SRNativeFSR4") +endif() + if(SR_STREAMLINE AND ON_WINDOWS) add_subdirectory("SRNativeStreamline") endif() diff --git a/native/cpp/SRNativeFSR/CMakeLists.txt b/native/cpp/SRNativeFSR/CMakeLists.txt index 95ad83ad..72548978 100644 --- a/native/cpp/SRNativeFSR/CMakeLists.txt +++ b/native/cpp/SRNativeFSR/CMakeLists.txt @@ -6,18 +6,7 @@ if (NOT (ON_LINUX OR ON_WINDOWS)) message(FATAL_ERROR "${LIB_PLATFORM} 平台不支持FSR") endif() -option( - SR_FSR_FFX_API_ONLY - "Only build the signed FFX API D3D12 provider (Windows only)" - OFF -) -if(SR_FSR_FFX_API_ONLY AND NOT ON_WINDOWS) - message(FATAL_ERROR "SR_FSR_FFX_API_ONLY is only supported on Windows") -endif() - -if(NOT SR_FSR_FFX_API_ONLY) - find_package(Vulkan REQUIRED) -endif() +find_package(Vulkan REQUIRED) # ============================================================ # FidelityFX SDK: 从子模块源码构建 / 使用预编译库 @@ -30,9 +19,7 @@ option(SR_FSR_BUILD_SDK "从源码编译 FidelityFX SDK(需要 SDK 子模块 # SDK 编译产物输出目录(同时也是预编译库查找目录) set(FFX_SDK_LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/${LIB_PLATFORM}") -if(SR_FSR_FFX_API_ONLY) - message(STATUS "[FSR] 仅构建签名 FFX API D3D12 provider") -elseif(SR_FSR_BUILD_SDK) +if(SR_FSR_BUILD_SDK) # --- 检查子模块是否已初始化 --- if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/SDK/CMakeLists.txt") message(FATAL_ERROR @@ -169,23 +156,13 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/src ) -if(SR_FSR_FFX_API_ONLY) - set(ALL_SRC - ${CMAKE_CURRENT_SOURCE_DIR}/src/ffx_api_upscale.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/sr_provider.cpp - ) -else() - aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src ALL_SRC) -endif() +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src ALL_SRC) add_library(SR_FSR_LIB SHARED ${ALL_SRC}) target_compile_definitions(SR_FSR_LIB PRIVATE ${_SR_FSR_PLATFORM_DEFS} SRLIB_VERSION="${SRLIB_VERSION}" ) -if(SR_FSR_FFX_API_ONLY) - target_compile_definitions(SR_FSR_LIB PRIVATE SR_FSR_FFX_API_ONLY) -endif() if(CMAKE_SYSTEM_NAME MATCHES "Linux") set_target_properties(SR_FSR_LIB PROPERTIES OUTPUT_NAME "SuperResolutionFSR+${LIB_PLATFORM}+${SR_BUILD_TYPE}") @@ -193,15 +170,11 @@ elseif(CMAKE_SYSTEM_NAME MATCHES "Windows") set_target_properties(SR_FSR_LIB PROPERTIES OUTPUT_NAME "libSuperResolutionFSR+${LIB_PLATFORM}+${SR_BUILD_TYPE}") endif() -if(SR_FSR_FFX_API_ONLY) - target_link_libraries(SR_FSR_LIB SR_MAIN_LIB) -else() - target_link_libraries(SR_FSR_LIB - SR_MAIN_LIB - ${FFX_LINK_BACKEND} - ${FFX_LINK_FSR2} - ${FFX_LINK_FSR3UP} - ${FFX_LINK_FSR3} - ${Vulkan_LIBRARIES} - ) -endif() +target_link_libraries(SR_FSR_LIB + SR_MAIN_LIB + ${FFX_LINK_BACKEND} + ${FFX_LINK_FSR2} + ${FFX_LINK_FSR3UP} + ${FFX_LINK_FSR3} + ${Vulkan_LIBRARIES} +) diff --git a/native/cpp/SRNativeFSR/src/sr_provider.cpp b/native/cpp/SRNativeFSR/src/sr_provider.cpp index dc109fab..9dac7b6e 100644 --- a/native/cpp/SRNativeFSR/src/sr_provider.cpp +++ b/native/cpp/SRNativeFSR/src/sr_provider.cpp @@ -1,40 +1,19 @@ #include "sr/fsr/sr_provider.h" -#if defined(ON_WIN64) -#include "sr/fsr/ffx_api_upscale.h" -#endif -#if !defined(SR_FSR_FFX_API_ONLY) #include "sr/fsr/fsr2.h" #include "sr/fsr/fsr3.h" -#endif -#if defined(SR_FSR_FFX_API_ONLY) -static constexpr uint32_t PROVIDER_COUNT = 1; -#elif defined(ON_WIN64) -static constexpr uint32_t PROVIDER_COUNT = 3; -#else static constexpr uint32_t PROVIDER_COUNT = 2; -#endif static SRUpscaleProvider g_providers[PROVIDER_COUNT]; static bool g_initialized = false; static void ensureInitialized() { if (!g_initialized) { - #if defined(SR_FSR_FFX_API_ONLY) - g_providers[0].providerId = SR_MODULES_FFX_API_UPSCALE_ID; - g_providers[0].callbacks = srGetFfxApiUpscaleCallbacks(); - #else g_providers[0].providerId = SR_MODULES_FSR2_ID; g_providers[0].callbacks = srGetFfxFSR2UpscaleCallbacks(); g_providers[1].providerId = SR_MODULES_FSR3_ID; g_providers[1].callbacks = srGetFfxFSR3UpscaleCallbacks(); - - #if defined(ON_WIN64) - g_providers[2].providerId = SR_MODULES_FFX_API_UPSCALE_ID; - g_providers[2].callbacks = srGetFfxApiUpscaleCallbacks(); - #endif - #endif g_initialized = true; } } @@ -43,12 +22,7 @@ extern "C" { SR_API SRReturnCode srGetFfxFSRUpscaleProviders(SRUpscaleProvider *outProvider) { ensureInitialized(); outProvider[0] = g_providers[0]; - #if !defined(SR_FSR_FFX_API_ONLY) outProvider[1] = g_providers[1]; - #if defined(ON_WIN64) - outProvider[2] = g_providers[2]; - #endif - #endif return (SRReturnCode) SR_RETURN_CODE_OK; } diff --git a/native/cpp/SRNativeFSR4/CMakeLists.txt b/native/cpp/SRNativeFSR4/CMakeLists.txt new file mode 100644 index 00000000..979682d0 --- /dev/null +++ b/native/cpp/SRNativeFSR4/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.15) +project(SuperResolutionNativeFSR4) +message(STATUS "生成FSR4模块") + +if (NOT ON_WINDOWS) + message(FATAL_ERROR "${LIB_PLATFORM} 平台不支持FSR4(FFX API 仅支持 Windows D3D12)") +endif () + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if (MSVC) + add_compile_options(/utf-8 /ZI) +endif () +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +link_directories( + ${PROJECT_SOURCE_DIR}/../SRNativeMain/libraries/${LIB_PLATFORM} +) +include_directories( + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_SOURCE_DIR}/../SRNativeMain/include + ${PROJECT_SOURCE_DIR}/src +) + +aux_source_directory(${PROJECT_SOURCE_DIR}/src ALL_SRC) +add_library(SR_FSR4_LIB SHARED ${ALL_SRC}) + +target_compile_definitions(SR_FSR4_LIB PRIVATE + ON_WIN64 + _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR + SRLIB_VERSION="${SRLIB_VERSION}" +) + +set_target_properties(SR_FSR4_LIB PROPERTIES OUTPUT_NAME "libSuperResolutionFSR4+${LIB_PLATFORM}+${SR_BUILD_TYPE}") + +target_link_libraries(SR_FSR4_LIB + SR_MAIN_LIB +) diff --git a/native/cpp/SRNativeFSR/include/sr/fsr/ffx_api_minimal.h b/native/cpp/SRNativeFSR4/include/sr/fsr4/ffx_api_minimal.h similarity index 100% rename from native/cpp/SRNativeFSR/include/sr/fsr/ffx_api_minimal.h rename to native/cpp/SRNativeFSR4/include/sr/fsr4/ffx_api_minimal.h diff --git a/native/cpp/SRNativeFSR/include/sr/fsr/ffx_api_upscale.h b/native/cpp/SRNativeFSR4/include/sr/fsr4/ffx_api_upscale.h similarity index 100% rename from native/cpp/SRNativeFSR/include/sr/fsr/ffx_api_upscale.h rename to native/cpp/SRNativeFSR4/include/sr/fsr4/ffx_api_upscale.h diff --git a/native/cpp/SRNativeFSR4/include/sr/fsr4/sr_provider.h b/native/cpp/SRNativeFSR4/include/sr/fsr4/sr_provider.h new file mode 100644 index 00000000..c80c8402 --- /dev/null +++ b/native/cpp/SRNativeFSR4/include/sr/fsr4/sr_provider.h @@ -0,0 +1,8 @@ +#pragma once +#include "sr/sr_api.h" +#include "sr/sr_modules.h" + +extern "C" { + SR_API SRReturnCode srGetFfxFSR4UpscaleProviders(SRUpscaleProvider * outProvider); + SR_API SRReturnCode srGetFfxFSR4UpscaleProvidersCount(uint32_t * outCount); +} diff --git a/native/cpp/SRNativeFSR/src/ffx_api_upscale.cpp b/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp similarity index 99% rename from native/cpp/SRNativeFSR/src/ffx_api_upscale.cpp rename to native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp index 1e4ba543..0cc202bb 100644 --- a/native/cpp/SRNativeFSR/src/ffx_api_upscale.cpp +++ b/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp @@ -1,4 +1,4 @@ -#include "sr/fsr/ffx_api_upscale.h" +#include "sr/fsr4/ffx_api_upscale.h" #if defined(ON_WIN64) @@ -7,7 +7,7 @@ #endif #include -#include "sr/fsr/ffx_api_minimal.h" +#include "sr/fsr4/ffx_api_minimal.h" #include #include diff --git a/native/cpp/SRNativeFSR4/src/sr_provider.cpp b/native/cpp/SRNativeFSR4/src/sr_provider.cpp new file mode 100644 index 00000000..39565b8e --- /dev/null +++ b/native/cpp/SRNativeFSR4/src/sr_provider.cpp @@ -0,0 +1,29 @@ +#include "sr/fsr4/sr_provider.h" +#include "sr/fsr4/ffx_api_upscale.h" + +static constexpr uint32_t PROVIDER_COUNT = 1; + +static SRUpscaleProvider g_providers[PROVIDER_COUNT]; +static bool g_initialized = false; + +static void ensureInitialized() { + if (!g_initialized) { + g_providers[0].providerId = SR_MODULES_FSR4_ID; + g_providers[0].callbacks = srGetFfxApiUpscaleCallbacks(); + g_initialized = true; + } +} + +extern "C" { + SR_API SRReturnCode srGetFfxFSR4UpscaleProviders(SRUpscaleProvider *outProvider) { + ensureInitialized(); + outProvider[0] = g_providers[0]; + return (SRReturnCode) SR_RETURN_CODE_OK; + } + + SR_API SRReturnCode srGetFfxFSR4UpscaleProvidersCount(uint32_t *outCount) { + ensureInitialized(); + *outCount = PROVIDER_COUNT; + return (SRReturnCode) SR_RETURN_CODE_OK; + } +} diff --git a/native/cpp/SRNativeMain/include/sr/sr_modules.h b/native/cpp/SRNativeMain/include/sr/sr_modules.h index 67904b61..a67fe55c 100644 --- a/native/cpp/SRNativeMain/include/sr/sr_modules.h +++ b/native/cpp/SRNativeMain/include/sr/sr_modules.h @@ -3,4 +3,4 @@ #define SR_MODULES_FSR3_ID 0x8000003 #define SR_MODULES_XeSS_ID 0x8000004 #define SR_MODULES_DLSS_ID 0x8000005 -#define SR_MODULES_FFX_API_UPSCALE_ID 0x8000006 +#define SR_MODULES_FSR4_ID 0x8000006 diff --git a/native/cpp/SRNativeMain/src/d3d12_interop.cpp b/native/cpp/SRNativeMain/src/d3d12_interop.cpp index 63f4e628..3523c2a6 100644 --- a/native/cpp/SRNativeMain/src/d3d12_interop.cpp +++ b/native/cpp/SRNativeMain/src/d3d12_interop.cpp @@ -14,20 +14,25 @@ #include #include #include +#include #include #include +#include #include +#include using Microsoft::WRL::ComPtr; namespace { constexpr uint32_t RESOURCE_COUNT = 5; -constexpr uint32_t RESOURCE_INPUT_COLOR = 0; -constexpr uint32_t RESOURCE_INPUT_DEPTH = 1; -constexpr uint32_t RESOURCE_INPUT_MOTION_VECTORS = 2; -constexpr uint32_t RESOURCE_INPUT_EXPOSURE = 3; -constexpr uint32_t RESOURCE_OUTPUT_COLOR = 4; +// Slot order of D3D12InteropContext::resources; mirrors the index constants +// on the Java side, which passes them to Nd3d12GetResource*. +[[maybe_unused]] constexpr uint32_t RESOURCE_INPUT_COLOR = 0; +[[maybe_unused]] constexpr uint32_t RESOURCE_INPUT_DEPTH = 1; +[[maybe_unused]] constexpr uint32_t RESOURCE_INPUT_MOTION_VECTORS = 2; +[[maybe_unused]] constexpr uint32_t RESOURCE_INPUT_EXPOSURE = 3; +[[maybe_unused]] constexpr uint32_t RESOURCE_OUTPUT_COLOR = 4; // Values mirror SRSurfaceFormat / FfxApiSurfaceFormat. constexpr int SR_FORMAT_R16G16B16A16_FLOAT = 4; @@ -37,30 +42,92 @@ constexpr int SR_FORMAT_R16G16_FLOAT = 18; constexpr int SR_FORMAT_R16_FLOAT = 21; constexpr int SR_FORMAT_R32_FLOAT = 28; -struct SharedTexture { - ComPtr resource; - HANDLE sharedHandle = nullptr; - uint64_t allocationSize = 0; +/** + * Minimal RAII wrapper for a Win32 HANDLE, in the spirit of + * wil::unique_handle (which this project does not depend on): the handle is + * closed automatically on destruction, and the wrapper is move-only so a + * raw void* can no longer be copied around or leaked by accident. + */ +class UniqueHandle { +public: + UniqueHandle() = default; + explicit UniqueHandle(HANDLE handle) : handle_(handle) {} + ~UniqueHandle() { reset(); } + + UniqueHandle(const UniqueHandle &) = delete; + UniqueHandle &operator=(const UniqueHandle &) = delete; + + UniqueHandle(UniqueHandle &&other) noexcept : handle_(other.release()) {} + UniqueHandle &operator=(UniqueHandle &&other) noexcept { + reset(other.release()); + return *this; + } + + HANDLE get() const { return handle_; } + explicit operator bool() const { return handle_ != nullptr; } + + // Frees the current handle and returns storage for an out-parameter + // (e.g. ID3D12Device::CreateSharedHandle). + HANDLE *put() { + reset(); + return &handle_; + } + HANDLE release() { + HANDLE handle = handle_; + handle_ = nullptr; + return handle; + } + void reset(HANDLE handle = nullptr) { + if (handle_) { + CloseHandle(handle_); + } + handle_ = handle; + } + +private: + HANDLE handle_ = nullptr; }; -struct D3D12InteropContext { - ComPtr adapter; - ComPtr device; - ComPtr queue; - ComPtr commandAllocator; - ComPtr commandList; - ComPtr fence; - HANDLE fenceSharedHandle = nullptr; - HANDLE fenceEvent = nullptr; - uint64_t lastSubmittedFenceValue = 0; - bool recording = false; - std::array resources; +// Lifecycle of the command list across a frame: Idle outside of a frame, +// Recording between a successful Nd3d12BeginFrame and Nd3d12ExecuteFrame. +enum class FrameState { Idle, Recording }; + +/** + * A COM interface pointer that is non-null by construction. + * convention: the only way to obtain one is the checked from() factory, so + * holders such as D3D12InteropContext can dereference their members without + * any null check. Move-only, like the ownership it wraps. + */ +template class ComObj { +public: + ComObj(const ComObj &) = delete; + ComObj &operator=(const ComObj &) = delete; + ComObj(ComObj &&) noexcept = default; + ComObj &operator=(ComObj &&) noexcept = default; + + /** + * Checked conversion from a nullable ComPtr: returns an engaged NonNullCom + * when ptr holds an interface, std::nullopt otherwise. + */ + static std::optional from(ComPtr ptr) { + if (!ptr) { + return std::nullopt; + } + return ComObj(std::move(ptr)); + } + + T *get() const { return ptr_.Get(); } + T *operator->() const { return ptr_.Get(); } + + // Returns an additional owning reference (AddRef), mirroring Rc::clone. + ComPtr share() const { return ptr_; } + +private: + explicit ComObj(ComPtr ptr) : ptr_(std::move(ptr)) {} + ComPtr ptr_; }; thread_local std::string g_lastError; -std::mutex g_deviceMutex; -ComPtr g_sharedDevice; -uint64_t g_sharedDeviceLuid = 0; /** * Records an error message in the thread-local error string, which can later @@ -82,6 +149,110 @@ void setHresultError(const char *operation, HRESULT hr) { setError(buffer); } +/** + * Runs a COM creation call (one taking a T** out-parameter and returning an + * HRESULT) and returns the created interface as a ComObj. + */ +template +std::optional> createCom(const char *operation, Create &&create) { + ComPtr ptr; + const HRESULT hr = create(ptr.ReleaseAndGetAddressOf()); + if (FAILED(hr)) { + setHresultError(operation, hr); + return std::nullopt; + } + return ComObj::from(std::move(ptr)); +} + +/** + * A fully-initialized shared interop texture: the factory createSharedTexture + * only produces one once the resource, its NT shared handle, and the + * allocation size all exist, so the half-initialized state is not + * representable. + */ +struct SharedTexture { + ComObj resource; + UniqueHandle sharedHandle; + uint64_t allocationSize = 0; +}; + +struct D3D12InteropContext { + /** + * All COM members are ComObj: a context only exists once every object + * was created successfully, so the frame and wait functions can dereference + * them unconditionally — validity is enforced by the type, not by + * convention. + */ + D3D12InteropContext(ComObj adapter, + ComObj device, + ComObj queue, + ComObj commandAllocator, + ComObj commandList, + ComObj fence, + UniqueHandle fenceSharedHandle, UniqueHandle fenceEvent, + std::array resources) + : adapter(std::move(adapter)), device(std::move(device)), + queue(std::move(queue)), commandAllocator(std::move(commandAllocator)), + commandList(std::move(commandList)), fence(std::move(fence)), + fenceSharedHandle(std::move(fenceSharedHandle)), + fenceEvent(std::move(fenceEvent)), resources(std::move(resources)) {} + + ComObj adapter; + ComObj device; + ComObj queue; + ComObj commandAllocator; + ComObj commandList; + ComObj fence; + UniqueHandle fenceSharedHandle; + UniqueHandle fenceEvent; + uint64_t lastSubmittedFenceValue = 0; + FrameState frameState = FrameState::Idle; + std::array resources; +}; + +/** + * Process-wide D3D12 device cache with its mutex bundled in, in the spirit + * of Rust's Mutex: the data and the lock protecting it live in one object, + * so the cached device can only be touched through locked access. + */ +class SharedDevice { +public: + /** + * Returns the cached device when it matches adapterLuid and has not been + * removed; otherwise creates a new device on the given adapter and caches + * it. On creation failure the thread-local error string is set and an + * empty optional is returned. + */ + std::optional> + getOrCreate(const ComObj &adapter, uint64_t adapterLuid) { + std::lock_guard lock(mutex_); + if (device_ && luid_ == adapterLuid && + SUCCEEDED(device_->GetDeviceRemovedReason())) { + return ComObj::from(device_); + } + device_.Reset(); + luid_ = 0; + auto device = + createCom("D3D12CreateDevice", [&](ID3D12Device **pp) { + return D3D12CreateDevice(adapter.get(), D3D_FEATURE_LEVEL_12_0, + IID_PPV_ARGS(pp)); + }); + if (!device) { + return std::nullopt; + } + device_ = device->share(); + luid_ = adapterLuid; + return device; + } + +private: + std::mutex mutex_; + ComPtr device_; + uint64_t luid_ = 0; +}; + +SharedDevice g_sharedDevice; + /** * Compares a DXGI LUID against its packed 64-bit representation * (LowPart in the low 32 bits, HighPart in the high 32 bits), which is how @@ -119,19 +290,19 @@ DXGI_FORMAT toDxgiFormat(int format) { } /** - * Creates one of the shared interop textures (index RESOURCE_INPUT_* / - * RESOURCE_OUTPUT_COLOR) as a committed UAV-capable Texture2D on the DEFAULT - * heap with D3D12_HEAP_FLAG_SHARED, records its driver allocation size, and - * exports an NT shared handle so the resource can be opened from OpenGL - * (e.g. via GL_EXT_memory_object). On failure the thread-local error string - * is set and false is returned. + * Creates one shared interop texture as a committed UAV-capable Texture2D on + * the DEFAULT heap with D3D12_HEAP_FLAG_SHARED, records its driver allocation + * size, and exports an NT shared handle so the resource can be opened from + * OpenGL (e.g. via GL_EXT_memory_object). Returns the complete SharedTexture, + * or an empty optional on failure with the thread-local error string set — + * the half-initialized state is not representable. */ -bool createSharedTexture(D3D12InteropContext *context, uint32_t index, - uint32_t width, uint32_t height, DXGI_FORMAT format) { - if (!context || index >= RESOURCE_COUNT || width == 0 || height == 0 || - format == DXGI_FORMAT_UNKNOWN) { +std::optional +createSharedTexture(const ComObj &device, uint32_t width, + uint32_t height, DXGI_FORMAT format) { + if (width == 0 || height == 0 || format == DXGI_FORMAT_UNKNOWN) { setError("Invalid shared D3D12 texture description."); - return false; + return std::nullopt; } D3D12_HEAP_PROPERTIES heapProperties = {}; @@ -153,32 +324,34 @@ bool createSharedTexture(D3D12InteropContext *context, uint32_t index, resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; - SharedTexture &texture = context->resources[index]; - HRESULT hr = context->device->CreateCommittedResource( - &heapProperties, D3D12_HEAP_FLAG_SHARED, &resourceDesc, - D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&texture.resource)); - if (FAILED(hr)) { - setHresultError("ID3D12Device::CreateCommittedResource", hr); - return false; + auto resource = createCom( + "ID3D12Device::CreateCommittedResource", [&](ID3D12Resource **pp) { + return device->CreateCommittedResource( + &heapProperties, D3D12_HEAP_FLAG_SHARED, &resourceDesc, + D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(pp)); + }); + if (!resource) { + return std::nullopt; } const D3D12_RESOURCE_ALLOCATION_INFO allocationInfo = - context->device->GetResourceAllocationInfo(0, 1, &resourceDesc); + device->GetResourceAllocationInfo(0, 1, &resourceDesc); if (allocationInfo.SizeInBytes == 0 || allocationInfo.SizeInBytes == UINT64_MAX) { setError("D3D12 returned an invalid shared resource allocation size."); - return false; + return std::nullopt; } - texture.allocationSize = allocationInfo.SizeInBytes; - hr = context->device->CreateSharedHandle(texture.resource.Get(), nullptr, - GENERIC_ALL, nullptr, - &texture.sharedHandle); + UniqueHandle sharedHandle; + const HRESULT hr = + device->CreateSharedHandle(resource->get(), nullptr, GENERIC_ALL, + nullptr, sharedHandle.put()); if (FAILED(hr)) { setHresultError("ID3D12Device::CreateSharedHandle(resource)", hr); - return false; + return std::nullopt; } - return true; + return SharedTexture{std::move(*resource), std::move(sharedHandle), + allocationInfo.SizeInBytes}; } /** @@ -192,44 +365,19 @@ bool waitForFence(D3D12InteropContext *context, uint64_t value) { return true; } const HRESULT hr = - context->fence->SetEventOnCompletion(value, context->fenceEvent); + context->fence->SetEventOnCompletion(value, context->fenceEvent.get()); if (FAILED(hr)) { setHresultError("ID3D12Fence::SetEventOnCompletion", hr); return false; } - if (WaitForSingleObject(context->fenceEvent, INFINITE) != WAIT_OBJECT_0) { + if (WaitForSingleObject(context->fenceEvent.get(), INFINITE) != + WAIT_OBJECT_0) { setError("Waiting for the D3D12 interop fence failed."); return false; } return true; } -/** - * Closes every Win32 handle owned by the context: the shared handle of each - * interop texture, the shared fence handle, and the fence wait event. - * COM resources are released separately via ComPtr when the context is - * destroyed. - */ -void closeSharedHandles(D3D12InteropContext *context) { - if (!context) { - return; - } - for (SharedTexture &texture : context->resources) { - if (texture.sharedHandle) { - CloseHandle(texture.sharedHandle); - texture.sharedHandle = nullptr; - } - } - if (context->fenceSharedHandle) { - CloseHandle(context->fenceSharedHandle); - context->fenceSharedHandle = nullptr; - } - if (context->fenceEvent) { - CloseHandle(context->fenceEvent); - context->fenceEvent = nullptr; - } -} - /** * Reinterprets the opaque jlong handle returned by Nd3d12CreateContext back * into the native context pointer. Returns null for a 0 handle. @@ -263,6 +411,9 @@ extern "C" { * NT handle, and the five shared interop textures: input color (render size, * colorFormat), input depth (R32_FLOAT), input motion vectors (R16G16_FLOAT), * input exposure (1x1 R16_FLOAT), and output color (output size, colorFormat). + * Each step yields a checked object (ComObj / SharedTexture); the context + * itself is only constructed once every step has succeeded, and intermediate + * objects clean up after themselves via RAII on any failure. * * Returns an opaque context handle (pointer as jlong), or 0 on failure with * the reason available via Nd3d12GetLastError. @@ -285,148 +436,156 @@ Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12Create return 0; } - auto *context = new (std::nothrow) D3D12InteropContext(); - if (!context) { - setError("Could not allocate the D3D12 interop context."); - return 0; - } - - ComPtr factory; - HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory)); - if (FAILED(hr)) { - setHresultError("CreateDXGIFactory1", hr); - delete context; + auto factory = createCom( + "CreateDXGIFactory1", + [](IDXGIFactory6 **pp) { return CreateDXGIFactory1(IID_PPV_ARGS(pp)); }); + if (!factory) { return 0; } + std::optional> adapter; for (UINT index = 0;; ++index) { ComPtr candidate; - if (factory->EnumAdapters1(index, &candidate) == DXGI_ERROR_NOT_FOUND) { + const HRESULT hr = (*factory)->EnumAdapters1(index, &candidate); + if (hr == DXGI_ERROR_NOT_FOUND) { break; } + if (FAILED(hr)) { + setHresultError("IDXGIFactory1::EnumAdapters1", hr); + return 0; + } DXGI_ADAPTER_DESC1 desc = {}; if (SUCCEEDED(candidate->GetDesc1(&desc)) && (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) == 0 && sameLuid(desc.AdapterLuid, static_cast(adapterLuid))) { - context->adapter = candidate; + adapter = ComObj::from(std::move(candidate)); break; } } - if (!context->adapter) { + if (!adapter) { setError("No D3D12 adapter matches the OpenGL device LUID."); - delete context; return 0; } - { - std::lock_guard lock(g_deviceMutex); - if (g_sharedDevice && - g_sharedDeviceLuid == static_cast(adapterLuid) && - SUCCEEDED(g_sharedDevice->GetDeviceRemovedReason())) { - context->device = g_sharedDevice; - } else { - g_sharedDevice.Reset(); - g_sharedDeviceLuid = 0; - hr = D3D12CreateDevice(context->adapter.Get(), D3D_FEATURE_LEVEL_12_0, - IID_PPV_ARGS(&context->device)); - if (FAILED(hr)) { - setHresultError("D3D12CreateDevice", hr); - delete context; - return 0; - } - g_sharedDevice = context->device; - g_sharedDeviceLuid = static_cast(adapterLuid); - } + auto device = + g_sharedDevice.getOrCreate(*adapter, static_cast(adapterLuid)); + if (!device) { + return 0; } D3D12_COMMAND_QUEUE_DESC queueDesc = {}; queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; queueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL; queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; - hr = context->device->CreateCommandQueue(&queueDesc, - IID_PPV_ARGS(&context->queue)); - if (FAILED(hr)) { - setHresultError("ID3D12Device::CreateCommandQueue", hr); - delete context; + auto queue = createCom( + "ID3D12Device::CreateCommandQueue", [&](ID3D12CommandQueue **pp) { + return (*device)->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(pp)); + }); + if (!queue) { return 0; } - hr = context->device->CreateCommandAllocator( - D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&context->commandAllocator)); - if (FAILED(hr)) { - setHresultError("ID3D12Device::CreateCommandAllocator", hr); - delete context; + auto commandAllocator = createCom( + "ID3D12Device::CreateCommandAllocator", [&](ID3D12CommandAllocator **pp) { + return (*device)->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, + IID_PPV_ARGS(pp)); + }); + if (!commandAllocator) { return 0; } - hr = context->device->CreateCommandList( - 0, D3D12_COMMAND_LIST_TYPE_DIRECT, context->commandAllocator.Get(), - nullptr, IID_PPV_ARGS(&context->commandList)); - if (FAILED(hr)) { - setHresultError("ID3D12Device::CreateCommandList", hr); - delete context; + auto commandList = createCom( + "ID3D12Device::CreateCommandList", [&](ID3D12GraphicsCommandList **pp) { + return (*device)->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, + commandAllocator->get(), nullptr, + IID_PPV_ARGS(pp)); + }); + if (!commandList) { return 0; } - hr = context->commandList->Close(); + HRESULT hr = (*commandList)->Close(); if (FAILED(hr)) { setHresultError("ID3D12GraphicsCommandList::Close(initial)", hr); - delete context; return 0; } - hr = context->device->CreateFence(0, D3D12_FENCE_FLAG_SHARED, - IID_PPV_ARGS(&context->fence)); - if (FAILED(hr)) { - setHresultError("ID3D12Device::CreateFence", hr); - delete context; + auto fence = createCom( + "ID3D12Device::CreateFence", [&](ID3D12Fence **pp) { + return (*device)->CreateFence(0, D3D12_FENCE_FLAG_SHARED, + IID_PPV_ARGS(pp)); + }); + if (!fence) { return 0; } - context->fenceEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if (!context->fenceEvent) { + + UniqueHandle fenceEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)); + if (!fenceEvent) { setError("CreateEventW failed for the D3D12 interop fence."); - delete context; return 0; } - hr = context->device->CreateSharedHandle(context->fence.Get(), nullptr, - GENERIC_ALL, nullptr, - &context->fenceSharedHandle); + + UniqueHandle fenceSharedHandle; + hr = (*device)->CreateSharedHandle(fence->get(), nullptr, GENERIC_ALL, + nullptr, fenceSharedHandle.put()); if (FAILED(hr)) { setHresultError("ID3D12Device::CreateSharedHandle(fence)", hr); - closeSharedHandles(context); - delete context; return 0; } - const bool resourcesCreated = - createSharedTexture( - context, RESOURCE_INPUT_COLOR, static_cast(renderWidth), - static_cast(renderHeight), dxgiColorFormat) && - createSharedTexture( - context, RESOURCE_INPUT_DEPTH, static_cast(renderWidth), - static_cast(renderHeight), DXGI_FORMAT_R32_FLOAT) && - createSharedTexture(context, RESOURCE_INPUT_MOTION_VECTORS, - static_cast(renderWidth), + auto inputColor = + createSharedTexture(*device, static_cast(renderWidth), + static_cast(renderHeight), dxgiColorFormat); + if (!inputColor) { + return 0; + } + auto inputDepth = + createSharedTexture(*device, static_cast(renderWidth), static_cast(renderHeight), - DXGI_FORMAT_R16G16_FLOAT) && - createSharedTexture(context, RESOURCE_INPUT_EXPOSURE, 1, 1, - DXGI_FORMAT_R16_FLOAT) && - createSharedTexture(context, RESOURCE_OUTPUT_COLOR, - static_cast(outputWidth), + DXGI_FORMAT_R32_FLOAT); + if (!inputDepth) { + return 0; + } + auto motionVectors = createSharedTexture( + *device, static_cast(renderWidth), + static_cast(renderHeight), DXGI_FORMAT_R16G16_FLOAT); + if (!motionVectors) { + return 0; + } + auto exposure = createSharedTexture(*device, 1, 1, DXGI_FORMAT_R16_FLOAT); + if (!exposure) { + return 0; + } + auto outputColor = + createSharedTexture(*device, static_cast(outputWidth), static_cast(outputHeight), dxgiColorFormat); - if (!resourcesCreated) { - closeSharedHandles(context); - delete context; + if (!outputColor) { + return 0; + } + + // Every object was created successfully; only now does the context exist, + // with the validity of all its members enforced by ComObj. + auto context = std::unique_ptr( + new (std::nothrow) D3D12InteropContext( + std::move(*adapter), std::move(*device), std::move(*queue), + std::move(*commandAllocator), std::move(*commandList), + std::move(*fence), std::move(fenceSharedHandle), + std::move(fenceEvent), + {std::move(*inputColor), std::move(*inputDepth), + std::move(*motionVectors), std::move(*exposure), + std::move(*outputColor)})); + if (!context) { + setError("Could not allocate the D3D12 interop context."); return 0; } - return static_cast(reinterpret_cast(context)); + return static_cast(reinterpret_cast(context.release())); } /** * Destroys a context created by Nd3d12CreateContext: waits for the last * submitted fence value so no in-flight GPU work references the resources, - * closes all shared Win32 handles, and frees the context. A null handle is - * a no-op. + * then frees the context, whose members (COM resources and Win32 handles) + * are released automatically by RAII. A null handle is a no-op. */ JNIEXPORT void JNICALL Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12DestroyContext( @@ -436,7 +595,6 @@ Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12Destro return; } waitForFence(context, context->lastSubmittedFenceValue); - closeSharedHandles(context); delete context; } @@ -449,7 +607,7 @@ Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetDev JNIEnv *, jclass, jlong contextHandle) { D3D12InteropContext *context = fromHandle(contextHandle); return context ? static_cast( - reinterpret_cast(context->device.Get())) + reinterpret_cast(context->device.get())) : 0; } @@ -463,7 +621,7 @@ Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetCom JNIEnv *, jclass, jlong contextHandle) { D3D12InteropContext *context = fromHandle(contextHandle); return context ? static_cast( - reinterpret_cast(context->commandList.Get())) + reinterpret_cast(context->commandList.get())) : 0; } @@ -477,7 +635,7 @@ Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetRes JNIEnv *, jclass, jlong contextHandle, jint index) { SharedTexture *texture = getResource(fromHandle(contextHandle), index); return texture ? static_cast( - reinterpret_cast(texture->resource.Get())) + reinterpret_cast(texture->resource.get())) : 0; } @@ -491,7 +649,7 @@ Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetRes JNIEnv *, jclass, jlong contextHandle, jint index) { SharedTexture *texture = getResource(fromHandle(contextHandle), index); return texture ? static_cast( - reinterpret_cast(texture->sharedHandle)) + reinterpret_cast(texture->sharedHandle.get())) : 0; } @@ -517,7 +675,7 @@ Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetFen JNIEnv *, jclass, jlong contextHandle) { D3D12InteropContext *context = fromHandle(contextHandle); return context ? static_cast( - reinterpret_cast(context->fenceSharedHandle)) + reinterpret_cast(context->fenceSharedHandle.get())) : 0; } @@ -539,7 +697,7 @@ Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12BeginF setError("Invalid D3D12 begin-frame arguments."); return E_INVALIDARG; } - if (context->recording) { + if (context->frameState == FrameState::Recording) { setError("The D3D12 command list is already recording."); return E_FAIL; } @@ -552,19 +710,19 @@ Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12BeginF setHresultError("ID3D12CommandAllocator::Reset", hr); return static_cast(hr); } - hr = context->commandList->Reset(context->commandAllocator.Get(), nullptr); + hr = context->commandList->Reset(context->commandAllocator.get(), nullptr); if (FAILED(hr)) { setHresultError("ID3D12GraphicsCommandList::Reset", hr); return static_cast(hr); } - hr = context->queue->Wait(context->fence.Get(), + hr = context->queue->Wait(context->fence.get(), static_cast(waitFenceValue)); if (FAILED(hr)) { setHresultError("ID3D12CommandQueue::Wait", hr); context->commandList->Close(); return static_cast(hr); } - context->recording = true; + context->frameState = FrameState::Recording; return S_OK; } @@ -581,21 +739,22 @@ JNIEXPORT jint JNICALL Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12ExecuteFrame( JNIEnv *, jclass, jlong contextHandle, jlong signalFenceValue) { D3D12InteropContext *context = fromHandle(contextHandle); - if (!context || !context->recording || signalFenceValue <= 0) { + if (!context || context->frameState != FrameState::Recording || + signalFenceValue <= 0) { setError("Invalid D3D12 execute-frame state or fence value."); return E_INVALIDARG; } HRESULT hr = context->commandList->Close(); - context->recording = false; + context->frameState = FrameState::Idle; if (FAILED(hr)) { setHresultError("ID3D12GraphicsCommandList::Close", hr); return static_cast(hr); } - ID3D12CommandList *commandLists[] = {context->commandList.Get()}; + ID3D12CommandList *commandLists[] = {context->commandList.get()}; context->queue->ExecuteCommandLists(1, commandLists); - hr = context->queue->Signal(context->fence.Get(), + hr = context->queue->Signal(context->fence.get(), static_cast(signalFenceValue)); if (FAILED(hr)) { setHresultError("ID3D12CommandQueue::Signal", hr); From 2e8d69eb64de196f5bf53074734a086a676ad736 Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Wed, 29 Jul 2026 15:28:30 +0800 Subject: [PATCH 08/19] reverted changes in fsr target --- native/cpp/SRNativeFSR/include/sr/fsr/sr_provider.h | 5 ++++- native/cpp/SRNativeFSR/src/sr_provider.cpp | 10 +++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/native/cpp/SRNativeFSR/include/sr/fsr/sr_provider.h b/native/cpp/SRNativeFSR/include/sr/fsr/sr_provider.h index 06ccb10b..86e014db 100644 --- a/native/cpp/SRNativeFSR/include/sr/fsr/sr_provider.h +++ b/native/cpp/SRNativeFSR/include/sr/fsr/sr_provider.h @@ -1,8 +1,11 @@ #pragma once +#include #include "sr/sr_api.h" #include "sr/sr_modules.h" +#include "fsr2.h" +#include "fsr3.h" extern "C" { SR_API SRReturnCode srGetFfxFSRUpscaleProviders(SRUpscaleProvider * outProvider); SR_API SRReturnCode srGetFfxFSRUpscaleProvidersCount(uint32_t * outCount); -} +} \ No newline at end of file diff --git a/native/cpp/SRNativeFSR/src/sr_provider.cpp b/native/cpp/SRNativeFSR/src/sr_provider.cpp index 9dac7b6e..2637adfe 100644 --- a/native/cpp/SRNativeFSR/src/sr_provider.cpp +++ b/native/cpp/SRNativeFSR/src/sr_provider.cpp @@ -1,10 +1,6 @@ #include "sr/fsr/sr_provider.h" -#include "sr/fsr/fsr2.h" -#include "sr/fsr/fsr3.h" -static constexpr uint32_t PROVIDER_COUNT = 2; - -static SRUpscaleProvider g_providers[PROVIDER_COUNT]; +static SRUpscaleProvider g_providers[2]; static bool g_initialized = false; static void ensureInitialized() { @@ -28,7 +24,7 @@ extern "C" { SR_API SRReturnCode srGetFfxFSRUpscaleProvidersCount(uint32_t *outCount) { ensureInitialized(); - *outCount = PROVIDER_COUNT; + *outCount = 2; return (SRReturnCode) SR_RETURN_CODE_OK; } -} +} \ No newline at end of file From 26f2e6ee12e3e6b7cf64d7d9eb576a4b47f5cdda Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Wed, 29 Jul 2026 15:56:26 +0800 Subject: [PATCH 09/19] moved d3d12 to dedicated library --- .../common/upscale/AlgorithmDescriptions.java | 2 ++ .../common/upscale/D3D12InteropAlgorithm.java | 5 +++ .../core/NativeLibManager.java | 9 +++++ native/cpp/CMakeLists.txt | 4 +++ .../cpp/SRNativeD3D12Interop/CMakeLists.txt | 33 +++++++++++++++++++ .../src/d3d12_interop.cpp | 0 native/cpp/SRNativeFSR/CMakeLists.txt | 2 +- native/cpp/SRNativeMain/CMakeLists.txt | 3 -- native/cpp/docs/ffx_api_d3d12_prototype.md | 4 ++- 9 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 native/cpp/SRNativeD3D12Interop/CMakeLists.txt rename native/cpp/{SRNativeMain => SRNativeD3D12Interop}/src/d3d12_interop.cpp (100%) diff --git a/common/src/main/java/io/homo/superresolution/common/upscale/AlgorithmDescriptions.java b/common/src/main/java/io/homo/superresolution/common/upscale/AlgorithmDescriptions.java index d69c73a7..acdca8e7 100644 --- a/common/src/main/java/io/homo/superresolution/common/upscale/AlgorithmDescriptions.java +++ b/common/src/main/java/io/homo/superresolution/common/upscale/AlgorithmDescriptions.java @@ -40,6 +40,7 @@ import io.homo.superresolution.common.upscale.sgsr.v1.Sgsr1; import io.homo.superresolution.common.upscale.sgsr.v2.Sgsr2; import io.homo.superresolution.common.upscale.xess.XeSS; +import io.homo.superresolution.core.NativeLibManager; import io.homo.superresolution.core.graphics.opengl.Gl; import net.minecraft.network.chat.Component; @@ -197,6 +198,7 @@ public class AlgorithmDescriptions { .requiredGlExtension("GL_EXT_semaphore_win32") .glMajorVersion(4) .glMinorVersion(6) + .isTrue(NativeLibManager::d3d12InteropAvailable) ) .extraResources( ExtraResources.builder() diff --git a/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java b/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java index 7da83e39..6a74c06e 100644 --- a/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java +++ b/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java @@ -15,6 +15,7 @@ import io.homo.superresolution.common.config.SuperResolutionConfig; import io.homo.superresolution.common.minecraft.handler.RenderHandlerManager; import io.homo.superresolution.common.workmode.SRWorkModeManager; +import io.homo.superresolution.core.NativeLibManager; import io.homo.superresolution.core.RenderSystems; import io.homo.superresolution.core.graphics.d3d12.D3D12InteropContext; import io.homo.superresolution.core.graphics.d3d12.D3D12InteropSemaphore; @@ -67,6 +68,10 @@ protected boolean isD3D12UpscalerReady() { @Override public void initialize(InitializationDescription desc) { + if (!NativeLibManager.d3d12InteropAvailable()) { + throw new IllegalStateException( + "The optional D3D12 interop native library is unavailable."); + } this.initDesc = desc; try { createResources(); diff --git a/common/src/main/java/io/homo/superresolution/core/NativeLibManager.java b/common/src/main/java/io/homo/superresolution/core/NativeLibManager.java index 443c9bb8..fc6f8cb0 100644 --- a/common/src/main/java/io/homo/superresolution/core/NativeLibManager.java +++ b/common/src/main/java/io/homo/superresolution/core/NativeLibManager.java @@ -47,6 +47,7 @@ public class NativeLibManager { #endif private static final List libs = new ArrayList<>(); public static NativeLib LIB_SUPER_RESOLUTION = null; + public static NativeLib LIB_SUPER_RESOLUTION_D3D12_INTEROP = null; public static NativeLib LIB_SUPER_RESOLUTION_FSR = null; public static NativeLib LIB_SUPER_RESOLUTION_FSR4 = null; public static NativeLib LIB_SUPER_RESOLUTION_XESS = null; @@ -67,6 +68,8 @@ public class NativeLibManager { if (operatingSystem.type == OperatingSystemType.WINDOWS && operatingSystem.arch == SystemArchitecture.X86_64) { boolean presentation = VulkanPresentationFeature.shouldInitializeStreamline(); LIB_SUPER_RESOLUTION = new NativeLib("SuperResolution", true, true); + LIB_SUPER_RESOLUTION_D3D12_INTEROP = + new NativeLib("SuperResolutionD3D12Interop", true, false); LIB_SUPER_RESOLUTION_FSR = new NativeLib("SuperResolutionFSR", false, false); LIB_SUPER_RESOLUTION_FSR4 = new NativeLib("SuperResolutionFSR4", false, false); LIB_SUPER_RESOLUTION_XESS = new NativeLib("SuperResolutionXeSS", false, false); @@ -79,6 +82,7 @@ public class NativeLibManager { LIB_STREAMLINE_PCL = new NativeLib("sl.pcl", false, presentation, true); LIB_STREAMLINE_NVNGX_REFLEX = new NativeLib("NvLowLatencyVk", false, presentation, true); libs.add(LIB_SUPER_RESOLUTION); + libs.add(LIB_SUPER_RESOLUTION_D3D12_INTEROP); libs.add(LIB_SUPER_RESOLUTION_FSR); libs.add(LIB_SUPER_RESOLUTION_FSR4); libs.add(LIB_SUPER_RESOLUTION_XESS); @@ -112,6 +116,11 @@ public static boolean nativeApiAvailable() { return nativeApiAvailable; } + public static boolean d3d12InteropAvailable() { + return LIB_SUPER_RESOLUTION_D3D12_INTEROP != null + && LIB_SUPER_RESOLUTION_D3D12_INTEROP.available; + } + public static void createLibraryDir(Path path) { File dir = path.toFile(); if (!dir.exists() && !dir.mkdirs()) { diff --git a/native/cpp/CMakeLists.txt b/native/cpp/CMakeLists.txt index 80fbb150..a651ee7b 100644 --- a/native/cpp/CMakeLists.txt +++ b/native/cpp/CMakeLists.txt @@ -3,6 +3,7 @@ project(SuperResolution) option(SR_FSR "启用以支持FSR" ON) option(SR_FSR4 "启用以支持FSR4(FFX API,仅Windows D3D12)" ON) +option(SR_D3D12_INTEROP "启用OpenGL与D3D12互操作(仅Windows)" ON) option(SR_XESS "启用以支持XESS" ON) option(SR_NGX "启用以支持NGX绑定" ON) option(SR_STREAMLINE "启用以支持Streamline" ON) @@ -78,6 +79,9 @@ include_directories( ) add_subdirectory("SRNativeMain") +if(SR_D3D12_INTEROP AND ON_WINDOWS) + add_subdirectory("SRNativeD3D12Interop") +endif() if(SR_FSR) add_subdirectory("SRNativeFSR") endif() diff --git a/native/cpp/SRNativeD3D12Interop/CMakeLists.txt b/native/cpp/SRNativeD3D12Interop/CMakeLists.txt new file mode 100644 index 00000000..c2c2bf1f --- /dev/null +++ b/native/cpp/SRNativeD3D12Interop/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.15) +project(SuperResolutionNativeD3D12Interop) +message(STATUS "生成D3D12互操作模块") + +if (NOT ON_WINDOWS) + message(FATAL_ERROR "${LIB_PLATFORM} 平台不支持D3D12互操作") +endif () + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if (MSVC) + add_compile_options(/utf-8 /ZI) +endif () +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +aux_source_directory(${PROJECT_SOURCE_DIR}/src ALL_SRC) +add_library(SR_D3D12_INTEROP_LIB SHARED ${ALL_SRC}) + +target_compile_definitions(SR_D3D12_INTEROP_LIB PRIVATE + ON_WIN64 + _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR +) + +set_target_properties( + SR_D3D12_INTEROP_LIB + PROPERTIES OUTPUT_NAME "libSuperResolutionD3D12Interop+${LIB_PLATFORM}+${SR_BUILD_TYPE}" +) + +target_link_libraries(SR_D3D12_INTEROP_LIB + d3d12 + dxgi +) diff --git a/native/cpp/SRNativeMain/src/d3d12_interop.cpp b/native/cpp/SRNativeD3D12Interop/src/d3d12_interop.cpp similarity index 100% rename from native/cpp/SRNativeMain/src/d3d12_interop.cpp rename to native/cpp/SRNativeD3D12Interop/src/d3d12_interop.cpp diff --git a/native/cpp/SRNativeFSR/CMakeLists.txt b/native/cpp/SRNativeFSR/CMakeLists.txt index 72548978..5893c1dd 100644 --- a/native/cpp/SRNativeFSR/CMakeLists.txt +++ b/native/cpp/SRNativeFSR/CMakeLists.txt @@ -177,4 +177,4 @@ target_link_libraries(SR_FSR_LIB ${FFX_LINK_FSR3UP} ${FFX_LINK_FSR3} ${Vulkan_LIBRARIES} -) +) \ No newline at end of file diff --git a/native/cpp/SRNativeMain/CMakeLists.txt b/native/cpp/SRNativeMain/CMakeLists.txt index be34061a..41829cad 100644 --- a/native/cpp/SRNativeMain/CMakeLists.txt +++ b/native/cpp/SRNativeMain/CMakeLists.txt @@ -76,9 +76,6 @@ target_link_libraries(SR_MAIN_LIB SPIRV freetype ) -if(WIN32) - target_link_libraries(SR_MAIN_LIB d3d12 dxgi) -endif() if(NOT WIN32) elseif(CMAKE_SYSTEM_NAME MATCHES "Linux") target_link_libraries(SR_MAIN_LIB -lstdc++fs -pthread -ldl) diff --git a/native/cpp/docs/ffx_api_d3d12_prototype.md b/native/cpp/docs/ffx_api_d3d12_prototype.md index 841a1c75..ac7420ee 100644 --- a/native/cpp/docs/ffx_api_d3d12_prototype.md +++ b/native/cpp/docs/ffx_api_d3d12_prototype.md @@ -61,7 +61,9 @@ states)` constructor. ## Renderer interop `D3D12InteropAlgorithm` implements the renderer-facing half as a sibling to -`VulkanInteropAlgorithm`. The initial implementation deliberately uses a +`VulkanInteropAlgorithm`. Its JNI implementation lives in the reusable +`SRNativeD3D12Interop` module rather than the FSR4 provider module. The +initial implementation deliberately uses a single serial resource set: 1. query OpenGL's `GL_DEVICE_LUID_EXT` and create D3D12 on the matching DXGI From 7c7c3d83227cc03464eaae1bce494e7bed41687b Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Wed, 29 Jul 2026 16:17:48 +0800 Subject: [PATCH 10/19] splitted --- .../src/d3d12_context.cpp | 357 +++++++++ .../SRNativeD3D12Interop/src/d3d12_frame.cpp | 122 +++ .../src/d3d12_interop.cpp | 732 ++---------------- .../src/d3d12_interop_internal.h | 198 +++++ 4 files changed, 743 insertions(+), 666 deletions(-) create mode 100644 native/cpp/SRNativeD3D12Interop/src/d3d12_context.cpp create mode 100644 native/cpp/SRNativeD3D12Interop/src/d3d12_frame.cpp create mode 100644 native/cpp/SRNativeD3D12Interop/src/d3d12_interop_internal.h diff --git a/native/cpp/SRNativeD3D12Interop/src/d3d12_context.cpp b/native/cpp/SRNativeD3D12Interop/src/d3d12_context.cpp new file mode 100644 index 00000000..ca466e3d --- /dev/null +++ b/native/cpp/SRNativeD3D12Interop/src/d3d12_context.cpp @@ -0,0 +1,357 @@ +#include "d3d12_interop_internal.h" + +#if defined(ON_WIN64) + +#include +#include +#include + +namespace sr::d3d12 { +namespace { + +// Values mirror SRSurfaceFormat / FfxApiSurfaceFormat on the Java and +// FidelityFX sides of the boundary. +constexpr int SR_FORMAT_R16G16B16A16_FLOAT = 4; +constexpr int SR_FORMAT_R8G8B8A8_UNORM = 10; +constexpr int SR_FORMAT_R11G11B10_FLOAT = 16; +constexpr int SR_FORMAT_R16G16_FLOAT = 18; +constexpr int SR_FORMAT_R16_FLOAT = 21; +constexpr int SR_FORMAT_R32_FLOAT = 28; + +thread_local std::string g_lastError; + +/** + * Process-wide D3D12 device cache with its synchronization bundled in. + * + * A device is reused only while it belongs to the requested adapter and has + * not been removed. Keeping the mutex private prevents unsynchronized access + * to the cached COM pointer and its associated LUID. + */ +class SharedDevice { +public: + std::optional> + getOrCreate(const ComObj &adapter, uint64_t adapterLuid) { + std::lock_guard lock(mutex_); + if (device_ && luid_ == adapterLuid && + SUCCEEDED(device_->GetDeviceRemovedReason())) { + return ComObj::from(device_); + } + + device_.Reset(); + luid_ = 0; + auto device = + createCom("D3D12CreateDevice", [&](ID3D12Device **pp) { + return D3D12CreateDevice(adapter.get(), D3D_FEATURE_LEVEL_12_0, + IID_PPV_ARGS(pp)); + }); + if (!device) { + return std::nullopt; + } + + device_ = device->share(); + luid_ = adapterLuid; + return device; + } + +private: + std::mutex mutex_; + ComPtr device_; + uint64_t luid_ = 0; +}; + +SharedDevice g_sharedDevice; + +/** + * Compares a DXGI LUID with the packed 64-bit representation passed through + * JNI: LowPart occupies the low 32 bits and HighPart the high 32 bits. + */ +bool sameLuid(const LUID &left, uint64_t right) { + const uint64_t leftValue = + static_cast(left.LowPart) | + (static_cast(static_cast(left.HighPart)) << 32); + return leftValue == right; +} + +/** + * Maps an SRSurfaceFormat/FfxApiSurfaceFormat integer to DXGI_FORMAT. + * Unsupported values deliberately map to DXGI_FORMAT_UNKNOWN so context + * construction rejects them before allocating GPU resources. + */ +DXGI_FORMAT toDxgiFormat(int format) { + switch (format) { + case SR_FORMAT_R16G16B16A16_FLOAT: + return DXGI_FORMAT_R16G16B16A16_FLOAT; + case SR_FORMAT_R8G8B8A8_UNORM: + return DXGI_FORMAT_R8G8B8A8_UNORM; + case SR_FORMAT_R11G11B10_FLOAT: + return DXGI_FORMAT_R11G11B10_FLOAT; + case SR_FORMAT_R16G16_FLOAT: + return DXGI_FORMAT_R16G16_FLOAT; + case SR_FORMAT_R16_FLOAT: + return DXGI_FORMAT_R16_FLOAT; + case SR_FORMAT_R32_FLOAT: + return DXGI_FORMAT_R32_FLOAT; + default: + return DXGI_FORMAT_UNKNOWN; + } +} + +/** + * Creates one committed, UAV-capable Texture2D on a shared DEFAULT heap. + * + * The returned value contains the resource, its exported NT handle, and the + * driver-reported allocation size needed by OpenGL memory import. Any failure + * releases intermediate objects automatically and records an error. + */ +std::optional +createSharedTexture(const ComObj &device, uint32_t width, + uint32_t height, DXGI_FORMAT format) { + if (width == 0 || height == 0 || format == DXGI_FORMAT_UNKNOWN) { + setError("Invalid shared D3D12 texture description."); + return std::nullopt; + } + + D3D12_HEAP_PROPERTIES heapProperties = {}; + heapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; + heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapProperties.CreationNodeMask = 1; + heapProperties.VisibleNodeMask = 1; + + D3D12_RESOURCE_DESC resourceDesc = {}; + resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + resourceDesc.Width = width; + resourceDesc.Height = height; + resourceDesc.DepthOrArraySize = 1; + resourceDesc.MipLevels = 1; + resourceDesc.Format = format; + resourceDesc.SampleDesc.Count = 1; + resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + + auto resource = createCom( + "ID3D12Device::CreateCommittedResource", [&](ID3D12Resource **pp) { + return device->CreateCommittedResource( + &heapProperties, D3D12_HEAP_FLAG_SHARED, &resourceDesc, + D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(pp)); + }); + if (!resource) { + return std::nullopt; + } + + const D3D12_RESOURCE_ALLOCATION_INFO allocationInfo = + device->GetResourceAllocationInfo(0, 1, &resourceDesc); + if (allocationInfo.SizeInBytes == 0 || + allocationInfo.SizeInBytes == UINT64_MAX) { + setError("D3D12 returned an invalid shared resource allocation size."); + return std::nullopt; + } + + UniqueHandle sharedHandle; + const HRESULT hr = device->CreateSharedHandle( + resource->get(), nullptr, GENERIC_ALL, nullptr, sharedHandle.put()); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateSharedHandle(resource)", hr); + return std::nullopt; + } + + return SharedTexture{std::move(*resource), std::move(sharedHandle), + allocationInfo.SizeInBytes}; +} + +/** + * Finds the non-software DXGI adapter whose LUID matches the OpenGL device. + * Matching adapters is required for D3D12/OpenGL resource sharing. + */ +std::optional> findAdapter(uint64_t adapterLuid) { + auto factory = + createCom("CreateDXGIFactory1", [](IDXGIFactory6 **pp) { + return CreateDXGIFactory1(IID_PPV_ARGS(pp)); + }); + if (!factory) { + return std::nullopt; + } + + for (UINT index = 0;; ++index) { + ComPtr candidate; + const HRESULT hr = (*factory)->EnumAdapters1(index, &candidate); + if (hr == DXGI_ERROR_NOT_FOUND) { + break; + } + if (FAILED(hr)) { + setHresultError("IDXGIFactory1::EnumAdapters1", hr); + return std::nullopt; + } + + DXGI_ADAPTER_DESC1 desc = {}; + if (SUCCEEDED(candidate->GetDesc1(&desc)) && + (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) == 0 && + sameLuid(desc.AdapterLuid, adapterLuid)) { + return ComObj::from(std::move(candidate)); + } + } + + setError("No D3D12 adapter matches the OpenGL device LUID."); + return std::nullopt; +} + +} // namespace + +// Error messages are thread-local because Java queries the error on the same +// thread immediately after a failed native operation. +void clearError() { g_lastError.clear(); } + +void setError(const char *message) { + g_lastError = message ? message : "Unknown D3D12 interop error."; +} + +void setHresultError(const char *operation, HRESULT hr) { + char buffer[256] = {}; + std::snprintf(buffer, sizeof(buffer), "%s failed with HRESULT 0x%08lX.", + operation, static_cast(hr)); + setError(buffer); +} + +const std::string &lastError() { return g_lastError; } + +/** + * Builds a fully usable interop context transactionally. + * + * The adapter and cached device are followed by the direct command queue, + * allocator/list pair, shared fence and event, then the five textures in Java + * resource-index order: + * + * input color, input depth, motion vectors, exposure, output color. + * + * The context itself is allocated only after every prerequisite succeeds, so + * cleanup on any earlier return is handled entirely by RAII. + */ +std::unique_ptr +createContext(uint64_t adapterLuid, uint32_t renderWidth, uint32_t renderHeight, + uint32_t outputWidth, uint32_t outputHeight, int colorFormat) { + clearError(); + if (adapterLuid == 0 || renderWidth == 0 || renderHeight == 0 || + outputWidth == 0 || outputHeight == 0) { + setError("Invalid D3D12 interop context dimensions or adapter LUID."); + return nullptr; + } + + const DXGI_FORMAT dxgiColorFormat = toDxgiFormat(colorFormat); + if (dxgiColorFormat == DXGI_FORMAT_UNKNOWN) { + setError("The configured internal color format is not supported by D3D12 " + "interop."); + return nullptr; + } + + auto adapter = findAdapter(adapterLuid); + if (!adapter) { + return nullptr; + } + + auto device = g_sharedDevice.getOrCreate(*adapter, adapterLuid); + if (!device) { + return nullptr; + } + + D3D12_COMMAND_QUEUE_DESC queueDesc = {}; + queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + queueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL; + queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + auto queue = createCom( + "ID3D12Device::CreateCommandQueue", [&](ID3D12CommandQueue **pp) { + return (*device)->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(pp)); + }); + if (!queue) { + return nullptr; + } + + auto commandAllocator = createCom( + "ID3D12Device::CreateCommandAllocator", [&](ID3D12CommandAllocator **pp) { + return (*device)->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, + IID_PPV_ARGS(pp)); + }); + if (!commandAllocator) { + return nullptr; + } + + auto commandList = createCom( + "ID3D12Device::CreateCommandList", [&](ID3D12GraphicsCommandList **pp) { + return (*device)->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, + commandAllocator->get(), nullptr, + IID_PPV_ARGS(pp)); + }); + if (!commandList) { + return nullptr; + } + HRESULT hr = (*commandList)->Close(); + if (FAILED(hr)) { + setHresultError("ID3D12GraphicsCommandList::Close(initial)", hr); + return nullptr; + } + + auto fence = createCom( + "ID3D12Device::CreateFence", [&](ID3D12Fence **pp) { + return (*device)->CreateFence(0, D3D12_FENCE_FLAG_SHARED, + IID_PPV_ARGS(pp)); + }); + if (!fence) { + return nullptr; + } + + UniqueHandle fenceEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)); + if (!fenceEvent) { + setError("CreateEventW failed for the D3D12 interop fence."); + return nullptr; + } + + UniqueHandle fenceSharedHandle; + hr = (*device)->CreateSharedHandle(fence->get(), nullptr, GENERIC_ALL, + nullptr, fenceSharedHandle.put()); + if (FAILED(hr)) { + setHresultError("ID3D12Device::CreateSharedHandle(fence)", hr); + return nullptr; + } + + auto inputColor = + createSharedTexture(*device, renderWidth, renderHeight, dxgiColorFormat); + if (!inputColor) { + return nullptr; + } + auto inputDepth = createSharedTexture(*device, renderWidth, renderHeight, + DXGI_FORMAT_R32_FLOAT); + if (!inputDepth) { + return nullptr; + } + auto motionVectors = createSharedTexture(*device, renderWidth, renderHeight, + DXGI_FORMAT_R16G16_FLOAT); + if (!motionVectors) { + return nullptr; + } + auto exposure = createSharedTexture(*device, 1, 1, DXGI_FORMAT_R16_FLOAT); + if (!exposure) { + return nullptr; + } + auto outputColor = + createSharedTexture(*device, outputWidth, outputHeight, dxgiColorFormat); + if (!outputColor) { + return nullptr; + } + + auto context = std::unique_ptr( + new (std::nothrow) D3D12InteropContext( + std::move(*adapter), std::move(*device), std::move(*queue), + std::move(*commandAllocator), std::move(*commandList), + std::move(*fence), std::move(fenceSharedHandle), + std::move(fenceEvent), + {std::move(*inputColor), std::move(*inputDepth), + std::move(*motionVectors), std::move(*exposure), + std::move(*outputColor)})); + if (!context) { + setError("Could not allocate the D3D12 interop context."); + } + return context; +} + +} // namespace sr::d3d12 + +#endif diff --git a/native/cpp/SRNativeD3D12Interop/src/d3d12_frame.cpp b/native/cpp/SRNativeD3D12Interop/src/d3d12_frame.cpp new file mode 100644 index 00000000..eb904cf7 --- /dev/null +++ b/native/cpp/SRNativeD3D12Interop/src/d3d12_frame.cpp @@ -0,0 +1,122 @@ +#include "d3d12_interop_internal.h" + +#if defined(ON_WIN64) + +namespace sr::d3d12 { + +/** + * Blocks the calling CPU thread until the shared fence reaches value. + * + * A null context, zero value, or already-completed value needs no wait. Other + * cases arm the context's reusable event and wait indefinitely for D3D12 to + * signal it. + */ +bool waitForFence(D3D12InteropContext *context, uint64_t value) { + if (!context || value == 0 || context->fence->GetCompletedValue() >= value) { + return true; + } + + const HRESULT hr = + context->fence->SetEventOnCompletion(value, context->fenceEvent.get()); + if (FAILED(hr)) { + setHresultError("ID3D12Fence::SetEventOnCompletion", hr); + return false; + } + if (WaitForSingleObject(context->fenceEvent.get(), INFINITE) != + WAIT_OBJECT_0) { + setError("Waiting for the D3D12 interop fence failed."); + return false; + } + return true; +} + +/** + * Starts recording a frame. + * + * The CPU first waits for the previous submission so the allocator can be + * reset safely. The D3D12 queue then waits for the value signaled by OpenGL, + * ensuring that imported input textures are ready before upscaling commands + * execute. + */ +HRESULT beginFrame(D3D12InteropContext *context, uint64_t waitFenceValue) { + if (!context || waitFenceValue == 0) { + setError("Invalid D3D12 begin-frame arguments."); + return E_INVALIDARG; + } + if (context->frameState == FrameState::Recording) { + setError("The D3D12 command list is already recording."); + return E_FAIL; + } + if (!waitForFence(context, context->lastSubmittedFenceValue)) { + return E_FAIL; + } + + HRESULT hr = context->commandAllocator->Reset(); + if (FAILED(hr)) { + setHresultError("ID3D12CommandAllocator::Reset", hr); + return hr; + } + hr = context->commandList->Reset(context->commandAllocator.get(), nullptr); + if (FAILED(hr)) { + setHresultError("ID3D12GraphicsCommandList::Reset", hr); + return hr; + } + hr = context->queue->Wait(context->fence.get(), waitFenceValue); + if (FAILED(hr)) { + setHresultError("ID3D12CommandQueue::Wait", hr); + context->commandList->Close(); + return hr; + } + + context->frameState = FrameState::Recording; + return S_OK; +} + +/** + * Ends and submits a frame. + * + * Closing the command list transitions the context back to Idle. After + * submission, the queue signals the shared fence value that OpenGL will wait + * on before consuming the output texture. + */ +HRESULT executeFrame(D3D12InteropContext *context, uint64_t signalFenceValue) { + if (!context || context->frameState != FrameState::Recording || + signalFenceValue == 0) { + setError("Invalid D3D12 execute-frame state or fence value."); + return E_INVALIDARG; + } + + HRESULT hr = context->commandList->Close(); + context->frameState = FrameState::Idle; + if (FAILED(hr)) { + setHresultError("ID3D12GraphicsCommandList::Close", hr); + return hr; + } + + ID3D12CommandList *commandLists[] = {context->commandList.get()}; + context->queue->ExecuteCommandLists(1, commandLists); + hr = context->queue->Signal(context->fence.get(), signalFenceValue); + if (FAILED(hr)) { + setHresultError("ID3D12CommandQueue::Signal", hr); + return hr; + } + + context->lastSubmittedFenceValue = signalFenceValue; + return S_OK; +} + +/** + * Waits until the most recently submitted frame has completed. + */ +HRESULT waitIdle(D3D12InteropContext *context) { + if (!context) { + setError("The D3D12 interop context is null."); + return E_INVALIDARG; + } + return waitForFence(context, context->lastSubmittedFenceValue) ? S_OK + : E_FAIL; +} + +} // namespace sr::d3d12 + +#endif diff --git a/native/cpp/SRNativeD3D12Interop/src/d3d12_interop.cpp b/native/cpp/SRNativeD3D12Interop/src/d3d12_interop.cpp index 3523c2a6..ff381276 100644 --- a/native/cpp/SRNativeD3D12Interop/src/d3d12_interop.cpp +++ b/native/cpp/SRNativeD3D12Interop/src/d3d12_interop.cpp @@ -1,591 +1,68 @@ #include -#if defined(ON_WIN64) - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include +#include "d3d12_interop_internal.h" -#include -#include -#include +#if defined(ON_WIN64) -#include #include -#include -#include -#include -#include -#include -#include -#include - -using Microsoft::WRL::ComPtr; namespace { -constexpr uint32_t RESOURCE_COUNT = 5; -// Slot order of D3D12InteropContext::resources; mirrors the index constants -// on the Java side, which passes them to Nd3d12GetResource*. -[[maybe_unused]] constexpr uint32_t RESOURCE_INPUT_COLOR = 0; -[[maybe_unused]] constexpr uint32_t RESOURCE_INPUT_DEPTH = 1; -[[maybe_unused]] constexpr uint32_t RESOURCE_INPUT_MOTION_VECTORS = 2; -[[maybe_unused]] constexpr uint32_t RESOURCE_INPUT_EXPOSURE = 3; -[[maybe_unused]] constexpr uint32_t RESOURCE_OUTPUT_COLOR = 4; - -// Values mirror SRSurfaceFormat / FfxApiSurfaceFormat. -constexpr int SR_FORMAT_R16G16B16A16_FLOAT = 4; -constexpr int SR_FORMAT_R8G8B8A8_UNORM = 10; -constexpr int SR_FORMAT_R11G11B10_FLOAT = 16; -constexpr int SR_FORMAT_R16G16_FLOAT = 18; -constexpr int SR_FORMAT_R16_FLOAT = 21; -constexpr int SR_FORMAT_R32_FLOAT = 28; - -/** - * Minimal RAII wrapper for a Win32 HANDLE, in the spirit of - * wil::unique_handle (which this project does not depend on): the handle is - * closed automatically on destruction, and the wrapper is move-only so a - * raw void* can no longer be copied around or leaked by accident. - */ -class UniqueHandle { -public: - UniqueHandle() = default; - explicit UniqueHandle(HANDLE handle) : handle_(handle) {} - ~UniqueHandle() { reset(); } - - UniqueHandle(const UniqueHandle &) = delete; - UniqueHandle &operator=(const UniqueHandle &) = delete; - - UniqueHandle(UniqueHandle &&other) noexcept : handle_(other.release()) {} - UniqueHandle &operator=(UniqueHandle &&other) noexcept { - reset(other.release()); - return *this; - } - - HANDLE get() const { return handle_; } - explicit operator bool() const { return handle_ != nullptr; } - - // Frees the current handle and returns storage for an out-parameter - // (e.g. ID3D12Device::CreateSharedHandle). - HANDLE *put() { - reset(); - return &handle_; - } - HANDLE release() { - HANDLE handle = handle_; - handle_ = nullptr; - return handle; - } - void reset(HANDLE handle = nullptr) { - if (handle_) { - CloseHandle(handle_); - } - handle_ = handle; - } - -private: - HANDLE handle_ = nullptr; -}; - -// Lifecycle of the command list across a frame: Idle outside of a frame, -// Recording between a successful Nd3d12BeginFrame and Nd3d12ExecuteFrame. -enum class FrameState { Idle, Recording }; - -/** - * A COM interface pointer that is non-null by construction. - * convention: the only way to obtain one is the checked from() factory, so - * holders such as D3D12InteropContext can dereference their members without - * any null check. Move-only, like the ownership it wraps. - */ -template class ComObj { -public: - ComObj(const ComObj &) = delete; - ComObj &operator=(const ComObj &) = delete; - ComObj(ComObj &&) noexcept = default; - ComObj &operator=(ComObj &&) noexcept = default; - - /** - * Checked conversion from a nullable ComPtr: returns an engaged NonNullCom - * when ptr holds an interface, std::nullopt otherwise. - */ - static std::optional from(ComPtr ptr) { - if (!ptr) { - return std::nullopt; - } - return ComObj(std::move(ptr)); - } - - T *get() const { return ptr_.Get(); } - T *operator->() const { return ptr_.Get(); } - - // Returns an additional owning reference (AddRef), mirroring Rc::clone. - ComPtr share() const { return ptr_; } - -private: - explicit ComObj(ComPtr ptr) : ptr_(std::move(ptr)) {} - ComPtr ptr_; -}; - -thread_local std::string g_lastError; - -/** - * Records an error message in the thread-local error string, which can later - * be retrieved from Java via Nd3d12GetLastError. A null message is replaced - * with a generic fallback. - */ -void setError(const char *message) { - g_lastError = message ? message : "Unknown D3D12 interop error."; -} - -/** - * Formats " failed with HRESULT 0x........" and stores it as the - * thread-local last-error string (see setError). - */ -void setHresultError(const char *operation, HRESULT hr) { - char buffer[256] = {}; - std::snprintf(buffer, sizeof(buffer), "%s failed with HRESULT 0x%08lX.", - operation, static_cast(hr)); - setError(buffer); -} - -/** - * Runs a COM creation call (one taking a T** out-parameter and returning an - * HRESULT) and returns the created interface as a ComObj. - */ -template -std::optional> createCom(const char *operation, Create &&create) { - ComPtr ptr; - const HRESULT hr = create(ptr.ReleaseAndGetAddressOf()); - if (FAILED(hr)) { - setHresultError(operation, hr); - return std::nullopt; - } - return ComObj::from(std::move(ptr)); -} - -/** - * A fully-initialized shared interop texture: the factory createSharedTexture - * only produces one once the resource, its NT shared handle, and the - * allocation size all exist, so the half-initialized state is not - * representable. - */ -struct SharedTexture { - ComObj resource; - UniqueHandle sharedHandle; - uint64_t allocationSize = 0; -}; - -struct D3D12InteropContext { - /** - * All COM members are ComObj: a context only exists once every object - * was created successfully, so the frame and wait functions can dereference - * them unconditionally — validity is enforced by the type, not by - * convention. - */ - D3D12InteropContext(ComObj adapter, - ComObj device, - ComObj queue, - ComObj commandAllocator, - ComObj commandList, - ComObj fence, - UniqueHandle fenceSharedHandle, UniqueHandle fenceEvent, - std::array resources) - : adapter(std::move(adapter)), device(std::move(device)), - queue(std::move(queue)), commandAllocator(std::move(commandAllocator)), - commandList(std::move(commandList)), fence(std::move(fence)), - fenceSharedHandle(std::move(fenceSharedHandle)), - fenceEvent(std::move(fenceEvent)), resources(std::move(resources)) {} - - ComObj adapter; - ComObj device; - ComObj queue; - ComObj commandAllocator; - ComObj commandList; - ComObj fence; - UniqueHandle fenceSharedHandle; - UniqueHandle fenceEvent; - uint64_t lastSubmittedFenceValue = 0; - FrameState frameState = FrameState::Idle; - std::array resources; -}; - -/** - * Process-wide D3D12 device cache with its mutex bundled in, in the spirit - * of Rust's Mutex: the data and the lock protecting it live in one object, - * so the cached device can only be touched through locked access. - */ -class SharedDevice { -public: - /** - * Returns the cached device when it matches adapterLuid and has not been - * removed; otherwise creates a new device on the given adapter and caches - * it. On creation failure the thread-local error string is set and an - * empty optional is returned. - */ - std::optional> - getOrCreate(const ComObj &adapter, uint64_t adapterLuid) { - std::lock_guard lock(mutex_); - if (device_ && luid_ == adapterLuid && - SUCCEEDED(device_->GetDeviceRemovedReason())) { - return ComObj::from(device_); - } - device_.Reset(); - luid_ = 0; - auto device = - createCom("D3D12CreateDevice", [&](ID3D12Device **pp) { - return D3D12CreateDevice(adapter.get(), D3D_FEATURE_LEVEL_12_0, - IID_PPV_ARGS(pp)); - }); - if (!device) { - return std::nullopt; - } - device_ = device->share(); - luid_ = adapterLuid; - return device; - } - -private: - std::mutex mutex_; - ComPtr device_; - uint64_t luid_ = 0; -}; - -SharedDevice g_sharedDevice; - -/** - * Compares a DXGI LUID against its packed 64-bit representation - * (LowPart in the low 32 bits, HighPart in the high 32 bits), which is how - * the adapter LUID is passed across the JNI boundary. - */ -bool sameLuid(const LUID &left, uint64_t right) { - const uint64_t leftValue = - static_cast(left.LowPart) | - (static_cast(static_cast(left.HighPart)) << 32); - return leftValue == right; -} - -/** - * Maps an SRSurfaceFormat/FfxApiSurfaceFormat value (as passed from Java) to - * the matching DXGI_FORMAT. Returns DXGI_FORMAT_UNKNOWN for unsupported - * formats so callers can reject them. - */ -DXGI_FORMAT toDxgiFormat(int format) { - switch (format) { - case SR_FORMAT_R16G16B16A16_FLOAT: - return DXGI_FORMAT_R16G16B16A16_FLOAT; - case SR_FORMAT_R8G8B8A8_UNORM: - return DXGI_FORMAT_R8G8B8A8_UNORM; - case SR_FORMAT_R11G11B10_FLOAT: - return DXGI_FORMAT_R11G11B10_FLOAT; - case SR_FORMAT_R16G16_FLOAT: - return DXGI_FORMAT_R16G16_FLOAT; - case SR_FORMAT_R16_FLOAT: - return DXGI_FORMAT_R16_FLOAT; - case SR_FORMAT_R32_FLOAT: - return DXGI_FORMAT_R32_FLOAT; - default: - return DXGI_FORMAT_UNKNOWN; - } -} - -/** - * Creates one shared interop texture as a committed UAV-capable Texture2D on - * the DEFAULT heap with D3D12_HEAP_FLAG_SHARED, records its driver allocation - * size, and exports an NT shared handle so the resource can be opened from - * OpenGL (e.g. via GL_EXT_memory_object). Returns the complete SharedTexture, - * or an empty optional on failure with the thread-local error string set — - * the half-initialized state is not representable. - */ -std::optional -createSharedTexture(const ComObj &device, uint32_t width, - uint32_t height, DXGI_FORMAT format) { - if (width == 0 || height == 0 || format == DXGI_FORMAT_UNKNOWN) { - setError("Invalid shared D3D12 texture description."); - return std::nullopt; - } - - D3D12_HEAP_PROPERTIES heapProperties = {}; - heapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; - heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; - heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; - heapProperties.CreationNodeMask = 1; - heapProperties.VisibleNodeMask = 1; - - D3D12_RESOURCE_DESC resourceDesc = {}; - resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; - resourceDesc.Width = width; - resourceDesc.Height = height; - resourceDesc.DepthOrArraySize = 1; - resourceDesc.MipLevels = 1; - resourceDesc.Format = format; - resourceDesc.SampleDesc.Count = 1; - resourceDesc.SampleDesc.Quality = 0; - resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; - resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; - - auto resource = createCom( - "ID3D12Device::CreateCommittedResource", [&](ID3D12Resource **pp) { - return device->CreateCommittedResource( - &heapProperties, D3D12_HEAP_FLAG_SHARED, &resourceDesc, - D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(pp)); - }); - if (!resource) { - return std::nullopt; - } - - const D3D12_RESOURCE_ALLOCATION_INFO allocationInfo = - device->GetResourceAllocationInfo(0, 1, &resourceDesc); - if (allocationInfo.SizeInBytes == 0 || - allocationInfo.SizeInBytes == UINT64_MAX) { - setError("D3D12 returned an invalid shared resource allocation size."); - return std::nullopt; - } - - UniqueHandle sharedHandle; - const HRESULT hr = - device->CreateSharedHandle(resource->get(), nullptr, GENERIC_ALL, - nullptr, sharedHandle.put()); - if (FAILED(hr)) { - setHresultError("ID3D12Device::CreateSharedHandle(resource)", hr); - return std::nullopt; - } - return SharedTexture{std::move(*resource), std::move(sharedHandle), - allocationInfo.SizeInBytes}; -} - -/** - * Blocks the calling thread until the interop fence reaches the given value. - * Returns immediately (success) when the context is null, the value is 0, or - * the fence has already completed it; otherwise arms the fence event and - * waits on it. Returns false and sets the error string on failure. - */ -bool waitForFence(D3D12InteropContext *context, uint64_t value) { - if (!context || value == 0 || context->fence->GetCompletedValue() >= value) { - return true; - } - const HRESULT hr = - context->fence->SetEventOnCompletion(value, context->fenceEvent.get()); - if (FAILED(hr)) { - setHresultError("ID3D12Fence::SetEventOnCompletion", hr); - return false; - } - if (WaitForSingleObject(context->fenceEvent.get(), INFINITE) != - WAIT_OBJECT_0) { - setError("Waiting for the D3D12 interop fence failed."); - return false; - } - return true; -} +using sr::d3d12::D3D12InteropContext; +using sr::d3d12::SharedTexture; -/** - * Reinterprets the opaque jlong handle returned by Nd3d12CreateContext back - * into the native context pointer. Returns null for a 0 handle. - */ +// Converts the opaque pointer-sized handle stored by Java back into its native +// context. A zero jlong naturally becomes a null pointer. D3D12InteropContext *fromHandle(jlong handle) { return reinterpret_cast(static_cast(handle)); } -/** - * Looks up one of the shared interop textures by index - * (RESOURCE_INPUT_COLOR .. RESOURCE_OUTPUT_COLOR). Returns null and sets the - * error string when the context is null or the index is out of range. - */ SharedTexture *getResource(D3D12InteropContext *context, jint index) { - if (!context || index < 0 || static_cast(index) >= RESOURCE_COUNT) { - setError("Invalid D3D12 interop resource index."); + if (!context || index < 0 || + static_cast(index) >= sr::d3d12::RESOURCE_COUNT) { + sr::d3d12::setError("Invalid D3D12 interop resource index."); return nullptr; } return &context->resources[static_cast(index)]; } + +// Centralizes pointer/HANDLE conversion at the JNI boundary. +template jlong toHandle(T *pointer) { + return static_cast(reinterpret_cast(pointer)); +} + } // namespace extern "C" { + /** - * Creates a D3D12 interop context for the GPU identified by adapterLuid - * (matching the OpenGL device so both APIs share one adapter). + * Creates a D3D12 context on the adapter used by OpenGL. * - * Initializes (or reuses, when the LUID matches and the device is not - * removed) a process-wide shared ID3D12Device, then creates a direct command - * queue, allocator, and command list, a shared fence with its wait event and - * NT handle, and the five shared interop textures: input color (render size, - * colorFormat), input depth (R32_FLOAT), input motion vectors (R16G16_FLOAT), - * input exposure (1x1 R16_FLOAT), and output color (output size, colorFormat). - * Each step yields a checked object (ComObj / SharedTexture); the context - * itself is only constructed once every step has succeeded, and intermediate - * objects clean up after themselves via RAII on any failure. - * - * Returns an opaque context handle (pointer as jlong), or 0 on failure with - * the reason available via Nd3d12GetLastError. + * The returned pointer is opaque to Java. Zero indicates failure, with the + * reason available through Nd3d12GetLastError. */ JNIEXPORT jlong JNICALL Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12CreateContext( JNIEnv *, jclass, jlong adapterLuid, jint renderWidth, jint renderHeight, jint outputWidth, jint outputHeight, jint colorFormat) { - g_lastError.clear(); if (adapterLuid == 0 || renderWidth <= 0 || renderHeight <= 0 || outputWidth <= 0 || outputHeight <= 0) { - setError("Invalid D3D12 interop context dimensions or adapter LUID."); - return 0; - } - - const DXGI_FORMAT dxgiColorFormat = toDxgiFormat(colorFormat); - if (dxgiColorFormat == DXGI_FORMAT_UNKNOWN) { - setError("The configured internal color format is not supported by D3D12 " - "interop."); - return 0; - } - - auto factory = createCom( - "CreateDXGIFactory1", - [](IDXGIFactory6 **pp) { return CreateDXGIFactory1(IID_PPV_ARGS(pp)); }); - if (!factory) { - return 0; - } - - std::optional> adapter; - for (UINT index = 0;; ++index) { - ComPtr candidate; - const HRESULT hr = (*factory)->EnumAdapters1(index, &candidate); - if (hr == DXGI_ERROR_NOT_FOUND) { - break; - } - if (FAILED(hr)) { - setHresultError("IDXGIFactory1::EnumAdapters1", hr); - return 0; - } - DXGI_ADAPTER_DESC1 desc = {}; - if (SUCCEEDED(candidate->GetDesc1(&desc)) && - (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) == 0 && - sameLuid(desc.AdapterLuid, static_cast(adapterLuid))) { - adapter = ComObj::from(std::move(candidate)); - break; - } - } - if (!adapter) { - setError("No D3D12 adapter matches the OpenGL device LUID."); - return 0; - } - - auto device = - g_sharedDevice.getOrCreate(*adapter, static_cast(adapterLuid)); - if (!device) { - return 0; - } - - D3D12_COMMAND_QUEUE_DESC queueDesc = {}; - queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; - queueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL; - queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; - auto queue = createCom( - "ID3D12Device::CreateCommandQueue", [&](ID3D12CommandQueue **pp) { - return (*device)->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(pp)); - }); - if (!queue) { - return 0; - } - - auto commandAllocator = createCom( - "ID3D12Device::CreateCommandAllocator", [&](ID3D12CommandAllocator **pp) { - return (*device)->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, - IID_PPV_ARGS(pp)); - }); - if (!commandAllocator) { + sr::d3d12::clearError(); + sr::d3d12::setError( + "Invalid D3D12 interop context dimensions or adapter LUID."); return 0; } - auto commandList = createCom( - "ID3D12Device::CreateCommandList", [&](ID3D12GraphicsCommandList **pp) { - return (*device)->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, - commandAllocator->get(), nullptr, - IID_PPV_ARGS(pp)); - }); - if (!commandList) { - return 0; - } - HRESULT hr = (*commandList)->Close(); - if (FAILED(hr)) { - setHresultError("ID3D12GraphicsCommandList::Close(initial)", hr); - return 0; - } - - auto fence = createCom( - "ID3D12Device::CreateFence", [&](ID3D12Fence **pp) { - return (*device)->CreateFence(0, D3D12_FENCE_FLAG_SHARED, - IID_PPV_ARGS(pp)); - }); - if (!fence) { - return 0; - } - - UniqueHandle fenceEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)); - if (!fenceEvent) { - setError("CreateEventW failed for the D3D12 interop fence."); - return 0; - } - - UniqueHandle fenceSharedHandle; - hr = (*device)->CreateSharedHandle(fence->get(), nullptr, GENERIC_ALL, - nullptr, fenceSharedHandle.put()); - if (FAILED(hr)) { - setHresultError("ID3D12Device::CreateSharedHandle(fence)", hr); - return 0; - } - - auto inputColor = - createSharedTexture(*device, static_cast(renderWidth), - static_cast(renderHeight), dxgiColorFormat); - if (!inputColor) { - return 0; - } - auto inputDepth = - createSharedTexture(*device, static_cast(renderWidth), - static_cast(renderHeight), - DXGI_FORMAT_R32_FLOAT); - if (!inputDepth) { - return 0; - } - auto motionVectors = createSharedTexture( - *device, static_cast(renderWidth), - static_cast(renderHeight), DXGI_FORMAT_R16G16_FLOAT); - if (!motionVectors) { - return 0; - } - auto exposure = createSharedTexture(*device, 1, 1, DXGI_FORMAT_R16_FLOAT); - if (!exposure) { - return 0; - } - auto outputColor = - createSharedTexture(*device, static_cast(outputWidth), - static_cast(outputHeight), dxgiColorFormat); - if (!outputColor) { - return 0; - } - - // Every object was created successfully; only now does the context exist, - // with the validity of all its members enforced by ComObj. - auto context = std::unique_ptr( - new (std::nothrow) D3D12InteropContext( - std::move(*adapter), std::move(*device), std::move(*queue), - std::move(*commandAllocator), std::move(*commandList), - std::move(*fence), std::move(fenceSharedHandle), - std::move(fenceEvent), - {std::move(*inputColor), std::move(*inputDepth), - std::move(*motionVectors), std::move(*exposure), - std::move(*outputColor)})); - if (!context) { - setError("Could not allocate the D3D12 interop context."); - return 0; - } - - return static_cast(reinterpret_cast(context.release())); + auto context = sr::d3d12::createContext( + static_cast(adapterLuid), static_cast(renderWidth), + static_cast(renderHeight), static_cast(outputWidth), + static_cast(outputHeight), colorFormat); + return context ? toHandle(context.release()) : 0; } /** - * Destroys a context created by Nd3d12CreateContext: waits for the last - * submitted fence value so no in-flight GPU work references the resources, - * then frees the context, whose members (COM resources and Win32 handles) - * are released automatically by RAII. A null handle is a no-op. + * Waits for outstanding GPU work and destroys a context. A null handle is a + * no-op. */ JNIEXPORT void JNICALL Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12DestroyContext( @@ -594,69 +71,57 @@ Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12Destro if (!context) { return; } - waitForFence(context, context->lastSubmittedFenceValue); + sr::d3d12::waitForFence(context, context->lastSubmittedFenceValue); delete context; } /** - * Returns the raw ID3D12Device pointer of the context as a jlong, so Java can - * hand it to the FidelityFX FSR backend. Returns 0 for an invalid handle. + * Returns the borrowed ID3D12Device pointer used by the FidelityFX backend. + * Its lifetime remains tied to the context. */ JNIEXPORT jlong JNICALL Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetDevice( JNIEnv *, jclass, jlong contextHandle) { D3D12InteropContext *context = fromHandle(contextHandle); - return context ? static_cast( - reinterpret_cast(context->device.get())) - : 0; + return context ? toHandle(context->device.get()) : 0; } /** - * Returns the raw ID3D12GraphicsCommandList pointer of the context as a - * jlong, so Java can record upscaling commands into it between - * Nd3d12BeginFrame and Nd3d12ExecuteFrame. Returns 0 for an invalid handle. + * Returns the borrowed command list into which Java records upscaling work + * between Nd3d12BeginFrame and Nd3d12ExecuteFrame. */ JNIEXPORT jlong JNICALL Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetCommandList( JNIEnv *, jclass, jlong contextHandle) { D3D12InteropContext *context = fromHandle(contextHandle); - return context ? static_cast( - reinterpret_cast(context->commandList.get())) - : 0; + return context ? toHandle(context->commandList.get()) : 0; } /** - * Returns the raw ID3D12Resource pointer of the interop texture at the given - * index as a jlong, so Java can bind it as an FSR input/output. Returns 0 for - * an invalid handle or index (with the error string set in the latter case). + * Returns the borrowed ID3D12Resource at the Java-defined resource index. + * Invalid handles or indices return zero. */ JNIEXPORT jlong JNICALL Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetResource( JNIEnv *, jclass, jlong contextHandle, jint index) { SharedTexture *texture = getResource(fromHandle(contextHandle), index); - return texture ? static_cast( - reinterpret_cast(texture->resource.get())) - : 0; + return texture ? toHandle(texture->resource.get()) : 0; } /** - * Returns the NT shared handle (as a jlong) of the interop texture at the - * given index, used to import the texture into OpenGL. Returns 0 for an - * invalid handle or index. + * Returns the texture's exported NT handle for OpenGL memory-object import. + * Ownership remains with the context. */ JNIEXPORT jlong JNICALL Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetResourceSharedHandle( JNIEnv *, jclass, jlong contextHandle, jint index) { SharedTexture *texture = getResource(fromHandle(contextHandle), index); - return texture ? static_cast( - reinterpret_cast(texture->sharedHandle.get())) - : 0; + return texture ? toHandle(texture->sharedHandle.get()) : 0; } /** - * Returns the driver-reported allocation size in bytes of the interop texture - * at the given index. OpenGL needs this when importing the shared memory - * object. Returns 0 for an invalid handle or index. + * Returns the driver-reported allocation size needed when OpenGL imports the + * shared texture memory. */ JNIEXPORT jlong JNICALL Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetResourceAllocationSize( @@ -666,130 +131,65 @@ Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetRes } /** - * Returns the NT shared handle (as a jlong) of the context's fence, used to - * import the fence into OpenGL as a semaphore for cross-API synchronization. - * Returns 0 for an invalid handle. + * Returns the shared fence's exported NT handle for OpenGL semaphore import. + * Ownership remains with the context. */ JNIEXPORT jlong JNICALL Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetFenceSharedHandle( JNIEnv *, jclass, jlong contextHandle) { D3D12InteropContext *context = fromHandle(contextHandle); - return context ? static_cast( - reinterpret_cast(context->fenceSharedHandle.get())) - : 0; + return context ? toHandle(context->fenceSharedHandle.get()) : 0; } /** - * Starts a D3D12 frame: CPU-waits for the previous frame's fence value, - * resets the command allocator and list into recording state, then makes the - * queue GPU-wait on waitFenceValue — the fence value signaled by OpenGL once - * the input textures are ready. Java may then record commands into the list - * obtained from Nd3d12GetCommandList. - * - * Returns S_OK on success, E_INVALIDARG/E_FAIL for invalid state, or the - * failing HRESULT (as jint) otherwise; the error string holds details. + * Prepares the command list for a frame and queues a GPU wait for OpenGL's + * input-ready fence value. */ JNIEXPORT jint JNICALL Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12BeginFrame( JNIEnv *, jclass, jlong contextHandle, jlong waitFenceValue) { - D3D12InteropContext *context = fromHandle(contextHandle); - if (!context || waitFenceValue <= 0) { - setError("Invalid D3D12 begin-frame arguments."); + if (waitFenceValue <= 0) { + sr::d3d12::setError("Invalid D3D12 begin-frame arguments."); return E_INVALIDARG; } - if (context->frameState == FrameState::Recording) { - setError("The D3D12 command list is already recording."); - return E_FAIL; - } - if (!waitForFence(context, context->lastSubmittedFenceValue)) { - return E_FAIL; - } - - HRESULT hr = context->commandAllocator->Reset(); - if (FAILED(hr)) { - setHresultError("ID3D12CommandAllocator::Reset", hr); - return static_cast(hr); - } - hr = context->commandList->Reset(context->commandAllocator.get(), nullptr); - if (FAILED(hr)) { - setHresultError("ID3D12GraphicsCommandList::Reset", hr); - return static_cast(hr); - } - hr = context->queue->Wait(context->fence.get(), - static_cast(waitFenceValue)); - if (FAILED(hr)) { - setHresultError("ID3D12CommandQueue::Wait", hr); - context->commandList->Close(); - return static_cast(hr); - } - context->frameState = FrameState::Recording; - return S_OK; + return static_cast(sr::d3d12::beginFrame( + fromHandle(contextHandle), static_cast(waitFenceValue))); } /** - * Ends a D3D12 frame started by Nd3d12BeginFrame: closes the command list, - * submits it to the queue, and signals signalFenceValue on the shared fence - * so OpenGL can wait for the output texture to be ready. The value is - * remembered as lastSubmittedFenceValue for later idle waits. - * - * Returns S_OK on success, E_INVALIDARG when not recording or the fence value - * is invalid, or the failing HRESULT (as jint) otherwise. + * Submits the recorded command list and signals the output-ready fence value + * consumed by OpenGL. */ JNIEXPORT jint JNICALL Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12ExecuteFrame( JNIEnv *, jclass, jlong contextHandle, jlong signalFenceValue) { - D3D12InteropContext *context = fromHandle(contextHandle); - if (!context || context->frameState != FrameState::Recording || - signalFenceValue <= 0) { - setError("Invalid D3D12 execute-frame state or fence value."); + if (signalFenceValue <= 0) { + sr::d3d12::setError("Invalid D3D12 execute-frame state or fence value."); return E_INVALIDARG; } - - HRESULT hr = context->commandList->Close(); - context->frameState = FrameState::Idle; - if (FAILED(hr)) { - setHresultError("ID3D12GraphicsCommandList::Close", hr); - return static_cast(hr); - } - - ID3D12CommandList *commandLists[] = {context->commandList.get()}; - context->queue->ExecuteCommandLists(1, commandLists); - hr = context->queue->Signal(context->fence.get(), - static_cast(signalFenceValue)); - if (FAILED(hr)) { - setHresultError("ID3D12CommandQueue::Signal", hr); - return static_cast(hr); - } - context->lastSubmittedFenceValue = static_cast(signalFenceValue); - return S_OK; + return static_cast(sr::d3d12::executeFrame( + fromHandle(contextHandle), static_cast(signalFenceValue))); } /** - * Blocks until all GPU work submitted so far (up to the last value signaled - * by Nd3d12ExecuteFrame) has completed. Returns S_OK, E_INVALIDARG for a - * null context, or E_FAIL if the fence wait itself failed. + * Blocks until the latest submitted D3D12 frame completes. */ JNIEXPORT jint JNICALL Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12WaitIdle( JNIEnv *, jclass, jlong contextHandle) { - D3D12InteropContext *context = fromHandle(contextHandle); - if (!context) { - setError("The D3D12 interop context is null."); - return E_INVALIDARG; - } - return waitForFence(context, context->lastSubmittedFenceValue) ? S_OK - : E_FAIL; + return static_cast(sr::d3d12::waitIdle(fromHandle(contextHandle))); } /** - * Returns the thread-local error message recorded by the last failed interop - * call on this thread, or an empty string if none occurred. + * Returns the thread-local diagnostic recorded by the most recent failed + * interop operation on this thread. */ JNIEXPORT jstring JNICALL Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12GetLastError( JNIEnv *env, jclass) { - return env->NewStringUTF(g_lastError.c_str()); -} + return env->NewStringUTF(sr::d3d12::lastError().c_str()); } +} // extern "C" + #endif diff --git a/native/cpp/SRNativeD3D12Interop/src/d3d12_interop_internal.h b/native/cpp/SRNativeD3D12Interop/src/d3d12_interop_internal.h new file mode 100644 index 00000000..0fe51e2f --- /dev/null +++ b/native/cpp/SRNativeD3D12Interop/src/d3d12_interop_internal.h @@ -0,0 +1,198 @@ +#pragma once + +#if defined(ON_WIN64) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace sr::d3d12 { + +using Microsoft::WRL::ComPtr; + +// Slot count and order of D3D12InteropContext::resources. The order mirrors +// the RESOURCE_* constants on the Java side. +constexpr uint32_t RESOURCE_COUNT = 5; + +/** + * Minimal move-only RAII wrapper for a Win32 HANDLE. + * + * This provides the ownership behavior needed here without adding a + * dependency on wil::unique_handle. A non-null handle is always closed when + * the wrapper is destroyed or reset. + */ +class UniqueHandle { +public: + UniqueHandle() = default; + explicit UniqueHandle(HANDLE handle) : handle_(handle) {} + ~UniqueHandle() { reset(); } + + UniqueHandle(const UniqueHandle &) = delete; + UniqueHandle &operator=(const UniqueHandle &) = delete; + + UniqueHandle(UniqueHandle &&other) noexcept : handle_(other.release()) {} + UniqueHandle &operator=(UniqueHandle &&other) noexcept { + reset(other.release()); + return *this; + } + + HANDLE get() const { return handle_; } + explicit operator bool() const { return handle_ != nullptr; } + + // Releases the current handle and returns storage suitable for Win32 + // out-parameters such as ID3D12Device::CreateSharedHandle. + HANDLE *put() { + reset(); + return &handle_; + } + + HANDLE release() { + HANDLE handle = handle_; + handle_ = nullptr; + return handle; + } + + void reset(HANDLE handle = nullptr) { + if (handle_) { + CloseHandle(handle_); + } + handle_ = handle; + } + +private: + HANDLE handle_ = nullptr; +}; + +/** + * Owning COM pointer that is non-null by construction. + * + * Instances can only be obtained through from(), which rejects an empty + * ComPtr. Consequently, a fully constructed interop context can dereference + * all of its COM members without repeating null checks. + */ +template class ComObj { +public: + ComObj(const ComObj &) = delete; + ComObj &operator=(const ComObj &) = delete; + ComObj(ComObj &&) noexcept = default; + ComObj &operator=(ComObj &&) noexcept = default; + + static std::optional from(ComPtr ptr) { + if (!ptr) { + return std::nullopt; + } + return ComObj(std::move(ptr)); + } + + T *get() const { return ptr_.Get(); } + T *operator->() const { return ptr_.Get(); } + + // Returns another owning COM reference, including the corresponding AddRef. + ComPtr share() const { return ptr_; } + +private: + explicit ComObj(ComPtr ptr) : ptr_(std::move(ptr)) {} + ComPtr ptr_; +}; + +// Command-list lifecycle across a frame. Recording begins only after a +// successful beginFrame() and ends when executeFrame() closes the list. +enum class FrameState { Idle, Recording }; + +/** + * A completely initialized shared texture. + * + * Keeping the resource, exported NT handle, and allocation size together + * prevents callers from observing a partially created interop texture. + */ +struct SharedTexture { + ComObj resource; + UniqueHandle sharedHandle; + uint64_t allocationSize = 0; +}; + +/** + * All D3D12 objects and synchronization state owned by one Java context. + * + * Every owning member is RAII-managed. Construction occurs only after every + * required D3D12 object and shared handle has been created successfully. + */ +struct D3D12InteropContext { + D3D12InteropContext(ComObj adapter, + ComObj device, + ComObj queue, + ComObj commandAllocator, + ComObj commandList, + ComObj fence, UniqueHandle fenceSharedHandle, + UniqueHandle fenceEvent, + std::array resources) + : adapter(std::move(adapter)), device(std::move(device)), + queue(std::move(queue)), commandAllocator(std::move(commandAllocator)), + commandList(std::move(commandList)), fence(std::move(fence)), + fenceSharedHandle(std::move(fenceSharedHandle)), + fenceEvent(std::move(fenceEvent)), resources(std::move(resources)) {} + + ComObj adapter; + ComObj device; + ComObj queue; + ComObj commandAllocator; + ComObj commandList; + ComObj fence; + UniqueHandle fenceSharedHandle; + UniqueHandle fenceEvent; + uint64_t lastSubmittedFenceValue = 0; + FrameState frameState = FrameState::Idle; + std::array resources; +}; + +// Thread-local diagnostics exposed to Java by Nd3d12GetLastError. +void clearError(); +void setError(const char *message); +void setHresultError(const char *operation, HRESULT hr); +const std::string &lastError(); + +/** + * Runs a COM creation operation and converts its nullable out-parameter into + * a checked ComObj. On failure, the operation and HRESULT are recorded in the + * thread-local error string. + */ +template +std::optional> createCom(const char *operation, Create &&create) { + ComPtr ptr; + const HRESULT hr = create(ptr.ReleaseAndGetAddressOf()); + if (FAILED(hr)) { + setHresultError(operation, hr); + return std::nullopt; + } + return ComObj::from(std::move(ptr)); +} + +/** + * Creates the adapter-bound device, command objects, shared fence, and all + * five shared textures. Returns null on failure after recording an error. + */ +std::unique_ptr +createContext(uint64_t adapterLuid, uint32_t renderWidth, uint32_t renderHeight, + uint32_t outputWidth, uint32_t outputHeight, int colorFormat); + +// Native frame operations used by the thin JNI boundary. +bool waitForFence(D3D12InteropContext *context, uint64_t value); +HRESULT beginFrame(D3D12InteropContext *context, uint64_t waitFenceValue); +HRESULT executeFrame(D3D12InteropContext *context, uint64_t signalFenceValue); +HRESULT waitIdle(D3D12InteropContext *context); + +} // namespace sr::d3d12 + +#endif From 233afccd949ab64ef5f40856e916d61987ff21d1 Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Wed, 29 Jul 2026 18:39:54 +0800 Subject: [PATCH 11/19] fixed ljwgl std version issue --- build.gradle.kts | 2 +- buildSrc/src/main/kotlin/multiversion/CommonConfig.kt | 5 +++++ configs/1.20.1.json | 1 + configs/1.21.1.json | 1 + configs/1.21.11.json | 1 + 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 753868ef..523f44d8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -85,7 +85,7 @@ allprojects { force("org.lwjgl:lwjgl-opengl:${(rootProject.extra["versionConfig"] as multiversion.VersionConfig).common.lwjglVersion}") force("org.lwjgl:lwjgl-vulkan:${(rootProject.extra["versionConfig"] as multiversion.VersionConfig).common.lwjglVersion}") force("org.lwjgl:lwjgl-openal:${(rootProject.extra["versionConfig"] as multiversion.VersionConfig).common.lwjglVersion}") - force("org.lwjgl:lwjgl-stb:${(rootProject.extra["versionConfig"] as multiversion.VersionConfig).common.lwjglVersion}") + force("org.lwjgl:lwjgl-stb:${(rootProject.extra["versionConfig"] as multiversion.VersionConfig).common.lwjglStbVersion}") force("org.lwjgl:lwjgl-jemalloc:${(rootProject.extra["versionConfig"] as multiversion.VersionConfig).common.lwjglVersion}") force("org.lwjgl:lwjgl-tinyfd:${(rootProject.extra["versionConfig"] as multiversion.VersionConfig).common.lwjglVersion}") force("org.lwjgl:lwjgl-freetype:${(rootProject.extra["versionConfig"] as multiversion.VersionConfig).common.lwjglVersion}") diff --git a/buildSrc/src/main/kotlin/multiversion/CommonConfig.kt b/buildSrc/src/main/kotlin/multiversion/CommonConfig.kt index 9ce8dbc6..35b78d72 100644 --- a/buildSrc/src/main/kotlin/multiversion/CommonConfig.kt +++ b/buildSrc/src/main/kotlin/multiversion/CommonConfig.kt @@ -11,6 +11,11 @@ class CommonConfig(config: Map<*, *>) { ?: emptyList() val lwjglVersion: String = config["lwjgl_version"]?.toString().orEmpty() + + // LWJGL 3.3.4 移除了旧版 stb_image_resize API(stbir_resize_uint8),而旧版 + // Minecraft 的 NativeImage.resizeSubRectTo 链接的正是该 API,因此这类版本需要在 + // 配置里单独指定 lwjgl_stb_version(如 3.3.3);缺省时跟随 lwjgl_version。 + val lwjglStbVersion: String = config["lwjgl_stb_version"]?.toString()?.takeIf { it.isNotBlank() } ?: lwjglVersion val architecturyApiVersion: String? = config["architectury_api_version"]?.toString() val clothConfigVersion: String? = config["cloth_config_version"]?.toString() val modArtifactMinecraftVer: String = config["mod_artifact_minecraft_ver"]?.toString().orEmpty() diff --git a/configs/1.20.1.json b/configs/1.20.1.json index ec870f72..a85e8072 100644 --- a/configs/1.20.1.json +++ b/configs/1.20.1.json @@ -7,6 +7,7 @@ "forge" ], "lwjgl_version": "3.3.4", + "lwjgl_stb_version": "3.3.3", "forge": { "minecraft_version_range": "[1.20.1,)" }, diff --git a/configs/1.21.1.json b/configs/1.21.1.json index ac67d648..3e51d78d 100644 --- a/configs/1.21.1.json +++ b/configs/1.21.1.json @@ -8,6 +8,7 @@ "neoforge" ], "lwjgl_version": "3.3.4", + "lwjgl_stb_version": "3.3.3", "architectury_api_version": "13.0.6", "neoforge": { "minecraft_version_range": "[1.21.1,)" diff --git a/configs/1.21.11.json b/configs/1.21.11.json index 8129c7f3..ab7df2da 100644 --- a/configs/1.21.11.json +++ b/configs/1.21.11.json @@ -9,6 +9,7 @@ "neoforge" ], "lwjgl_version": "3.3.4", + "lwjgl_stb_version": "3.3.3", "architectury_api_version": "19.0.1", "neoforge": { "minecraft_version_range": "[1.21.11,)" From faafa078b06612e02803e0d9fddff3bc32ddd9a3 Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Wed, 29 Jul 2026 19:33:52 +0800 Subject: [PATCH 12/19] guard D3D12 dispatch during resize --- .../common/upscale/D3D12InteropAlgorithm.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java b/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java index 6a74c06e..4e4162cc 100644 --- a/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java +++ b/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java @@ -12,6 +12,7 @@ import io.homo.superresolution.api.AbstractAlgorithm; import io.homo.superresolution.api.InitializationDescription; +import io.homo.superresolution.common.SuperResolution; import io.homo.superresolution.common.config.SuperResolutionConfig; import io.homo.superresolution.common.minecraft.handler.RenderHandlerManager; import io.homo.superresolution.common.workmode.SRWorkModeManager; @@ -53,6 +54,7 @@ public abstract class D3D12InteropAlgorithm extends AbstractAlgorithm { private int builtRenderHeight = -1; private int builtScreenWidth = -1; private int builtScreenHeight = -1; + private boolean resizeMismatchLogged; protected abstract void onD3D12InteropCreated(InitializationDescription desc); @@ -119,6 +121,7 @@ private void createResources() { builtRenderHeight = RenderHandlerManager.getRenderHeight(); builtScreenWidth = RenderHandlerManager.getScreenWidth(); builtScreenHeight = RenderHandlerManager.getScreenHeight(); + resizeMismatchLogged = false; } @Override @@ -127,6 +130,25 @@ public boolean dispatch(DispatchResource dispatchResource) { if (d3d12Interop == null || !isD3D12UpscalerReady()) { return false; } + if (!matchesBuiltSize(dispatchResource)) { + if (!resizeMismatchLogged) { + SuperResolution.LOGGER.warn( + "Skipping D3D12 upscale while resize is pending: " + + "dispatch render={}x{}, screen={}x{}; " + + "built render={}x{}, screen={}x{}", + dispatchResource.renderWidth(), + dispatchResource.renderHeight(), + dispatchResource.screenWidth(), + dispatchResource.screenHeight(), + builtRenderWidth, + builtRenderHeight, + builtScreenWidth, + builtScreenHeight); + resizeMismatchLogged = true; + } + needsHistoryReset = true; + return false; + } InteropResourcesConverter.processInputTextures( dispatchResource.resources().colorTexture(), @@ -181,6 +203,13 @@ public boolean dispatch(DispatchResource dispatchResource) { return dispatched; } + private boolean matchesBuiltSize(DispatchResource dispatchResource) { + return dispatchResource.renderWidth() == builtRenderWidth && + dispatchResource.renderHeight() == builtRenderHeight && + dispatchResource.screenWidth() == builtScreenWidth && + dispatchResource.screenHeight() == builtScreenHeight; + } + private int[] sharedTextureIds() { return new int[]{ Math.toIntExact(inputColor.handle()), @@ -271,6 +300,7 @@ private void destroyResources() { builtRenderHeight = -1; builtScreenWidth = -1; builtScreenHeight = -1; + resizeMismatchLogged = false; } @Override From 86e3c2b3300109ddf32cce33482438e4c0d082a0 Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Wed, 29 Jul 2026 19:43:51 +0800 Subject: [PATCH 13/19] refactor D3D12 resize resources as generations --- .../common/upscale/D3D12InteropAlgorithm.java | 486 +++++++++++++----- .../common/upscale/ffxfsr/FfxFSR4D3D12.java | 57 +- 2 files changed, 374 insertions(+), 169 deletions(-) diff --git a/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java b/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java index 4e4162cc..ba4d78a7 100644 --- a/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java +++ b/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java @@ -28,43 +28,143 @@ import io.homo.superresolution.core.graphics.impl.texture.TextureUsages; import io.homo.superresolution.core.graphics.opengl.texture.GlTexture2D; +import java.util.Objects; + import static org.lwjgl.opengl.EXTSemaphore.GL_LAYOUT_GENERAL_EXT; import static org.lwjgl.opengl.EXTSemaphore.GL_LAYOUT_SHADER_READ_ONLY_EXT; /** * Low-latency OpenGL/Direct3D 12 interop path. * + *

Every interop texture, semaphore, output framebuffer, and provider + * context belongs to one immutable-size resource generation. Resize creates a + * complete replacement generation before publishing it; dispatch therefore + * cannot combine a new frame size with resources from an older size.

+ * *

D3D12 owns the shared committed resources and a shared timeline fence. * OpenGL imports those objects, writes the preprocessed inputs, signals the * fence, waits for the D3D12 dispatch, and then copies the vertically flipped * output into a regular OpenGL texture.

*/ -public abstract class D3D12InteropAlgorithm extends AbstractAlgorithm { - protected D3D12InteropContext d3d12Interop; - protected GlD3D12ImportableTexture2D inputColor; - protected GlD3D12ImportableTexture2D inputDepth; - protected GlD3D12ImportableTexture2D inputMotionVectors; - protected GlD3D12ImportableTexture2D inputExposure; - protected GlD3D12ImportableTexture2D outputColor; - - private D3D12InteropSemaphore semaphore; - private GlTexture2D flippedOutput; - private IFrameBuffer outputFramebuffer; - private int builtRenderWidth = -1; - private int builtRenderHeight = -1; - private int builtScreenWidth = -1; - private int builtScreenHeight = -1; +public abstract class D3D12InteropAlgorithm extends AbstractAlgorithm { + private enum LifecycleState { + NEW, + READY, + RESIZE_PENDING, + REBUILDING, + DESTROYED + } + + /** + * The dimensions bound to one resource generation. + * + *

The screen dimensions are sampled once and the render dimensions are + * derived from that same snapshot. This avoids constructing a generation + * from values observed on opposite sides of a window resize.

+ */ + protected record InteropSize( + int renderWidth, + int renderHeight, + int screenWidth, + int screenHeight) { + public InteropSize { + if (renderWidth < 1 || renderHeight < 1 || + screenWidth < 1 || screenHeight < 1) { + throw new IllegalArgumentException( + "Interop dimensions must be positive"); + } + } + + private static InteropSize capture() { + return fromScreenSize( + RenderHandlerManager.getScreenWidth(), + RenderHandlerManager.getScreenHeight()); + } + + private static InteropSize fromScreenSize(int width, int height) { + int screenWidth = Math.max(width, 32); + int screenHeight = Math.max(height, 32); + float scaleFactor = RenderHandlerManager.getScaleFactor(); + return new InteropSize( + (int) Math.max(screenWidth * scaleFactor, 32), + (int) Math.max(screenHeight * scaleFactor, 32), + screenWidth, + screenHeight); + } + + private boolean matches(DispatchResource dispatchResource) { + return dispatchResource.renderWidth() == renderWidth && + dispatchResource.renderHeight() == renderHeight && + dispatchResource.screenWidth() == screenWidth && + dispatchResource.screenHeight() == screenHeight; + } + } + + private static final class InteropResources { + private final InteropSize size; + private final D3D12InteropContext context; + private final GlD3D12ImportableTexture2D inputColor; + private final GlD3D12ImportableTexture2D inputDepth; + private final GlD3D12ImportableTexture2D inputMotionVectors; + private final GlD3D12ImportableTexture2D inputExposure; + private final GlD3D12ImportableTexture2D outputColor; + private final D3D12InteropSemaphore semaphore; + private final GlTexture2D flippedOutput; + private final IFrameBuffer outputFramebuffer; + private final int[] sharedTextureIds; + + private InteropResources( + InteropSize size, + D3D12InteropContext context, + GlD3D12ImportableTexture2D inputColor, + GlD3D12ImportableTexture2D inputDepth, + GlD3D12ImportableTexture2D inputMotionVectors, + GlD3D12ImportableTexture2D inputExposure, + GlD3D12ImportableTexture2D outputColor, + D3D12InteropSemaphore semaphore, + GlTexture2D flippedOutput, + IFrameBuffer outputFramebuffer) { + this.size = size; + this.context = context; + this.inputColor = inputColor; + this.inputDepth = inputDepth; + this.inputMotionVectors = inputMotionVectors; + this.inputExposure = inputExposure; + this.outputColor = outputColor; + this.semaphore = semaphore; + this.flippedOutput = flippedOutput; + this.outputFramebuffer = outputFramebuffer; + this.sharedTextureIds = new int[]{ + Math.toIntExact(inputColor.handle()), + Math.toIntExact(inputDepth.handle()), + Math.toIntExact(inputMotionVectors.handle()), + Math.toIntExact(inputExposure.handle()), + Math.toIntExact(outputColor.handle()) + }; + } + } + + private record Generation(InteropResources resources, U upscaler) { + } + + private Generation activeGeneration; + private LifecycleState lifecycleState = LifecycleState.NEW; private boolean resizeMismatchLogged; - protected abstract void onD3D12InteropCreated(InitializationDescription desc); + protected abstract U createD3D12Upscaler( + InitializationDescription desc, + D3D12InteropContext interop, + InteropSize size); - protected abstract void onBeforeD3D12InteropDestroyed(); + protected abstract void destroyD3D12Upscaler(U upscaler); protected abstract boolean dispatchD3D12Upscale( + U upscaler, + D3D12InteropContext interop, long commandList, DispatchResource dispatchResource); - protected boolean isD3D12UpscalerReady() { + protected boolean isD3D12UpscalerReady(U upscaler) { return true; } @@ -75,12 +175,34 @@ public void initialize(InitializationDescription desc) { "The optional D3D12 interop native library is unavailable."); } this.initDesc = desc; + lifecycleState = LifecycleState.REBUILDING; + try { + activeGeneration = createGeneration(InteropSize.capture()); + lifecycleState = LifecycleState.READY; + } catch (Throwable throwable) { + lifecycleState = LifecycleState.DESTROYED; + throw throwable; + } + } + + private Generation createGeneration(InteropSize size) { + InteropResources resources = createInteropResources(size); + U upscaler = null; try { - createResources(); - onD3D12InteropCreated(desc); + upscaler = Objects.requireNonNull( + createD3D12Upscaler(initDesc, resources.context, size), + "D3D12 upscaler creation returned null"); + return new Generation<>(resources, upscaler); } catch (Throwable throwable) { + if (upscaler != null) { + try { + destroyD3D12Upscaler(upscaler); + } catch (Throwable cleanupFailure) { + throwable.addSuppressed(cleanupFailure); + } + } try { - destroyResources(); + destroyInteropResources(resources); } catch (Throwable cleanupFailure) { throwable.addSuppressed(cleanupFailure); } @@ -88,84 +210,133 @@ public void initialize(InitializationDescription desc) { } } - private void createResources() { - d3d12Interop = D3D12InteropContext.create( - RenderHandlerManager.getRenderWidth(), - RenderHandlerManager.getRenderHeight(), - RenderHandlerManager.getScreenWidth(), - RenderHandlerManager.getScreenHeight(), - SuperResolutionConfig.getInternalTextureFormat()); - - inputColor = new GlD3D12ImportableTexture2D(d3d12Interop.inputColor()); - inputDepth = new GlD3D12ImportableTexture2D(d3d12Interop.inputDepth()); - inputMotionVectors = new GlD3D12ImportableTexture2D(d3d12Interop.inputMotionVectors()); - inputExposure = new GlD3D12ImportableTexture2D(d3d12Interop.inputExposure()); - outputColor = new GlD3D12ImportableTexture2D(d3d12Interop.outputColor()); - semaphore = new D3D12InteropSemaphore(d3d12Interop.getFenceSharedHandle()); - - flippedOutput = (GlTexture2D) RenderSystems.opengl().device().createTexture( - TextureDescription.create() - .type(TextureType.Texture2D) - .usages(TextureUsages.create().sampler().storage()) - .format(SuperResolutionConfig.getInternalTextureFormat()) - .width(RenderHandlerManager.getScreenWidth()) - .height(RenderHandlerManager.getScreenHeight()) - .label("D3D12UpscaleFlippedOutput") - .build()); - outputFramebuffer = RenderSystems.opengl().device().createFramebuffer( - FramebufferDescription.create() - .colorAttachment(flippedOutput) - .label("D3D12UpscaleOutputFramebuffer") - .build()); - builtRenderWidth = RenderHandlerManager.getRenderWidth(); - builtRenderHeight = RenderHandlerManager.getRenderHeight(); - builtScreenWidth = RenderHandlerManager.getScreenWidth(); - builtScreenHeight = RenderHandlerManager.getScreenHeight(); - resizeMismatchLogged = false; + private InteropResources createInteropResources(InteropSize size) { + D3D12InteropContext context = null; + GlD3D12ImportableTexture2D inputColor = null; + GlD3D12ImportableTexture2D inputDepth = null; + GlD3D12ImportableTexture2D inputMotionVectors = null; + GlD3D12ImportableTexture2D inputExposure = null; + GlD3D12ImportableTexture2D outputColor = null; + D3D12InteropSemaphore semaphore = null; + GlTexture2D flippedOutput = null; + IFrameBuffer outputFramebuffer = null; + try { + context = D3D12InteropContext.create( + size.renderWidth(), + size.renderHeight(), + size.screenWidth(), + size.screenHeight(), + SuperResolutionConfig.getInternalTextureFormat()); + + inputColor = new GlD3D12ImportableTexture2D(context.inputColor()); + inputDepth = new GlD3D12ImportableTexture2D(context.inputDepth()); + inputMotionVectors = + new GlD3D12ImportableTexture2D(context.inputMotionVectors()); + inputExposure = + new GlD3D12ImportableTexture2D(context.inputExposure()); + outputColor = new GlD3D12ImportableTexture2D(context.outputColor()); + semaphore = + new D3D12InteropSemaphore(context.getFenceSharedHandle()); + + flippedOutput = + (GlTexture2D) RenderSystems.opengl().device().createTexture( + TextureDescription.create() + .type(TextureType.Texture2D) + .usages(TextureUsages.create() + .sampler() + .storage()) + .format(SuperResolutionConfig + .getInternalTextureFormat()) + .width(size.screenWidth()) + .height(size.screenHeight()) + .label("D3D12UpscaleFlippedOutput") + .build()); + outputFramebuffer = + RenderSystems.opengl().device().createFramebuffer( + FramebufferDescription.create() + .colorAttachment(flippedOutput) + .label("D3D12UpscaleOutputFramebuffer") + .build()); + return new InteropResources( + size, + context, + inputColor, + inputDepth, + inputMotionVectors, + inputExposure, + outputColor, + semaphore, + flippedOutput, + outputFramebuffer); + } catch (Throwable throwable) { + try { + destroyPartialInteropResources( + context, + inputColor, + inputDepth, + inputMotionVectors, + inputExposure, + outputColor, + semaphore, + flippedOutput, + outputFramebuffer); + } catch (Throwable cleanupFailure) { + throwable.addSuppressed(cleanupFailure); + } + throw throwable; + } } @Override public boolean dispatch(DispatchResource dispatchResource) { super.dispatch(dispatchResource); - if (d3d12Interop == null || !isD3D12UpscalerReady()) { + Generation generation = activeGeneration; + if (lifecycleState == LifecycleState.DESTROYED || + lifecycleState == LifecycleState.REBUILDING || + generation == null || + !isD3D12UpscalerReady(generation.upscaler())) { return false; } - if (!matchesBuiltSize(dispatchResource)) { + + InteropResources resources = generation.resources(); + if (!resources.size.matches(dispatchResource)) { + lifecycleState = LifecycleState.RESIZE_PENDING; if (!resizeMismatchLogged) { SuperResolution.LOGGER.warn( - "Skipping D3D12 upscale while resize is pending: " + - "dispatch render={}x{}, screen={}x{}; " + - "built render={}x{}, screen={}x{}", + "Retaining the previous D3D12 output while resize is " + + "pending: dispatch render={}x{}, screen={}x{}; " + + "active generation render={}x{}, screen={}x{}", dispatchResource.renderWidth(), dispatchResource.renderHeight(), dispatchResource.screenWidth(), dispatchResource.screenHeight(), - builtRenderWidth, - builtRenderHeight, - builtScreenWidth, - builtScreenHeight); + resources.size.renderWidth(), + resources.size.renderHeight(), + resources.size.screenWidth(), + resources.size.screenHeight()); resizeMismatchLogged = true; } needsHistoryReset = true; return false; } + lifecycleState = LifecycleState.READY; InteropResourcesConverter.processInputTextures( dispatchResource.resources().colorTexture(), - inputColor, + resources.inputColor, dispatchResource.resources().depthTexture(), - inputDepth, + resources.inputDepth, dispatchResource.resources().motionVectorsTexture(), - inputMotionVectors, + resources.inputMotionVectors, dispatchResource.resources().exposureTexture(), - inputExposure, - SRWorkModeManager.getCurrentState().motionVectorPreprocessingFunction()); + resources.inputExposure, + SRWorkModeManager.getCurrentState() + .motionVectorPreprocessingFunction()); - int[] sharedTextures = sharedTextureIds(); - long openGlReadyValue = d3d12Interop.nextFenceValue(); - semaphore.signal( + long openGlReadyValue = resources.context.nextFenceValue(); + resources.semaphore.signal( openGlReadyValue, - sharedTextures, + resources.sharedTextureIds, new int[]{ GL_LAYOUT_SHADER_READ_ONLY_EXT, GL_LAYOUT_SHADER_READ_ONLY_EXT, @@ -174,22 +345,24 @@ public boolean dispatch(DispatchResource dispatchResource) { GL_LAYOUT_GENERAL_EXT }); - d3d12Interop.beginFrame(openGlReadyValue); + resources.context.beginFrame(openGlReadyValue); boolean dispatched; - long d3d12DoneValue = d3d12Interop.nextFenceValue(); + long d3d12DoneValue = resources.context.nextFenceValue(); try { dispatched = dispatchD3D12Upscale( - d3d12Interop.getCommandList(), + generation.upscaler(), + resources.context, + resources.context.getCommandList(), dispatchResource); } finally { // Always close and submit the command list, then reacquire every // resource in OpenGL. This keeps the allocator and cross-API // ownership usable even if a provider throws after recording part // of a dispatch. - d3d12Interop.executeFrame(d3d12DoneValue); - semaphore.waitFor( + resources.context.executeFrame(d3d12DoneValue); + resources.semaphore.waitFor( d3d12DoneValue, - sharedTextures, + resources.sharedTextureIds, new int[]{ GL_LAYOUT_SHADER_READ_ONLY_EXT, GL_LAYOUT_SHADER_READ_ONLY_EXT, @@ -199,119 +372,152 @@ public boolean dispatch(DispatchResource dispatchResource) { }); } - InteropResourcesConverter.flipY(outputColor, flippedOutput); + InteropResourcesConverter.flipY( + resources.outputColor, + resources.flippedOutput); return dispatched; } - private boolean matchesBuiltSize(DispatchResource dispatchResource) { - return dispatchResource.renderWidth() == builtRenderWidth && - dispatchResource.renderHeight() == builtRenderHeight && - dispatchResource.screenWidth() == builtScreenWidth && - dispatchResource.screenHeight() == builtScreenHeight; - } - - private int[] sharedTextureIds() { - return new int[]{ - Math.toIntExact(inputColor.handle()), - Math.toIntExact(inputDepth.handle()), - Math.toIntExact(inputMotionVectors.handle()), - Math.toIntExact(inputExposure.handle()), - Math.toIntExact(outputColor.handle()) - }; - } - @Override public void resize(int width, int height) { - if (isD3D12UpscalerReady() && - RenderHandlerManager.getRenderWidth() == builtRenderWidth && - RenderHandlerManager.getRenderHeight() == builtRenderHeight && - RenderHandlerManager.getScreenWidth() == builtScreenWidth && - RenderHandlerManager.getScreenHeight() == builtScreenHeight) { + InteropSize targetSize = InteropSize.fromScreenSize(width, height); + Generation previous = activeGeneration; + if (previous != null && + previous.resources().size.equals(targetSize) && + isD3D12UpscalerReady(previous.upscaler())) { + lifecycleState = LifecycleState.READY; + resizeMismatchLogged = false; return; } - destroyResources(); - needsHistoryReset = true; + + lifecycleState = LifecycleState.REBUILDING; + if (previous != null) { + drainGeneration(previous); + } + + Generation replacement; try { - createResources(); - onD3D12InteropCreated(initDesc); + replacement = createGeneration(targetSize); } catch (Throwable throwable) { - try { - destroyResources(); - } catch (Throwable cleanupFailure) { - throwable.addSuppressed(cleanupFailure); - } + lifecycleState = previous == null + ? LifecycleState.DESTROYED + : LifecycleState.RESIZE_PENDING; throw throwable; } + + // Publish only after both the interop resources and provider context + // are ready. If construction fails, the previous generation remains + // intact and can still provide its last completed output. + activeGeneration = replacement; + lifecycleState = LifecycleState.READY; + resizeMismatchLogged = false; + needsHistoryReset = true; + + if (previous != null) { + destroyGeneration(previous, false); + } } @Override public void destroy() { - destroyResources(); + Generation generation = activeGeneration; + activeGeneration = null; + lifecycleState = LifecycleState.DESTROYED; + resizeMismatchLogged = false; + if (generation != null) { + destroyGeneration(generation, true); + } + } + + private void drainGeneration(Generation generation) { + // OpenGL command buffers are submitted asynchronously and their + // waitForFence() implementation is a no-op. Drain the GL queue before + // deleting imported memory objects or releasing their owning D3D12 + // resources during resize. + RenderSystems.opengl().finish(); + generation.resources().context.waitIdle(); } - private void destroyResources() { - if (d3d12Interop != null) { - // OpenGL command buffers are submitted asynchronously and their - // waitForFence() implementation is a no-op. Drain the GL queue - // before deleting imported memory objects or releasing their - // owning D3D12 resources during resize. - RenderSystems.opengl().finish(); - d3d12Interop.waitIdle(); + private void destroyGeneration( + Generation generation, + boolean drain) { + if (drain) { + drainGeneration(generation); + } + try { + destroyD3D12Upscaler(generation.upscaler()); + } finally { + destroyInteropResources(generation.resources()); } - onBeforeD3D12InteropDestroyed(); + } + + private static void destroyInteropResources( + InteropResources resources) { + destroyPartialInteropResources( + resources.context, + resources.inputColor, + resources.inputDepth, + resources.inputMotionVectors, + resources.inputExposure, + resources.outputColor, + resources.semaphore, + resources.flippedOutput, + resources.outputFramebuffer); + } + private static void destroyPartialInteropResources( + D3D12InteropContext context, + GlD3D12ImportableTexture2D inputColor, + GlD3D12ImportableTexture2D inputDepth, + GlD3D12ImportableTexture2D inputMotionVectors, + GlD3D12ImportableTexture2D inputExposure, + GlD3D12ImportableTexture2D outputColor, + D3D12InteropSemaphore semaphore, + GlTexture2D flippedOutput, + IFrameBuffer outputFramebuffer) { if (outputFramebuffer != null) { outputFramebuffer.destroy(); - outputFramebuffer = null; } if (flippedOutput != null) { flippedOutput.destroy(); - flippedOutput = null; } if (outputColor != null) { outputColor.destroy(); - outputColor = null; } if (inputExposure != null) { inputExposure.destroy(); - inputExposure = null; } if (inputMotionVectors != null) { inputMotionVectors.destroy(); - inputMotionVectors = null; } if (inputDepth != null) { inputDepth.destroy(); - inputDepth = null; } if (inputColor != null) { inputColor.destroy(); - inputColor = null; } if (semaphore != null) { semaphore.close(); - semaphore = null; } - if (d3d12Interop != null) { - d3d12Interop.close(); - d3d12Interop = null; + if (context != null) { + context.close(); } - builtRenderWidth = -1; - builtRenderHeight = -1; - builtScreenWidth = -1; - builtScreenHeight = -1; - resizeMismatchLogged = false; } @Override public IFrameBuffer getOutputFrameBuffer() { - return outputFramebuffer; + Generation generation = activeGeneration; + return generation == null + ? null + : generation.resources().outputFramebuffer; } @Override public int getOutputTextureId() { - return flippedOutput == null + Generation generation = activeGeneration; + return generation == null ? 0 - : Math.toIntExact(flippedOutput.handle()); + : Math.toIntExact( + generation.resources().flippedOutput.handle()); } } diff --git a/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java b/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java index 46d07335..52c5695f 100644 --- a/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java +++ b/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java @@ -13,7 +13,6 @@ import io.homo.superresolution.api.InitializationDescription; import io.homo.superresolution.common.SuperResolution; import io.homo.superresolution.common.config.SuperResolutionConfig; -import io.homo.superresolution.common.minecraft.handler.RenderHandlerManager; import io.homo.superresolution.common.upscale.D3D12InteropAlgorithm; import io.homo.superresolution.common.upscale.DispatchResource; import io.homo.superresolution.core.NativeLibManager; @@ -30,15 +29,17 @@ /** * AMD FSR 4.1 through the signed FFX API Direct3D 12 provider. */ -public final class FfxFSR4D3D12 extends D3D12InteropAlgorithm { +public final class FfxFSR4D3D12 + extends D3D12InteropAlgorithm { public static final String UPSCALER_DLL_NAME = "amd_fidelityfx_upscaler_dx12.dll"; private static final long PROVIDER_ID = 0x8000006L; - private SRUpscaleContext context; - @Override - protected void onD3D12InteropCreated(InitializationDescription desc) { + protected SRUpscaleContext createD3D12Upscaler( + InitializationDescription desc, + D3D12InteropContext interop, + InteropSize size) { Path providerLibrary = NativeLibManager.LIB_SUPER_RESOLUTION_FSR4 .getTargetPath(SuperResolutionConstants.NATIVE_LIBRARIES_DIR.getPath()) .toAbsolutePath(); @@ -88,16 +89,16 @@ protected void onD3D12InteropCreated(InitializationDescription desc) { SRUpscaleContextCreateFlags.ENABLE_MOTION_VECTORS_JITTERED); } - context = new SRUpscaleContext(0); + SRUpscaleContext context = new SRUpscaleContext(0); try (SRCreateUpscaleContextDesc createDesc = SRCreateUpscaleContextDesc.createD3D12( - new SRD3D12DeviceInfo(d3d12Interop.getDevice()), + new SRD3D12DeviceInfo(interop.getDevice()), new Vector2i( - RenderHandlerManager.getScreenWidth(), - RenderHandlerManager.getScreenHeight()), + size.screenWidth(), + size.screenHeight()), new Vector2i( - RenderHandlerManager.getRenderWidth(), - RenderHandlerManager.getRenderHeight()), + size.renderWidth(), + size.renderHeight()), flags)) { SRReturnCode pathCode = createDesc .getExtraParams() @@ -114,7 +115,6 @@ protected void onD3D12InteropCreated(InitializationDescription desc) { provider, createDesc); if (createCode != SRReturnCode.OK) { - context = null; throw new IllegalStateException( "Could not create the FSR 4.1 context: " + createCode); @@ -123,56 +123,55 @@ protected void onD3D12InteropCreated(InitializationDescription desc) { SuperResolutionNativeAPI.srInitUpscaleContext(context); if (initCode != SRReturnCode.OK) { context.destroy(); - context = null; throw new IllegalStateException( "Could not initialize the FSR 4.1 context: " + initCode); } } + return context; } } @Override - protected void onBeforeD3D12InteropDestroyed() { - if (context != null) { - if (context.nativePtr > 0) { - SRReturnCode code = context.destroy(); - if (code != SRReturnCode.OK) { - SuperResolution.LOGGER.error( - "Failed to destroy FSR 4.1 context: {}", - code); - } + protected void destroyD3D12Upscaler(SRUpscaleContext context) { + if (context.nativePtr > 0) { + SRReturnCode code = context.destroy(); + if (code != SRReturnCode.OK) { + SuperResolution.LOGGER.error( + "Failed to destroy FSR 4.1 context: {}", + code); } - context = null; } } @Override - protected boolean isD3D12UpscalerReady() { - return context != null && context.nativePtr > 0; + protected boolean isD3D12UpscalerReady(SRUpscaleContext context) { + return context.nativePtr > 0; } @Override protected boolean dispatchD3D12Upscale( + SRUpscaleContext context, + D3D12InteropContext interop, long commandList, DispatchResource dispatchResource) { try (SRDispatchUpscaleDesc desc = new SRDispatchUpscaleDesc()) { desc.setCommandBuffer( SRDispatchCommandBufferInfo.createD3D12(commandList)); desc.setColor(resource( - d3d12Interop.inputColor(), + interop.inputColor(), SRResourceStates.COMPUTE_READ)); desc.setDepth(resource( - d3d12Interop.inputDepth(), + interop.inputDepth(), SRResourceStates.COMPUTE_READ)); desc.setMotionVectors(resource( - d3d12Interop.inputMotionVectors(), + interop.inputMotionVectors(), SRResourceStates.COMPUTE_READ)); // Minecraft does not currently provide a valid exposure texture. // The context therefore always uses FFX auto exposure rather than // binding the uninitialized 1x1 interop exposure resource. desc.setOutput(resource( - d3d12Interop.outputColor(), + interop.outputColor(), SRResourceStates.COMMON)); desc.setJitterOffset(new Vector2f(dispatchResource.jitterOffset())); From 80340cce1c884c5efb1e2e0383e62c7cd4fa0570 Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Wed, 29 Jul 2026 21:32:09 +0800 Subject: [PATCH 14/19] allow remote download of fsr4 dll --- .../superresolution/common/upscale/AlgorithmDescriptions.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/src/main/java/io/homo/superresolution/common/upscale/AlgorithmDescriptions.java b/common/src/main/java/io/homo/superresolution/common/upscale/AlgorithmDescriptions.java index acdca8e7..dc481cf6 100644 --- a/common/src/main/java/io/homo/superresolution/common/upscale/AlgorithmDescriptions.java +++ b/common/src/main/java/io/homo/superresolution/common/upscale/AlgorithmDescriptions.java @@ -204,6 +204,10 @@ public class AlgorithmDescriptions { ExtraResources.builder() .add(ExtraResource.builder( FfxFSR4D3D12.UPSCALER_DLL_NAME) + .addRemote( + "https://raw.githubusercontent.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/v2.3.0/Kits/FidelityFX/signedbin/amd_fidelityfx_upscaler_dx12.dll", + "AMD FidelityFX SDK v2.3.0" + ) .build()) .build() ) From 239dce6433ff472b45da30310c8d1b4aa280505b Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Wed, 29 Jul 2026 22:27:31 +0800 Subject: [PATCH 15/19] update name --- .../superresolution/common/upscale/D3D12InteropAlgorithm.java | 2 +- .../superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java | 2 +- .../core/graphics/d3d12/D3D12InteropContext.java | 2 +- .../superresolution/core/graphics/d3d12/D3D12InteropNative.java | 2 +- .../core/graphics/d3d12/D3D12InteropSemaphore.java | 2 +- .../core/graphics/d3d12/GlD3D12ImportableTexture2D.java | 2 +- .../java/io/homo/superresolution/srapi/SRD3D12DeviceInfo.java | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java b/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java index ba4d78a7..ce8decfa 100644 --- a/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java +++ b/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java @@ -1,6 +1,6 @@ /* * Super Resolution - * Copyright (c) 2026. 187J3X1-114514 + * Copyright (c) 2026. Xiang Keshen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java b/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java index 52c5695f..02cee470 100644 --- a/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java +++ b/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java @@ -1,6 +1,6 @@ /* * Super Resolution - * Copyright (c) 2026. 187J3X1-114514 + * Copyright (c) 2026. Xiang Keshen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropContext.java b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropContext.java index 19b203f6..9924e9a8 100644 --- a/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropContext.java +++ b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropContext.java @@ -1,6 +1,6 @@ /* * Super Resolution - * Copyright (c) 2026. 187J3X1-114514 + * Copyright (c) 2026. Xiang Keshen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropNative.java b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropNative.java index cbccd6a8..492a3d5b 100644 --- a/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropNative.java +++ b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropNative.java @@ -1,6 +1,6 @@ /* * Super Resolution - * Copyright (c) 2026. 187J3X1-114514 + * Copyright (c) 2026. Xiang Keshen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropSemaphore.java b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropSemaphore.java index 13ce1399..e0646487 100644 --- a/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropSemaphore.java +++ b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropSemaphore.java @@ -1,6 +1,6 @@ /* * Super Resolution - * Copyright (c) 2026. 187J3X1-114514 + * Copyright (c) 2026. Xiang Keshen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/GlD3D12ImportableTexture2D.java b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/GlD3D12ImportableTexture2D.java index e7dfd29c..f71bd4b6 100644 --- a/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/GlD3D12ImportableTexture2D.java +++ b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/GlD3D12ImportableTexture2D.java @@ -1,6 +1,6 @@ /* * Super Resolution - * Copyright (c) 2026. 187J3X1-114514 + * Copyright (c) 2026. Xiang Keshen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/common/src/main/java/io/homo/superresolution/srapi/SRD3D12DeviceInfo.java b/common/src/main/java/io/homo/superresolution/srapi/SRD3D12DeviceInfo.java index 56eb1ca4..a86d8d21 100644 --- a/common/src/main/java/io/homo/superresolution/srapi/SRD3D12DeviceInfo.java +++ b/common/src/main/java/io/homo/superresolution/srapi/SRD3D12DeviceInfo.java @@ -1,6 +1,6 @@ /* * Super Resolution - * Copyright (c) 2026. 187J3X1-114514 + * Copyright (c) 2026. Xiang Keshen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From 1a40b34b04be1315786dddcd2b9120ff10734cc4 Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Thu, 30 Jul 2026 00:44:34 +0800 Subject: [PATCH 16/19] fixed minor issues in fsr4 --- native/cpp/SRNativeFSR4/CMakeLists.txt | 3 --- .../include/sr/fsr4/ffx_api_minimal.h | 12 +++++++++ .../cpp/SRNativeFSR4/src/ffx_api_upscale.cpp | 27 ++++++++++++++----- native/cpp/SRNativeFSR4/src/sr_provider.cpp | 9 ++++--- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/native/cpp/SRNativeFSR4/CMakeLists.txt b/native/cpp/SRNativeFSR4/CMakeLists.txt index 979682d0..6654a682 100644 --- a/native/cpp/SRNativeFSR4/CMakeLists.txt +++ b/native/cpp/SRNativeFSR4/CMakeLists.txt @@ -14,9 +14,6 @@ if (MSVC) endif () set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") -link_directories( - ${PROJECT_SOURCE_DIR}/../SRNativeMain/libraries/${LIB_PLATFORM} -) include_directories( ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/../SRNativeMain/include diff --git a/native/cpp/SRNativeFSR4/include/sr/fsr4/ffx_api_minimal.h b/native/cpp/SRNativeFSR4/include/sr/fsr4/ffx_api_minimal.h index 8aa21394..8cb6ae35 100644 --- a/native/cpp/SRNativeFSR4/include/sr/fsr4/ffx_api_minimal.h +++ b/native/cpp/SRNativeFSR4/include/sr/fsr4/ffx_api_minimal.h @@ -148,6 +148,11 @@ struct FfxApiResource { uint32_t state; }; +struct FfxApiEffectMemoryUsage { + uint64_t totalUsageInBytes; + uint64_t aliasableUsageInBytes; +}; + typedef void *(*ffxAlloc)(void *pUserData, uint64_t size); typedef void (*ffxDealloc)(void *pUserData, void *pMem); @@ -196,6 +201,8 @@ struct FfxApiFunctions { #define FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12 \ FFX_API_MAKE_BACKEND_SUB_ID(FFX_API_BACKEND_ID_DX12, 0x02) #define FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION 6u +#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GPU_MEMORY_USAGE \ + FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x08) #define FFX_UPSCALER_VERSION_MAJOR 4 #define FFX_UPSCALER_VERSION_MINOR 1 @@ -265,3 +272,8 @@ struct ffxQueryGetProviderVersion { uint64_t versionId; const char *versionName; }; + +struct ffxQueryDescUpscaleGetGPUMemoryUsage { + ffxQueryDescHeader header; + FfxApiEffectMemoryUsage *gpuMemoryUsageUpscaler; +}; diff --git a/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp b/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp index 0cc202bb..6612d7de 100644 --- a/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp +++ b/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp @@ -211,7 +211,10 @@ extern "C" { &privateData->createDesc.header, nullptr); if (code != FFX_API_RETURN_OK) { - report(desc, SR_MESSAGE_TYPE_ERROR, L"FFX API failed to create an upscaling context."); + const std::wstring message = + L"FFX API failed to create an upscaling context. Code=" + + std::to_wstring(code); + report(desc, SR_MESSAGE_TYPE_ERROR, message.c_str()); FreeLibrary(module); delete privateData; return fromFfxReturnCode(code); @@ -272,10 +275,18 @@ extern "C" { } case SR_UPSCALE_CONTEXT_QUERY_GPU_MEMORY_INFO: { static thread_local SRQueryGpuMemoryResult memoryResult = {}; - // The modern API exposes a pre-creation V2 memory query. Keep - // SRAPI's context query valid until that richer query surface - // is represented in SRAPI. - memoryResult.gpuMemory = 0; + FfxApiEffectMemoryUsage usage = {}; + ffxQueryDescUpscaleGetGPUMemoryUsage query = {}; + query.header.type = + FFX_API_QUERY_DESC_TYPE_UPSCALE_GPU_MEMORY_USAGE; + query.gpuMemoryUsageUpscaler = &usage; + const ffxReturnCode_t code = privateData->functions.query( + &privateData->context, + &query.header); + if (code != FFX_API_RETURN_OK) { + return fromFfxReturnCode(code); + } + memoryResult.gpuMemory = usage.totalUsageInBytes; result->type = queryType; result->data = &memoryResult; return SR_RETURN_CODE_OK; @@ -298,10 +309,12 @@ extern "C" { if (!context || !context->userContext || !desc) { return SR_RETURN_CODE_NULL_POINTER; } - if (desc->commandList.renderApiType != SR_RENDER_API_TYPE_D3D12 || - !desc->commandList.apiCommandBuffer.d3d12.commandList) { + if (desc->commandList.renderApiType != SR_RENDER_API_TYPE_D3D12) { return SR_RETURN_CODE_UNSUPPORTED_RENDER_API; } + if (!desc->commandList.apiCommandBuffer.d3d12.commandList) { + return SR_RETURN_CODE_INVALID_ARGUMENT; + } auto *privateData = static_cast(context->userContext); ffxDispatchDescUpscale dispatchDesc = {}; diff --git a/native/cpp/SRNativeFSR4/src/sr_provider.cpp b/native/cpp/SRNativeFSR4/src/sr_provider.cpp index 39565b8e..19b61ddc 100644 --- a/native/cpp/SRNativeFSR4/src/sr_provider.cpp +++ b/native/cpp/SRNativeFSR4/src/sr_provider.cpp @@ -1,17 +1,18 @@ #include "sr/fsr4/sr_provider.h" #include "sr/fsr4/ffx_api_upscale.h" +#include + static constexpr uint32_t PROVIDER_COUNT = 1; static SRUpscaleProvider g_providers[PROVIDER_COUNT]; -static bool g_initialized = false; +static std::once_flag g_initializeOnce; static void ensureInitialized() { - if (!g_initialized) { + std::call_once(g_initializeOnce, [] { g_providers[0].providerId = SR_MODULES_FSR4_ID; g_providers[0].callbacks = srGetFfxApiUpscaleCallbacks(); - g_initialized = true; - } + }); } extern "C" { From 440e78f5930f392444f7288f4079db9804a86ffa Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Thu, 30 Jul 2026 00:58:51 +0800 Subject: [PATCH 17/19] replaced hand write header with vendered ffx headers --- native/cpp/SRNativeFSR4/CMakeLists.txt | 2 + .../include/sr/fsr4/ffx_api_minimal.h | 279 ------------------ .../cpp/SRNativeFSR4/src/ffx_api_upscale.cpp | 5 +- .../third_party/FidelityFX/2.3.0/README.md | 16 + .../2.3.0/api/include/dx12/ffx_api_dx12.h | 272 +++++++++++++++++ .../FidelityFX/2.3.0/api/include/ffx_api.h | 213 +++++++++++++ .../2.3.0/api/include/ffx_api_types.h | 257 ++++++++++++++++ .../2.3.0/upscalers/include/ffx_upscale.h | 230 +++++++++++++++ 8 files changed, 994 insertions(+), 280 deletions(-) delete mode 100644 native/cpp/SRNativeFSR4/include/sr/fsr4/ffx_api_minimal.h create mode 100644 native/cpp/third_party/FidelityFX/2.3.0/README.md create mode 100644 native/cpp/third_party/FidelityFX/2.3.0/api/include/dx12/ffx_api_dx12.h create mode 100644 native/cpp/third_party/FidelityFX/2.3.0/api/include/ffx_api.h create mode 100644 native/cpp/third_party/FidelityFX/2.3.0/api/include/ffx_api_types.h create mode 100644 native/cpp/third_party/FidelityFX/2.3.0/upscalers/include/ffx_upscale.h diff --git a/native/cpp/SRNativeFSR4/CMakeLists.txt b/native/cpp/SRNativeFSR4/CMakeLists.txt index 6654a682..5ac9bcce 100644 --- a/native/cpp/SRNativeFSR4/CMakeLists.txt +++ b/native/cpp/SRNativeFSR4/CMakeLists.txt @@ -17,6 +17,8 @@ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") include_directories( ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/../SRNativeMain/include + ${PROJECT_SOURCE_DIR}/../third_party/FidelityFX/2.3.0/api/include + ${PROJECT_SOURCE_DIR}/../third_party/FidelityFX/2.3.0/upscalers/include ${PROJECT_SOURCE_DIR}/src ) diff --git a/native/cpp/SRNativeFSR4/include/sr/fsr4/ffx_api_minimal.h b/native/cpp/SRNativeFSR4/include/sr/fsr4/ffx_api_minimal.h deleted file mode 100644 index 8cb6ae35..00000000 --- a/native/cpp/SRNativeFSR4/include/sr/fsr4/ffx_api_minimal.h +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Minimal ABI declarations for the AMD FSR SDK 2.3 FFX API. - * - * These declarations intentionally cover only the signed DX12 upscaler DLL - * surface used by SRNativeFSR. They mirror AMD's MIT-licensed ffx_api.h, - * ffx_api_types.h, ffx_api_dx12.h, and ffx_upscale.h structures without - * requiring the full SDK as a build dependency. - * - * Copyright (C) 2026 Advanced Micro Devices, Inc. - * Copyright (C) 2026 Super Resolution contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#pragma once - -#include - -typedef void *ffxContext; -typedef uint32_t ffxReturnCode_t; -typedef uint64_t ffxStructType_t; - -enum FfxApiReturnCodes { - FFX_API_RETURN_OK = 0, - FFX_API_RETURN_ERROR = 1, - FFX_API_RETURN_ERROR_UNKNOWN_DESCTYPE = 2, - FFX_API_RETURN_ERROR_RUNTIME_ERROR = 3, - FFX_API_RETURN_NO_PROVIDER = 4, - FFX_API_RETURN_ERROR_MEMORY = 5, - FFX_API_RETURN_ERROR_PARAMETER = 6, - FFX_API_RETURN_PROVIDER_NO_SUPPORT_NEW_DESCTYPE = 7, -}; - -struct ffxApiHeader { - ffxStructType_t type; - ffxApiHeader *pNext; -}; - -typedef ffxApiHeader ffxCreateContextDescHeader; -typedef ffxApiHeader ffxQueryDescHeader; -typedef ffxApiHeader ffxDispatchDescHeader; -typedef void (*ffxApiMessage)(uint32_t type, const wchar_t *message); - -struct FfxApiDimensions2D { - uint32_t width; - uint32_t height; -}; - -struct FfxApiFloatCoords2D { - float x; - float y; -}; - -enum FfxApiSurfaceFormat { - FFX_API_SURFACE_FORMAT_UNKNOWN, - FFX_API_SURFACE_FORMAT_R32G32B32A32_TYPELESS, - FFX_API_SURFACE_FORMAT_R32G32B32A32_UINT, - FFX_API_SURFACE_FORMAT_R32G32B32A32_FLOAT, - FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT, - FFX_API_SURFACE_FORMAT_R32G32B32_FLOAT, - FFX_API_SURFACE_FORMAT_R32G32_FLOAT, - FFX_API_SURFACE_FORMAT_R8_UINT, - FFX_API_SURFACE_FORMAT_R32_UINT, - FFX_API_SURFACE_FORMAT_R8G8B8A8_TYPELESS, - FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM, - FFX_API_SURFACE_FORMAT_R8G8B8A8_SNORM, - FFX_API_SURFACE_FORMAT_R8G8B8A8_SRGB, - FFX_API_SURFACE_FORMAT_B8G8R8A8_TYPELESS, - FFX_API_SURFACE_FORMAT_B8G8R8A8_UNORM, - FFX_API_SURFACE_FORMAT_B8G8R8A8_SRGB, - FFX_API_SURFACE_FORMAT_R11G11B10_FLOAT, - FFX_API_SURFACE_FORMAT_R10G10B10A2_UNORM, - FFX_API_SURFACE_FORMAT_R16G16_FLOAT, - FFX_API_SURFACE_FORMAT_R16G16_UINT, - FFX_API_SURFACE_FORMAT_R16G16_SINT, - FFX_API_SURFACE_FORMAT_R16_FLOAT, - FFX_API_SURFACE_FORMAT_R16_UINT, - FFX_API_SURFACE_FORMAT_R16_UNORM, - FFX_API_SURFACE_FORMAT_R16_SNORM, - FFX_API_SURFACE_FORMAT_R8_UNORM, - FFX_API_SURFACE_FORMAT_R8G8_UNORM, - FFX_API_SURFACE_FORMAT_R8G8_UINT, - FFX_API_SURFACE_FORMAT_R32_FLOAT, - FFX_API_SURFACE_FORMAT_R9G9B9E5_SHAREDEXP, - FFX_API_SURFACE_FORMAT_R16G16B16A16_TYPELESS, - FFX_API_SURFACE_FORMAT_R32G32_TYPELESS, - FFX_API_SURFACE_FORMAT_R10G10B10A2_TYPELESS, - FFX_API_SURFACE_FORMAT_R16G16_TYPELESS, - FFX_API_SURFACE_FORMAT_R16_TYPELESS, - FFX_API_SURFACE_FORMAT_R8_TYPELESS, - FFX_API_SURFACE_FORMAT_R8G8_TYPELESS, - FFX_API_SURFACE_FORMAT_R32_TYPELESS, - FFX_API_SURFACE_FORMAT_R32G32_UINT, - FFX_API_SURFACE_FORMAT_R8_SNORM, -}; - -enum FfxApiResourceFlags { - FFX_API_RESOURCE_FLAGS_NONE = 0, -}; - -enum FfxApiResourceType { - FFX_API_RESOURCE_TYPE_BUFFER, - FFX_API_RESOURCE_TYPE_TEXTURE1D, - FFX_API_RESOURCE_TYPE_TEXTURE2D, - FFX_API_RESOURCE_TYPE_TEXTURE_CUBE, - FFX_API_RESOURCE_TYPE_TEXTURE3D, -}; - -struct FfxApiResourceDescription { - uint32_t type; - uint32_t format; - union { - uint32_t width; - uint32_t size; - }; - union { - uint32_t height; - uint32_t stride; - }; - union { - uint32_t depth; - uint32_t alignment; - }; - uint32_t mipCount; - uint32_t flags; - uint32_t usage; -}; - -struct FfxApiResource { - void *resource; - FfxApiResourceDescription description; - uint32_t state; -}; - -struct FfxApiEffectMemoryUsage { - uint64_t totalUsageInBytes; - uint64_t aliasableUsageInBytes; -}; - -typedef void *(*ffxAlloc)(void *pUserData, uint64_t size); -typedef void (*ffxDealloc)(void *pUserData, void *pMem); - -struct ffxAllocationCallbacks { - void *pUserData; - ffxAlloc alloc; - ffxDealloc dealloc; -}; - -typedef ffxReturnCode_t (*PfnFfxCreateContext)( - ffxContext *context, - ffxCreateContextDescHeader *desc, - const ffxAllocationCallbacks *memCb); -typedef ffxReturnCode_t (*PfnFfxDestroyContext)( - ffxContext *context, - const ffxAllocationCallbacks *memCb); -typedef ffxReturnCode_t (*PfnFfxQuery)( - ffxContext *context, - ffxQueryDescHeader *desc); -typedef ffxReturnCode_t (*PfnFfxDispatch)( - ffxContext *context, - const ffxDispatchDescHeader *desc); - -struct FfxApiFunctions { - PfnFfxCreateContext createContext; - PfnFfxDestroyContext destroyContext; - PfnFfxQuery query; - PfnFfxDispatch dispatch; -}; - -#define FFX_API_EFFECT_MASK 0x00ff0000u -#define FFX_API_BACKEND_MASK 0xff000000u -#define FFX_API_EFFECT_ID_UPSCALE 0x00010000u -#define FFX_API_BACKEND_ID_DX12 0x00000000u -#define FFX_API_MAKE_EFFECT_SUB_ID(effectId, subversion) \ - ((effectId & FFX_API_EFFECT_MASK) | (subversion & ~FFX_API_EFFECT_MASK)) -#define FFX_API_MAKE_BACKEND_SUB_ID(backendId, subversion) \ - ((backendId & FFX_API_BACKEND_MASK) | (subversion & ~FFX_API_BACKEND_MASK)) - -#define FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE \ - FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x00) -#define FFX_API_DISPATCH_DESC_TYPE_UPSCALE \ - FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x01) -#define FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE_VERSION \ - FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x0b) -#define FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12 \ - FFX_API_MAKE_BACKEND_SUB_ID(FFX_API_BACKEND_ID_DX12, 0x02) -#define FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION 6u -#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GPU_MEMORY_USAGE \ - FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x08) - -#define FFX_UPSCALER_VERSION_MAJOR 4 -#define FFX_UPSCALER_VERSION_MINOR 1 -#define FFX_UPSCALER_VERSION_PATCH 1 -#define FFX_UPSCALER_MAKE_VERSION(major, minor, patch) \ - (((major) << 22) | ((minor) << 12) | (patch)) -#define FFX_UPSCALER_VERSION \ - FFX_UPSCALER_MAKE_VERSION( \ - FFX_UPSCALER_VERSION_MAJOR, \ - FFX_UPSCALER_VERSION_MINOR, \ - FFX_UPSCALER_VERSION_PATCH) - -enum FfxApiCreateContextUpscaleFlags { - FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE = (1 << 0), - FFX_UPSCALE_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION = (1 << 2), - FFX_UPSCALE_ENABLE_DEPTH_INVERTED = (1 << 3), - FFX_UPSCALE_ENABLE_AUTO_EXPOSURE = (1 << 5), - FFX_UPSCALE_ENABLE_DEBUG_CHECKING = (1 << 7), -}; - -struct ffxCreateContextDescUpscale { - ffxCreateContextDescHeader header; - uint32_t flags; - FfxApiDimensions2D maxRenderSize; - FfxApiDimensions2D maxUpscaleSize; - ffxApiMessage fpMessage; -}; - -struct ffxCreateContextDescUpscaleVersion { - ffxCreateContextDescHeader header; - uint32_t version; -}; - -struct ffxCreateBackendDX12Desc { - ffxCreateContextDescHeader header; - void *device; -}; - -struct ffxDispatchDescUpscale { - ffxDispatchDescHeader header; - void *commandList; - FfxApiResource color; - FfxApiResource depth; - FfxApiResource motionVectors; - FfxApiResource exposure; - FfxApiResource reactive; - FfxApiResource transparencyAndComposition; - FfxApiResource output; - FfxApiFloatCoords2D jitterOffset; - FfxApiFloatCoords2D motionVectorScale; - FfxApiDimensions2D renderSize; - FfxApiDimensions2D upscaleSize; - bool enableSharpening; - float sharpness; - float frameTimeDelta; - float preExposure; - bool reset; - float cameraNear; - float cameraFar; - float cameraFovAngleVertical; - float viewSpaceToMetersFactor; - uint32_t flags; -}; - -struct ffxQueryGetProviderVersion { - ffxQueryDescHeader header; - uint64_t versionId; - const char *versionName; -}; - -struct ffxQueryDescUpscaleGetGPUMemoryUsage { - ffxQueryDescHeader header; - FfxApiEffectMemoryUsage *gpuMemoryUsageUpscaler; -}; diff --git a/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp b/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp index 6612d7de..f6ed2b6a 100644 --- a/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp +++ b/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp @@ -7,7 +7,10 @@ #endif #include -#include "sr/fsr4/ffx_api_minimal.h" +#include +#include +#include +#include #include #include diff --git a/native/cpp/third_party/FidelityFX/2.3.0/README.md b/native/cpp/third_party/FidelityFX/2.3.0/README.md new file mode 100644 index 00000000..356ef4ce --- /dev/null +++ b/native/cpp/third_party/FidelityFX/2.3.0/README.md @@ -0,0 +1,16 @@ +# AMD FSR SDK 2.3.0 headers + +This directory contains the header-only subset of the official AMD FSR SDK +2.3.0 required by `SRNativeFSR4`: + +- `api/include/ffx_api.h` +- `api/include/ffx_api_types.h` +- `api/include/dx12/ffx_api_dx12.h` +- `upscalers/include/ffx_upscale.h` + +Source: + +Pinned source commit: `60f4ea81909200d8542eca14dccb2628b763a9a3` + +The headers retain AMD's copyright and MIT license notices. The corresponding +license text is also available at `native/cpp/LICENSE-FFXSDK.txt`. diff --git a/native/cpp/third_party/FidelityFX/2.3.0/api/include/dx12/ffx_api_dx12.h b/native/cpp/third_party/FidelityFX/2.3.0/api/include/dx12/ffx_api_dx12.h new file mode 100644 index 00000000..1b711807 --- /dev/null +++ b/native/cpp/third_party/FidelityFX/2.3.0/api/include/dx12/ffx_api_dx12.h @@ -0,0 +1,272 @@ +// This file is part of the FidelityFX SDK. +// +// Copyright (C) 2026 Advanced Micro Devices, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#pragma once +#include "../ffx_api.h" +#include "../ffx_api_types.h" +#include +#include +#include + +/// Resource allocation callback function. +typedef ffxReturnCode_t(*PfnFfxResourceAllocatorFunc)(uint32_t effectId, + D3D12_RESOURCE_STATES initialState, + const D3D12_HEAP_PROPERTIES* pHeapProps, + const D3D12_RESOURCE_DESC* pD3DDesc, + const struct FfxApiResourceDescription* pFfxDesc, + const D3D12_CLEAR_VALUE* pOptimizedClear, + ID3D12Resource** ppD3DResource); + +/// Resource destruction callback function. +typedef ffxReturnCode_t(*PfnFfxResourceDeallocatorFunc)(uint32_t effectId, ID3D12Resource* pResource); + +// Heap allocation callback function. +typedef ffxReturnCode_t(*PfnFfxHeapAllocatorFunc)(uint32_t effectId, const D3D12_HEAP_DESC* pHeapDesc, bool aliasable, ID3D12Heap** ppD3DHeap, uint64_t* pHeapStartOffset); + +// Heap destruction callback function. +typedef ffxReturnCode_t(*PfnFfxHeapDeallocatorFunc)(uint32_t effectId, ID3D12Heap* pD3DHeap, uint64_t heapStartOffset, uint64_t heapSize); + +#define FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12 FFX_API_MAKE_BACKEND_SUB_ID(FFX_API_BACKEND_ID_DX12, 0x02) +struct ffxCreateBackendDX12Desc +{ + ffxCreateContextDescHeader header; + ID3D12Device* device; ///< Device on which the backend will run. +}; + +#define FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12_ALLOCATION_CALLBACKS FFX_API_MAKE_BACKEND_SUB_ID(FFX_API_BACKEND_ID_DX12, 0x03) +struct ffxCreateBackendDX12AllocationCallbacksDesc +{ + ffxCreateContextDescHeader header; + PfnFfxResourceAllocatorFunc pfnFfxResourceAllocator; ///< Resource allocation function (can be null) + PfnFfxResourceDeallocatorFunc pfnFfxResourceDeallocator; ///< Resource deallocation function (can be null) + PfnFfxHeapAllocatorFunc pfnFfxHeapAllocator; ///< Heap allocation function (can be null) + PfnFfxHeapDeallocatorFunc pfnFfxHeapDeallocator; ///< Heap deallocation function (can be null) + FfxApiConstantBufferAllocator pfnFfxConstantBufferAllocator; ///< Constant buffer allocation function (can be null) +}; + +#if defined(__cplusplus) + +static inline uint32_t ffxApiGetSurfaceFormatDX12(DXGI_FORMAT format) +{ + switch (format) + { + case DXGI_FORMAT_R32G32B32A32_TYPELESS: + return FFX_API_SURFACE_FORMAT_R32G32B32A32_TYPELESS; + case DXGI_FORMAT_R32G32B32A32_FLOAT: + return FFX_API_SURFACE_FORMAT_R32G32B32A32_FLOAT; + case DXGI_FORMAT_R32G32B32A32_UINT: + return FFX_API_SURFACE_FORMAT_R32G32B32A32_UINT; + //case DXGI_FORMAT_R32G32B32A32_SINT: + //case DXGI_FORMAT_R32G32B32_TYPELESS: + //case DXGI_FORMAT_R32G32B32_FLOAT: + //case DXGI_FORMAT_R32G32B32_UINT: + //case DXGI_FORMAT_R32G32B32_SINT: + + case DXGI_FORMAT_R16G16B16A16_TYPELESS: + return FFX_API_SURFACE_FORMAT_R16G16B16A16_TYPELESS; + case DXGI_FORMAT_R16G16B16A16_FLOAT: + return FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT; + //case DXGI_FORMAT_R16G16B16A16_UNORM: + //case DXGI_FORMAT_R16G16B16A16_UINT: + //case DXGI_FORMAT_R16G16B16A16_SNORM: + //case DXGI_FORMAT_R16G16B16A16_SINT: + + case DXGI_FORMAT_R32G32_TYPELESS: + return FFX_API_SURFACE_FORMAT_R32G32_TYPELESS; + case DXGI_FORMAT_R32G32_FLOAT: + return FFX_API_SURFACE_FORMAT_R32G32_FLOAT; + //case DXGI_FORMAT_R32G32_FLOAT: + //case DXGI_FORMAT_R32G32_UINT: + //case DXGI_FORMAT_R32G32_SINT: + + case DXGI_FORMAT_R32G8X24_TYPELESS: + case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: + case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS: + return FFX_API_SURFACE_FORMAT_R32_FLOAT; + + case DXGI_FORMAT_R24G8_TYPELESS: + case DXGI_FORMAT_D24_UNORM_S8_UINT: + case DXGI_FORMAT_R24_UNORM_X8_TYPELESS: + return FFX_API_SURFACE_FORMAT_R32_UINT; + + case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT: + case DXGI_FORMAT_X24_TYPELESS_G8_UINT: + return FFX_API_SURFACE_FORMAT_R8_UINT; + + case DXGI_FORMAT_R10G10B10A2_TYPELESS: + return FFX_API_SURFACE_FORMAT_R10G10B10A2_TYPELESS; + case DXGI_FORMAT_R10G10B10A2_UNORM: + return FFX_API_SURFACE_FORMAT_R10G10B10A2_UNORM; + //case DXGI_FORMAT_R10G10B10A2_UINT: + + case DXGI_FORMAT_R11G11B10_FLOAT: + return FFX_API_SURFACE_FORMAT_R11G11B10_FLOAT; + + case DXGI_FORMAT_R8G8B8A8_TYPELESS: + return FFX_API_SURFACE_FORMAT_R8G8B8A8_TYPELESS; + case DXGI_FORMAT_R8G8B8A8_UNORM: + return FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM; + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + return FFX_API_SURFACE_FORMAT_R8G8B8A8_SRGB; + //case DXGI_FORMAT_R8G8B8A8_UINT: + case DXGI_FORMAT_R8G8B8A8_SNORM: + return FFX_API_SURFACE_FORMAT_R8G8B8A8_SNORM; + + case DXGI_FORMAT_B8G8R8A8_TYPELESS: + return FFX_API_SURFACE_FORMAT_B8G8R8A8_TYPELESS; + case DXGI_FORMAT_B8G8R8A8_UNORM: + return FFX_API_SURFACE_FORMAT_B8G8R8A8_UNORM; + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + return FFX_API_SURFACE_FORMAT_B8G8R8A8_SRGB; + + case DXGI_FORMAT_R16G16_TYPELESS: + return FFX_API_SURFACE_FORMAT_R16G16_TYPELESS; + case DXGI_FORMAT_R16G16_FLOAT: + return FFX_API_SURFACE_FORMAT_R16G16_FLOAT; + //case DXGI_FORMAT_R16G16_UNORM: + case DXGI_FORMAT_R16G16_UINT: + return FFX_API_SURFACE_FORMAT_R16G16_UINT; + //case DXGI_FORMAT_R16G16_SNORM + //case DXGI_FORMAT_R16G16_SINT + + //case DXGI_FORMAT_R32_SINT: + case DXGI_FORMAT_R32_UINT: + return FFX_API_SURFACE_FORMAT_R32_UINT; + case DXGI_FORMAT_R32_TYPELESS: + return FFX_API_SURFACE_FORMAT_R32_TYPELESS; + case DXGI_FORMAT_D32_FLOAT: + case DXGI_FORMAT_R32_FLOAT: + return FFX_API_SURFACE_FORMAT_R32_FLOAT; + + case DXGI_FORMAT_R8G8_UINT: + return FFX_API_SURFACE_FORMAT_R8G8_UINT; + case DXGI_FORMAT_R8G8_TYPELESS: + return FFX_API_SURFACE_FORMAT_R8G8_TYPELESS; + case DXGI_FORMAT_R8G8_UNORM: + return FFX_API_SURFACE_FORMAT_R8G8_UNORM; + //case DXGI_FORMAT_R8G8_SNORM: + //case DXGI_FORMAT_R8G8_SINT: + + case DXGI_FORMAT_R16_TYPELESS: + return FFX_API_SURFACE_FORMAT_R16_TYPELESS; + case DXGI_FORMAT_R16_FLOAT: + return FFX_API_SURFACE_FORMAT_R16_FLOAT; + case DXGI_FORMAT_R16_UINT: + return FFX_API_SURFACE_FORMAT_R16_UINT; + case DXGI_FORMAT_D16_UNORM: + case DXGI_FORMAT_R16_UNORM: + return FFX_API_SURFACE_FORMAT_R16_UNORM; + case DXGI_FORMAT_R16_SNORM: + return FFX_API_SURFACE_FORMAT_R16_SNORM; + //case DXGI_FORMAT_R16_SINT: + + case DXGI_FORMAT_R8_TYPELESS: + return FFX_API_SURFACE_FORMAT_R8_TYPELESS; + case DXGI_FORMAT_R8_UNORM: + case DXGI_FORMAT_A8_UNORM: + return FFX_API_SURFACE_FORMAT_R8_UNORM; + case DXGI_FORMAT_R8_SNORM: + return FFX_API_SURFACE_FORMAT_R8_SNORM; + case DXGI_FORMAT_R8_UINT: + return FFX_API_SURFACE_FORMAT_R8_UINT; + //case DXGI_FORMAT_R8_SNORM: + //case DXGI_FORMAT_R8_SINT: + //case DXGI_FORMAT_R1_UNORM: + + case DXGI_FORMAT_R9G9B9E5_SHAREDEXP: + return FFX_API_SURFACE_FORMAT_R9G9B9E5_SHAREDEXP; + + case DXGI_FORMAT_UNKNOWN: + default: + return FFX_API_SURFACE_FORMAT_UNKNOWN; + } +} + +static inline FfxApiResource ffxApiGetResourceDX12(ID3D12Resource* pRes, uint32_t state = FFX_API_RESOURCE_STATE_COMPUTE_READ, uint32_t additionalUsages = 0) +{ + FfxApiResource res{}; + res.resource = pRes; + res.state = state; + if (!pRes) return res; + + D3D12_RESOURCE_DESC desc = pRes->GetDesc(); + if (desc.Dimension == D3D12_RESOURCE_DIMENSION_UNKNOWN) { + return res; + } + + if (desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) + { + res.description.flags = FFX_API_RESOURCE_FLAGS_NONE; + res.description.usage = FFX_API_RESOURCE_USAGE_UAV; + res.description.size = static_cast(desc.Width); + res.description.stride = static_cast(desc.Height); + res.description.type = FFX_API_RESOURCE_TYPE_BUFFER; + } + else + { + res.description.flags = FFX_API_RESOURCE_FLAGS_NONE; + if (desc.Format == DXGI_FORMAT_D16_UNORM || desc.Format == DXGI_FORMAT_D32_FLOAT) + { + res.description.usage = FFX_API_RESOURCE_USAGE_DEPTHTARGET; + } + else if (desc.Format == DXGI_FORMAT_D24_UNORM_S8_UINT || desc.Format == DXGI_FORMAT_D32_FLOAT_S8X24_UINT) + { + res.description.usage = FFX_API_RESOURCE_USAGE_DEPTHTARGET | FFX_API_RESOURCE_USAGE_STENCILTARGET; + } + else + { + res.description.usage = FFX_API_RESOURCE_USAGE_READ_ONLY; + } + + if (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS) + res.description.usage |= FFX_API_RESOURCE_USAGE_UAV; + + res.description.width = static_cast(desc.Width); + res.description.height = static_cast(desc.Height); + res.description.depth = static_cast(desc.DepthOrArraySize); + res.description.mipCount = static_cast(desc.MipLevels); + + switch (desc.Dimension) + { + case D3D12_RESOURCE_DIMENSION_TEXTURE1D: + res.description.type = FFX_API_RESOURCE_TYPE_TEXTURE1D; + break; + case D3D12_RESOURCE_DIMENSION_TEXTURE2D: + if (desc.DepthOrArraySize == 6) + res.description.type = FFX_API_RESOURCE_TYPE_TEXTURE_CUBE; + else + res.description.type = FFX_API_RESOURCE_TYPE_TEXTURE2D; + break; + case D3D12_RESOURCE_DIMENSION_TEXTURE3D: + res.description.type = FFX_API_RESOURCE_TYPE_TEXTURE3D; + break; + default: + break; // D3D12_RESOURCE_DIMENSION_BUFFER and D3D12_RESOURCE_DIMENSION_UNKNOWN are handled above. + } + } + + res.description.format = ffxApiGetSurfaceFormatDX12(desc.Format); + res.description.usage |= additionalUsages; + return res; +} + +#endif diff --git a/native/cpp/third_party/FidelityFX/2.3.0/api/include/ffx_api.h b/native/cpp/third_party/FidelityFX/2.3.0/api/include/ffx_api.h new file mode 100644 index 00000000..484ec23f --- /dev/null +++ b/native/cpp/third_party/FidelityFX/2.3.0/api/include/ffx_api.h @@ -0,0 +1,213 @@ +// This file is part of the FidelityFX SDK. +// +// Copyright (C) 2026 Advanced Micro Devices, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#pragma once + +#if defined(__cplusplus) +extern "C" { +#endif // #if defined(__cplusplus) + +#define FFX_API_ENTRY __declspec(dllexport) + +#include + +enum FfxApiReturnCodes +{ + FFX_API_RETURN_OK = 0, ///< The oparation was successful. + FFX_API_RETURN_ERROR = 1, ///< An error occurred that is not further specified. + FFX_API_RETURN_ERROR_UNKNOWN_DESCTYPE = 2, ///< The structure type given was not recognized for the function or context with which it was used. This is likely a programming error. + FFX_API_RETURN_ERROR_RUNTIME_ERROR = 3, ///< The underlying runtime (e.g. D3D12, Vulkan) or effect returned an error code. + FFX_API_RETURN_NO_PROVIDER = 4, ///< No provider was found for the given structure type. This is likely a programming error. + FFX_API_RETURN_ERROR_MEMORY = 5, ///< A memory allocation failed. + FFX_API_RETURN_ERROR_PARAMETER = 6, ///< A parameter was invalid, e.g. a null pointer, empty resource or out-of-bounds enum value. + FFX_API_RETURN_PROVIDER_NO_SUPPORT_NEW_DESCTYPE = 7, ///< The structure type given is new and not supported in the old provider. This is likely fixed with driver upgrade or effect DLL upgrade. +}; + +typedef void* ffxContext; +typedef uint32_t ffxReturnCode_t; + +#define FFX_API_EFFECT_MASK 0x00ff0000u +#define FFX_API_BACKEND_MASK 0xff000000u +#define FFX_API_EFFECT_ID_GENERAL 0x00000000u + +// Base Descriptor types +typedef uint64_t ffxStructType_t; +typedef struct ffxApiHeader +{ + ffxStructType_t type; ///< The structure type. Must always be set to the corresponding value for any structure (found nearby with a similar name). + struct ffxApiHeader* pNext; ///< Pointer to next structure, used for optional parameters and extensions. Can be null. +} ffxApiHeader; + +typedef ffxApiHeader ffxCreateContextDescHeader; +typedef ffxApiHeader ffxConfigureDescHeader; +typedef ffxApiHeader ffxQueryDescHeader; +typedef ffxApiHeader ffxDispatchDescHeader; + +// Extensions for global debug +#define FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_SILENCE 0x0000000u +#define FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_ERRORS 0x0000001u +#define FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_WARNINGS 0x0000002u +#define FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_VERBOSE 0xfffffffu + +enum FfxApiMsgType +{ + FFX_API_MESSAGE_TYPE_ERROR = 0, + FFX_API_MESSAGE_TYPE_WARNING = 1, + FFX_API_MESSAGE_TYPE_COUNT +}; + +typedef void (*ffxApiMessage)(uint32_t type, const wchar_t* message); + +#define FFX_API_CONFIGURE_DESC_TYPE_GLOBALDEBUG1 0x0000001u +struct ffxConfigureDescGlobalDebug1 +{ + ffxConfigureDescHeader header; + ffxApiMessage fpMessage; + uint32_t debugLevel; +}; + +#define FFX_API_CONFIGURE_DESC_TYPE_GLOBALDEBUG 7u +struct ffxConfigureDescGlobalDebug +{ + ffxConfigureDescHeader header; + uint64_t effectId; + ffxApiMessage fpMessage; ///< A pointer to a function that can receive messages from the runtime. May be null. + uint32_t debugLevel; +}; + +#define FFX_API_QUERY_DESC_TYPE_GET_VERSIONS 4u +struct ffxQueryDescGetVersions +{ + ffxQueryDescHeader header; + uint64_t createDescType; ///< Create description for the effect whose versions should be enumerated. + void* device; ///< For DX12: pointer to ID3D12Device. + uint64_t *outputCount; ///< Input capacity of id and name arrays. Output number of returned versions. If initially zero, output is number of available versions. + uint64_t *versionIds; ///< Output array of version ids to be used as version overrides. If null, only names and count are returned. + const char** versionNames; ///< Output array of version names for display. If null, only ids and count are returned. If both this and versionIds are null, only count is returned. +}; + +#define FFX_API_DESC_TYPE_OVERRIDE_VERSION 5u +struct ffxOverrideVersion +{ + ffxApiHeader header; + uint64_t versionId; ///< Id of version to use. Must be a value returned from a query in ffxQueryDescGetVersions.versionIds array. +}; + +#define FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION 6u +struct ffxQueryGetProviderVersion +{ + ffxQueryDescHeader header; + uint64_t versionId; ///< Id of provider being used for queried context. 0 if invalid. + const char* versionName; ///< Version name for display. If nullptr, the query was invalid. +}; + +// Memory allocation function. Must return a valid pointer to at least size bytes of memory aligned to hold any type. +// May return null to indicate failure. Standard library malloc fulfills this requirement. +typedef void* (*ffxAlloc)(void* pUserData, uint64_t size); + +// Memory deallocation function. May be called with null pointer as second argument. +typedef void (*ffxDealloc)(void* pUserData, void* pMem); + +typedef struct ffxAllocationCallbacks +{ + void* pUserData; + ffxAlloc alloc; + ffxDealloc dealloc; +} ffxAllocationCallbacks; + +// Creates a FFX object context. +// Depending on the desc structures provided to this function, the context will be created with the desired version and attributes. +// Non-zero return indicates error code. +// Pointers passed in desc must remain live until ffxDestroyContext is called on the context. +// MemCb may be null; the system allocator (malloc/free) will be used in this case. +FFX_API_ENTRY ffxReturnCode_t ffxCreateContext(ffxContext* context, ffxCreateContextDescHeader* desc, const ffxAllocationCallbacks* memCb); +typedef ffxReturnCode_t (*PfnFfxCreateContext)(ffxContext* context, ffxCreateContextDescHeader* desc, const ffxAllocationCallbacks* memCb); + +// Destroys an FFX object context. +// Non-zero return indicates error code. +// MemCb must be compatible with the callbacks passed into ffxCreateContext. +FFX_API_ENTRY ffxReturnCode_t ffxDestroyContext(ffxContext* context, const ffxAllocationCallbacks* memCb); +typedef ffxReturnCode_t (*PfnFfxDestroyContext)(ffxContext* context, const ffxAllocationCallbacks* memCb); + +// Configures the provided FFX object context. +// If context is null, configure operates on any global state. +// Non-zero return indicates error code. +FFX_API_ENTRY ffxReturnCode_t ffxConfigure(ffxContext* context, const ffxConfigureDescHeader* desc); +typedef ffxReturnCode_t (*PfnFfxConfigure)(ffxContext* context, const ffxConfigureDescHeader* desc); + +// Queries the provided FFX object context. +// If context is null, query operates on any global state. +// Non-zero return indicates error code. +FFX_API_ENTRY ffxReturnCode_t ffxQuery(ffxContext* context, ffxQueryDescHeader* desc); +typedef ffxReturnCode_t (*PfnFfxQuery)(ffxContext* context, ffxQueryDescHeader* desc); + +// Dispatches work on the given FFX object context defined by the dispatch descriptor. +// Non-zero return indicates error code. +FFX_API_ENTRY ffxReturnCode_t ffxDispatch(ffxContext* context, const ffxDispatchDescHeader* desc); +typedef ffxReturnCode_t (*PfnFfxDispatch)(ffxContext* context, const ffxDispatchDescHeader* desc); + +// FFX_API_EFFECT_IDs +#define FFX_API_EFFECT_ID_UPSCALE 0x00010000u +#define FFX_API_EFFECT_ID_FRAMEGENERATION 0x00020000u +#define FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN 0x00030000u +// Need to keep this ID around for the deprecated VK frame gen swapchain +#define FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN_VK 0x00040000u +// Need to keep this ID around for the deprecated VK frame gen swapchain +#define FFX_API_EFFECT_ID_DENOISER 0x00050000u +#define FFX_API_EFFECT_ID_RADIANCECACHE 0x00060000u + +#define FFX_API_MAKE_EFFECT_SUB_ID(effectId, subversion) ((effectId & FFX_API_EFFECT_MASK) | (subversion & ~FFX_API_EFFECT_MASK)) + +// FFX_APID_BACKEND_IDs +#define FFX_API_BACKEND_ID_DX12 0x00000000u +#define FFX_API_BACKEND_ID_XBOX 0x01000000u +#define FFX_API_BACKEND_ID_VK 0x02000000u // For new effects going forward, please use this backend ID for vulkan specifics + +#define FFX_API_MAKE_BACKEND_SUB_ID(backendId, subversion) ((backendId & FFX_API_BACKEND_MASK) | (subversion & ~FFX_API_BACKEND_MASK)) + +// Combiner for BACKEND-specific EFFECT sub-Ids +#define FFX_API_MAKE_BACKEND_EFFECT_SUB_ID(backendId, effectId, subversion) ((subversion & ~FFX_API_EFFECT_MASK) | (effectId & FFX_API_EFFECT_MASK) | (backendId & FFX_API_BACKEND_MASK) | (subversion & ~(FFX_API_BACKEND_MASK | FFX_API_EFFECT_MASK))) + +// Pragma macros for controlling warnings so that deprecations take affect externally but can be suppressed internally. +// This is so we can maintain the API/ABI until we are ready to make the breaking change. +// These are sadly compiler specific. +#if defined(_MSC_VER) && !defined(__clang__) && !defined(__GNUC__) && !defined(__INTEL_COMPILER) +#define FFX_PRAGMA_WARNING_PUSH warning( push ) +#define FFX_PRAGMA_WARNING_POP warning( pop ) +#define FFX_PRAGMA_WARNING_DISABLE_DEPRECATIONS warning( disable: 4996 ) +#define FFX_PRAGMA_WARNING_WARN_DEPRECATIONS warning( default: 4996 ) +#elif defined(__clang__) || defined(__GNUC__) +#define FFX_PRAGMA_WARNING_PUSH GCC diagnostic push +#define FFX_PRAGMA_WARNING_POP GCC diagnostic pop +#define FFX_PRAGMA_WARNING_DISABLE_DEPRECATIONS GCC diagnostic ignored "-Wdeprecated" +#define FFX_PRAGMA_WARNING_WARN_DEPRECATIONS GCC diagnostic warning "-Wdeprecated" +#else +#define FFX_PRAGMA_WARNING_PUSH +#define FFX_PRAGMA_WARNING_POP +#define FFX_PRAGMA_WARNING_DISABLE_DEPRECATIONS +#endif + +#define FFX_DEPRECATION(message) [[deprecated(message)]] + +#if defined(__cplusplus) +} +#endif // #if defined(__cplusplus) diff --git a/native/cpp/third_party/FidelityFX/2.3.0/api/include/ffx_api_types.h b/native/cpp/third_party/FidelityFX/2.3.0/api/include/ffx_api_types.h new file mode 100644 index 00000000..07c0da20 --- /dev/null +++ b/native/cpp/third_party/FidelityFX/2.3.0/api/include/ffx_api_types.h @@ -0,0 +1,257 @@ +// This file is part of the FidelityFX SDK. +// +// Copyright (C) 2026 Advanced Micro Devices, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#pragma once + +#include +#include + +/// An enumeration of surface formats. Needs to match enum FfxApiSurfaceFormat +enum FfxApiSurfaceFormat +{ + FFX_API_SURFACE_FORMAT_UNKNOWN, ///< Unknown format + FFX_API_SURFACE_FORMAT_R32G32B32A32_TYPELESS, ///< 32 bit per channel, 4 channel typeless format + FFX_API_SURFACE_FORMAT_R32G32B32A32_UINT, ///< 32 bit per channel, 4 channel uint format + FFX_API_SURFACE_FORMAT_R32G32B32A32_FLOAT, ///< 32 bit per channel, 4 channel float format + FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT, ///< 16 bit per channel, 4 channel float format + FFX_API_SURFACE_FORMAT_R32G32B32_FLOAT, ///< 32 bit per channel, 3 channel float format + FFX_API_SURFACE_FORMAT_R32G32_FLOAT, ///< 32 bit per channel, 2 channel float format + FFX_API_SURFACE_FORMAT_R8_UINT, ///< 8 bit per channel, 1 channel float format + FFX_API_SURFACE_FORMAT_R32_UINT, ///< 32 bit per channel, 1 channel float format + FFX_API_SURFACE_FORMAT_R8G8B8A8_TYPELESS, ///< 8 bit per channel, 4 channel typeless format + FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM, ///< 8 bit per channel, 4 channel unsigned normalized format + FFX_API_SURFACE_FORMAT_R8G8B8A8_SNORM, ///< 8 bit per channel, 4 channel signed normalized format + FFX_API_SURFACE_FORMAT_R8G8B8A8_SRGB, ///< 8 bit per channel, 4 channel srgb normalized + FFX_API_SURFACE_FORMAT_B8G8R8A8_TYPELESS, ///< 8 bit per channel, 4 channel typeless format + FFX_API_SURFACE_FORMAT_B8G8R8A8_UNORM, ///< 8 bit per channel, 4 channel unsigned normalized format + FFX_API_SURFACE_FORMAT_B8G8R8A8_SRGB, ///< 8 bit per channel, 4 channel srgb normalized + FFX_API_SURFACE_FORMAT_R11G11B10_FLOAT, ///< 32 bit 3 channel float format + FFX_API_SURFACE_FORMAT_R10G10B10A2_UNORM, ///< 10 bit per 3 channel, 2 bit for 1 channel normalized format + FFX_API_SURFACE_FORMAT_R16G16_FLOAT, ///< 16 bit per channel, 2 channel float format + FFX_API_SURFACE_FORMAT_R16G16_UINT, ///< 16 bit per channel, 2 channel unsigned int format + FFX_API_SURFACE_FORMAT_R16G16_SINT, ///< 16 bit per channel, 2 channel signed int format + FFX_API_SURFACE_FORMAT_R16_FLOAT, ///< 16 bit per channel, 1 channel float format + FFX_API_SURFACE_FORMAT_R16_UINT, ///< 16 bit per channel, 1 channel unsigned int format + FFX_API_SURFACE_FORMAT_R16_UNORM, ///< 16 bit per channel, 1 channel unsigned normalized format + FFX_API_SURFACE_FORMAT_R16_SNORM, ///< 16 bit per channel, 1 channel signed normalized format + FFX_API_SURFACE_FORMAT_R8_UNORM, ///< 8 bit per channel, 1 channel unsigned normalized format + FFX_API_SURFACE_FORMAT_R8G8_UNORM, ///< 8 bit per channel, 2 channel unsigned normalized format + FFX_API_SURFACE_FORMAT_R8G8_UINT, ///< 8 bit per channel, 2 channel unsigned integer format + FFX_API_SURFACE_FORMAT_R32_FLOAT, ///< 32 bit per channel, 1 channel float format + FFX_API_SURFACE_FORMAT_R9G9B9E5_SHAREDEXP, ///< 9 bit per channel, 5 bit exponent format + + FFX_API_SURFACE_FORMAT_R16G16B16A16_TYPELESS, ///< 16 bit per channel, 4 channel typeless format + FFX_API_SURFACE_FORMAT_R32G32_TYPELESS, ///< 32 bit per channel, 2 channel typeless format + FFX_API_SURFACE_FORMAT_R10G10B10A2_TYPELESS, ///< 10 bit per 3 channel, 2 bit for 1 channel typeless format + FFX_API_SURFACE_FORMAT_R16G16_TYPELESS, ///< 16 bit per channel, 2 channel typless format + FFX_API_SURFACE_FORMAT_R16_TYPELESS, ///< 16 bit per channel, 1 channel typeless format + FFX_API_SURFACE_FORMAT_R8_TYPELESS, ///< 8 bit per channel, 1 channel typeless format + FFX_API_SURFACE_FORMAT_R8G8_TYPELESS, ///< 8 bit per channel, 2 channel typeless format + FFX_API_SURFACE_FORMAT_R32_TYPELESS, ///< 32 bit per channel, 1 channel typeless format + FFX_API_SURFACE_FORMAT_R32G32_UINT, ///< 32 bit per channel, 2 channel uint format + + FFX_API_SURFACE_FORMAT_R8_SNORM, ///< 8 bit per channel, 1 channel signed normalized format +}; + +/// An enumeration of resource usage. +enum FfxApiResourceUsage +{ + FFX_API_RESOURCE_USAGE_READ_ONLY = 0, ///< No usage flags indicate a resource is read only. + FFX_API_RESOURCE_USAGE_RENDERTARGET = (1<<0), ///< Indicates a resource will be used as render target. + FFX_API_RESOURCE_USAGE_UAV = (1<<1), ///< Indicates a resource will be used as UAV. + FFX_API_RESOURCE_USAGE_DEPTHTARGET = (1<<2), ///< Indicates a resource will be used as depth target. + FFX_API_RESOURCE_USAGE_INDIRECT = (1<<3), ///< Indicates a resource will be used as indirect argument buffer + FFX_API_RESOURCE_USAGE_ARRAYVIEW = (1<<4), ///< Indicates a resource that will generate array views. Works on 2D and cubemap textures + FFX_API_RESOURCE_USAGE_STENCILTARGET = (1<<5), ///< Indicates a resource will be used as stencil target. + + FFX_API_RESOURCE_USAGE_DCC_RENDERTARGET = (1<<15), ///< Indicates a resource that should specify optimal render target memory access flags (for console use) +}; + + +/// An enumeration of resource states. +enum FfxApiResourceState +{ + FFX_API_RESOURCE_STATE_COMMON = (1 << 0), + FFX_API_RESOURCE_STATE_UNORDERED_ACCESS = (1 << 1), ///< Indicates a resource is in the state to be used as UAV. + FFX_API_RESOURCE_STATE_COMPUTE_READ = (1 << 2), ///< Indicates a resource is in the state to be read by compute shaders. + FFX_API_RESOURCE_STATE_PIXEL_READ = (1 << 3), ///< Indicates a resource is in the state to be read by pixel shaders. + FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ = (FFX_API_RESOURCE_STATE_PIXEL_READ | FFX_API_RESOURCE_STATE_COMPUTE_READ), ///< Indicates a resource is in the state to be read by pixel or compute shaders. + FFX_API_RESOURCE_STATE_COPY_SRC = (1 << 4), ///< Indicates a resource is in the state to be used as source in a copy command. + FFX_API_RESOURCE_STATE_COPY_DEST = (1 << 5), ///< Indicates a resource is in the state to be used as destination in a copy command. + FFX_API_RESOURCE_STATE_GENERIC_READ = (FFX_API_RESOURCE_STATE_COPY_SRC | FFX_API_RESOURCE_STATE_COMPUTE_READ), ///< Indicates a resource is in generic (slow) read state. + FFX_API_RESOURCE_STATE_INDIRECT_ARGUMENT = (1 << 6), ///< Indicates a resource is in the state to be used as an indirect command argument + FFX_API_RESOURCE_STATE_PRESENT = (1 << 7), ///< Indicates a resource is in the state to be used to present to the swap chain + FFX_API_RESOURCE_STATE_RENDER_TARGET = (1 << 8), ///< Indicates a resource is in the state to be used as render target + FFX_API_RESOURCE_STATE_DEPTH_ATTACHMENT = (1 << 9), ///< Indicates a resource is in the state to be used as depth attachment +}; + +/// An enumeration of surface dimensions. +enum FfxApiResourceDimension +{ + FFX_API_RESOURCE_DIMENSION_TEXTURE_1D, ///< A resource with a single dimension. + FFX_API_RESOURCE_DIMENSION_TEXTURE_2D, ///< A resource with two dimensions. +}; + +/// An enumeration of resource flags. +enum FfxApiResourceFlags +{ + FFX_API_RESOURCE_FLAGS_NONE = 0, ///< No flags. + FFX_API_RESOURCE_FLAGS_ALIASABLE = (1 << 0), ///< A bit indicating a resource does not need to persist across frames. + FFX_API_RESOURCE_FLAGS_UNDEFINED = (1 << 1), ///< Special case flag used internally when importing resources that require additional setup +}; + +// An enumeration for different resource types +enum FfxApiResourceType +{ + FFX_API_RESOURCE_TYPE_BUFFER, ///< The resource is a buffer. + FFX_API_RESOURCE_TYPE_TEXTURE1D, ///< The resource is a 1-dimensional texture. + FFX_API_RESOURCE_TYPE_TEXTURE2D, ///< The resource is a 2-dimensional texture. + FFX_API_RESOURCE_TYPE_TEXTURE_CUBE, ///< The resource is a cube map. + FFX_API_RESOURCE_TYPE_TEXTURE3D, ///< The resource is a 3-dimensional texture. +}; + +enum FfxApiBackbufferTransferFunction +{ + FFX_API_BACKBUFFER_TRANSFER_FUNCTION_SRGB, + FFX_API_BACKBUFFER_TRANSFER_FUNCTION_PQ, + FFX_API_BACKBUFFER_TRANSFER_FUNCTION_SCRGB +}; + +/// A structure encapsulating a 2-dimensional point, using 32bit unsigned integers. +struct FfxApiDimensions2D +{ + uint32_t width; ///< The width of a 2-dimensional range. + uint32_t height; ///< The height of a 2-dimensional range. +}; + +/// A structure encapsulating a 2-dimensional set of floating point coordinates. +struct FfxApiFloatCoords2D +{ + float x; ///< The x coordinate of a 2-dimensional point. + float y; ///< The y coordinate of a 2-dimensional point. +}; + +/// A structure encapsulating a 3-dimensional set of floating point coordinates. +struct FfxApiFloatCoords3D +{ + float x; ///< The x coordinate of a 3-dimensional point. + float y; ///< The y coordinate of a 3-dimensional point. + float z; ///< The z coordinate of a 3-dimensional point. +}; + +/// A structure encapsulating a 2-dimensional rect. +struct FfxApiRect2D +{ + int32_t left; + int32_t top; + int32_t width; + int32_t height; +}; + +/// A structure describing a resource. +/// +/// @ingroup SDKTypes +struct FfxApiResourceDescription +{ + uint32_t type; ///< The type of the resource. + uint32_t format; ///< The surface format. + union { + uint32_t width; ///< The width of the texture resource. + uint32_t size; ///< The size of the buffer resource. + }; + + union { + uint32_t height; ///< The height of the texture resource. + uint32_t stride; ///< The stride of the buffer resource. + }; + + union { + uint32_t depth; ///< The depth of the texture resource. + uint32_t alignment; ///< The alignment of the buffer resource. + }; + + uint32_t mipCount; ///< Number of mips (or 0 for full mipchain). + uint32_t flags; ///< A set of resource flags. + uint32_t usage; ///< Resource usage flags. +}; + +#define FFX_RESOURCE_NAME_SIZE 64 + +struct FfxApiResource +{ + void* resource; + struct FfxApiResourceDescription description; + uint32_t state; +}; + +//struct definition matches FfxApiEffectMemoryUsage +typedef struct FfxApiEffectMemoryUsage +{ + uint64_t totalUsageInBytes; + uint64_t aliasableUsageInBytes; +} FfxApiEffectMemoryUsage; + +/// A structure describing a constant buffer allocation. +/// +/// @ingroup SDKTypes +typedef struct FfxApiConstantBufferAllocation +{ + struct FfxApiResource resource; ///< The resource representing the constant buffer resource. + uint64_t handle; ///< The binding handle for the constant buffer + +} FfxApiConstantBufferAllocation; + +/// A function definition for a constant buffer allocation callback +/// Used to provide a constant buffer allocator to the calling backend +/// +/// @param [in] data The constant buffer data. +/// @param [in] dataSize The size of the constant buffer data. +/// +/// @ingroup SDKTypes +typedef FfxApiConstantBufferAllocation(*FfxApiConstantBufferAllocator)(void* data, const uint64_t dataSize); + +/// A 4-component floating point vector +typedef struct FfxApiFloat4 +{ + float x; + float y; + float z; + float w; +} FfxApiFloat4; + +/// A 4x4 matrix in row-major layout with row-vector convention. +/// This is the canonical format expected by FFX APIs. +/// +/// Memory layout: rows[row_index][column_index] +/// Mathematical convention: v * M (row vector multiplied by matrix) +typedef struct FfxApiMatrix4x4 +{ + FfxApiFloat4 rows[4]; ///< Four rows of the matrix. Access as rows[row].x, rows[row].y etc. +} FfxApiMatrix4x4; + +/// An inclusive floating-point interval [min, max]. +/// Valid instances must satisfy min <= max. +typedef struct FfxApiFloatBounds +{ + float min; ///< Lower bound of the interval. + float max; ///< Upper bound of the interval. +} FfxApiFloatBounds; \ No newline at end of file diff --git a/native/cpp/third_party/FidelityFX/2.3.0/upscalers/include/ffx_upscale.h b/native/cpp/third_party/FidelityFX/2.3.0/upscalers/include/ffx_upscale.h new file mode 100644 index 00000000..021ac23c --- /dev/null +++ b/native/cpp/third_party/FidelityFX/2.3.0/upscalers/include/ffx_upscale.h @@ -0,0 +1,230 @@ +// This file is part of the FidelityFX SDK. +// +// Copyright (C) 2026 Advanced Micro Devices, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#pragma once +#include "../../api/include/ffx_api.h" +#include "../../api/include/ffx_api_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FFX_UPSCALER_VERSION_MAJOR 4 +#define FFX_UPSCALER_VERSION_MINOR 1 +#define FFX_UPSCALER_VERSION_PATCH 1 + +#define FFX_UPSCALER_MAKE_VERSION(major, minor, patch) (((major) << 22) | ((minor) << 12) | (patch)) +#define FFX_UPSCALER_VERSION FFX_UPSCALER_MAKE_VERSION(FFX_UPSCALER_VERSION_MAJOR, FFX_UPSCALER_VERSION_MINOR, FFX_UPSCALER_VERSION_PATCH) + +enum FfxApiUpscaleQualityMode +{ + FFX_UPSCALE_QUALITY_MODE_NATIVEAA = 0, ///< Perform upscaling with a per-dimension upscaling ratio of 1.0x. + FFX_UPSCALE_QUALITY_MODE_QUALITY = 1, ///< Perform upscaling with a per-dimension upscaling ratio of 1.5x. + FFX_UPSCALE_QUALITY_MODE_BALANCED = 2, ///< Perform upscaling with a per-dimension upscaling ratio of 1.7x. + FFX_UPSCALE_QUALITY_MODE_PERFORMANCE = 3, ///< Perform upscaling with a per-dimension upscaling ratio of 2.0x. + FFX_UPSCALE_QUALITY_MODE_ULTRA_PERFORMANCE = 4 ///< Perform upscaling with a per-dimension upscaling ratio of 3.0x. +}; + +enum FfxApiCreateContextUpscaleFlags +{ + FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE = (1 << 0), ///< A bit indicating if the input color data provided is using a high-dynamic range. + FFX_UPSCALE_ENABLE_DISPLAY_RESOLUTION_MOTION_VECTORS = (1 << 1), ///< A bit indicating if the motion vectors are rendered at display resolution. + FFX_UPSCALE_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION = (1 << 2), ///< A bit indicating that the motion vectors have the jittering pattern applied to them. + FFX_UPSCALE_ENABLE_DEPTH_INVERTED = (1 << 3), ///< A bit indicating that the input depth buffer data provided is inverted [1..0]. + FFX_UPSCALE_ENABLE_DEPTH_INFINITE = (1 << 4), ///< A bit indicating that the input depth buffer data provided is using an infinite far plane. + FFX_UPSCALE_ENABLE_AUTO_EXPOSURE = (1 << 5), ///< A bit indicating if automatic exposure should be applied to input color data. + FFX_UPSCALE_ENABLE_DYNAMIC_RESOLUTION = (1 << 6), ///< A bit indicating that the application uses dynamic resolution scaling. + FFX_UPSCALE_ENABLE_DEBUG_CHECKING = (1 << 7), ///< A bit indicating that the runtime should check some API values and report issues. + FFX_UPSCALE_ENABLE_NON_LINEAR_COLORSPACE = (1 << 8), ///< A bit indicating that the color resource contains perceptual (gamma corrected) colors + FFX_UPSCALE_ENABLE_DEBUG_VISUALIZATION = (1 << 9), ///< A bit indicating if debug visualization is allowed. (memory consumption could increase) +}; + +enum FfxApiDispatchFsrUpscaleFlags +{ + FFX_UPSCALE_FLAG_DRAW_DEBUG_VIEW = (1 << 0), ///< A bit indicating that the output resource will contain debug views with relevant information. + FFX_UPSCALE_FLAG_NON_LINEAR_COLOR_SRGB = (1 << 1), ///< A bit indicating that the input color resource contains perceptual sRGB colors + FFX_UPSCALE_FLAG_NON_LINEAR_COLOR_PQ = (1 << 2), ///< A bit indicating that the input color resource contains perceptual PQ colors +}; + +enum FfxApiDispatchUpscaleAutoreactiveFlags +{ + FFX_UPSCALE_AUTOREACTIVEFLAGS_APPLY_TONEMAP = (1 << 0), ///< Apply tonemapping function prior to calculating reactivity. + FFX_UPSCALE_AUTOREACTIVEFLAGS_APPLY_INVERSETONEMAP = (1 << 1), ///< Apply inverse tonemapping function prior to calculating reactivity. + FFX_UPSCALE_AUTOREACTIVEFLAGS_APPLY_THRESHOLD = (1 << 2), ///< Compare to a threshold value and only record reactivity above the threshold. + FFX_UPSCALE_AUTOREACTIVEFLAGS_USE_COMPONENTS_MAX = (1 << 3), ///< When enabled use the max. component value, otherwise use the length to calculate reactivity. +}; + +#define FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x00) +struct ffxCreateContextDescUpscale +{ + ffxCreateContextDescHeader header; ///< Header descriptor, use type FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE. + uint32_t flags; ///< Zero or a combination of values from FfxApiCreateContextUpscaleFlags. + struct FfxApiDimensions2D maxRenderSize; ///< The maximum size that rendering will be performed at. + struct FfxApiDimensions2D maxUpscaleSize; ///< The size of the presentation resolution targeted by the upscaling process. + ffxApiMessage fpMessage; ///< A pointer to a function that can receive messages from the runtime. May be null. +}; + +#define FFX_API_DISPATCH_DESC_TYPE_UPSCALE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x01) +struct ffxDispatchDescUpscale +{ + ffxDispatchDescHeader header; ///< Header descriptor, use type FFX_API_DISPATCH_DESC_TYPE_UPSCALE. + void* commandList; ///< Command list to record upscaling rendering commands into. + struct FfxApiResource color; ///< Color buffer for the current frame (at render resolution). + struct FfxApiResource depth; ///< 32bit depth values for the current frame (at render resolution). + struct FfxApiResource motionVectors; ///< 2-dimensional motion vectors (at render resolution if FFX_FSR_ENABLE_DISPLAY_RESOLUTION_MOTION_VECTORS is not set). + struct FfxApiResource exposure; ///< Optional resource containing a 1x1 exposure value. + struct FfxApiResource reactive; ///< Optional resource containing alpha value of reactive objects in the scene. + struct FfxApiResource transparencyAndComposition; ///< Optional resource containing alpha value of special objects in the scene. + struct FfxApiResource output; ///< Output color buffer for the current frame (at presentation resolution). + struct FfxApiFloatCoords2D jitterOffset; ///< The subpixel jitter offset applied to the camera. + struct FfxApiFloatCoords2D motionVectorScale; ///< The scale factor to apply to motion vectors. + struct FfxApiDimensions2D renderSize; ///< The resolution that was used for rendering the input resources. + struct FfxApiDimensions2D upscaleSize; ///< The resolution that the upscaler will upscale to (optional, assumed maxUpscaleSize otherwise). + bool enableSharpening; ///< Enable an additional sharpening pass. + float sharpness; ///< The sharpness value between 0 and 1, where 0 is no additional sharpness and 1 is maximum additional sharpness. + float frameTimeDelta; ///< The time elapsed since the last frame (expressed in milliseconds). + float preExposure; ///< The pre exposure value (must be > 0.0f) + bool reset; ///< A boolean value which when set to true, indicates the camera has moved discontinuously. + float cameraNear; ///< The distance to the near plane of the camera. + float cameraFar; ///< The distance to the far plane of the camera. + float cameraFovAngleVertical; ///< The camera angle field of view in the vertical direction (expressed in radians). + float viewSpaceToMetersFactor; ///< The scale factor to convert view space units to meters + uint32_t flags; ///< Zero or a combination of values from FfxApiDispatchFsrUpscaleFlags. +}; + +#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GETUPSCALERATIOFROMQUALITYMODE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x02) +struct ffxQueryDescUpscaleGetUpscaleRatioFromQualityMode +{ + ffxQueryDescHeader header; ///< Header descriptor, use type FFX_API_QUERY_DESC_TYPE_UPSCALE_GETUPSCALERATIOFROMQUALITYMODE. + uint32_t qualityMode; ///< The desired quality mode for FSR upscaling. + float* pOutUpscaleRatio; ///< A pointer to a float which will hold the upscaling the per-dimension upscaling ratio. +}; + +#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GETRENDERRESOLUTIONFROMQUALITYMODE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x03) +struct ffxQueryDescUpscaleGetRenderResolutionFromQualityMode +{ + ffxQueryDescHeader header; ///< Header descriptor, use type FFX_API_QUERY_DESC_TYPE_UPSCALE_GETRENDERRESOLUTIONFROMQUALITYMODE. + uint32_t displayWidth; ///< The target display resolution width. + uint32_t displayHeight; ///< The target display resolution height. + uint32_t qualityMode; ///< The desired quality mode for FSR upscaling. + uint32_t* pOutRenderWidth; ///< A pointer to a uint32_t which will hold the calculated render resolution width. + uint32_t* pOutRenderHeight; ///< A pointer to a uint32_t which will hold the calculated render resolution height. +}; + +#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GETJITTERPHASECOUNT FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x04) +struct ffxQueryDescUpscaleGetJitterPhaseCount +{ + ffxQueryDescHeader header; ///< Header descriptor, use type FFX_API_QUERY_DESC_TYPE_UPSCALE_GETJITTERPHASECOUNT. + uint32_t renderWidth; ///< The render resolution width. + uint32_t displayWidth; ///< The output resolution width. + int32_t* pOutPhaseCount; ///< A pointer to a int32_t which will hold the jitter phase count for the scaling factor between renderWidth and displayWidth. +}; + +#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GETJITTEROFFSET FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x05) +struct ffxQueryDescUpscaleGetJitterOffset +{ + ffxQueryDescHeader header; ///< Header descriptor, use type FFX_API_QUERY_DESC_TYPE_UPSCALE_GETJITTEROFFSET. + int32_t index; ///< The index within the jitter sequence. + int32_t phaseCount; ///< The length of jitter phase. See ffxQueryDescFsrGetJitterPhaseCount. + float* pOutX; ///< A pointer to a float which will contain the subpixel jitter offset for the x dimension. + float* pOutY; ///< A pointer to a float which will contain the subpixel jitter offset for the y dimension. +}; + +#define FFX_API_DISPATCH_DESC_TYPE_UPSCALE_GENERATEREACTIVEMASK FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x06) +struct ffxDispatchDescUpscaleGenerateReactiveMask +{ + ffxDispatchDescHeader header; ///< Header descriptor, use type FFX_API_DISPATCH_DESC_TYPE_UPSCALE_GENERATEREACTIVEMASK. + void* commandList; ///< The FfxCommandList to record FSRUPSCALE rendering commands into. + struct FfxApiResource colorOpaqueOnly; ///< A FfxApiResource containing the opaque only color buffer for the current frame (at render resolution). + struct FfxApiResource colorPreUpscale; ///< A FfxApiResource containing the opaque+translucent color buffer for the current frame (at render resolution). + struct FfxApiResource outReactive; ///< A FfxApiResource containing the surface to generate the reactive mask into. + struct FfxApiDimensions2D renderSize; ///< The resolution that was used for rendering the input resources. + float scale; ///< A value to scale the output + float cutoffThreshold; ///< A threshold value to generate a binary reactive mask + float binaryValue; ///< A value to set for the binary reactive mask + uint32_t flags; ///< Flags to determine how to generate the reactive mask +}; + +#define FFX_API_CONFIGURE_DESC_TYPE_UPSCALE_KEYVALUE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x07) +struct ffxConfigureDescUpscaleKeyValue +{ + ffxConfigureDescHeader header; ///< Header descriptor, use type FFX_API_CONFIGURE_DESC_TYPE_UPSCALE_KEYVALUE. + uint64_t key; ///< Configuration key, member of the FfxApiConfigureUpscaleKey enumeration. + uint64_t u64; ///< Integer value or enum value to set. + void* ptr; ///< Pointer to set or pointer to value to set. +}; + +enum FfxApiConfigureUpscaleKey +{ + FFX_API_CONFIGURE_UPSCALE_KEY_FVELOCITYFACTOR = 0, ///< Override constant buffer fVelocityFactor. The float value is casted from void * ptr. Value of 0.0f can improve temporal stability of bright pixels. Default value is 1.0f. Value is clamped to [0.0f, 1.0f]. + FFX_API_CONFIGURE_UPSCALE_KEY_FREACTIVENESSSCALE = 1, ///< Override constant buffer fReactivenessScale. The float value is casted from void * ptr. Meant for development purpose to test if writing a larger value to reactive mask, reduces ghosting. Default value is 1.0f. Value is clamped to [0.0f, +infinity]. + FFX_API_CONFIGURE_UPSCALE_KEY_FSHADINGCHANGESCALE = 2, ///< Override fShadingChangeScale. Increasing this scales fsr3.1 computed shading change value at read to have higher reactiveness. Default value is 1.0f. Value is clamped to [0.0f, +infinity]. + FFX_API_CONFIGURE_UPSCALE_KEY_FACCUMULATIONADDEDPERFRAME = 3, ///< Override constant buffer fAccumulationAddedPerFrame. Corresponds to amount of accumulation added per frame at pixel coordinate where disocclusion occured or when reactive mask value is > 0.0f. Decreasing this and drawing the ghosting object (IE no mv) to reactive mask with value close to 1.0f can decrease temporal ghosting. Decreasing this value could result in more thin feature pixels flickering. Default value is 0.333. Value is clamped to [0.0f, 1.0f]. + FFX_API_CONFIGURE_UPSCALE_KEY_FMINDISOCCLUSIONACCUMULATION = 4, ///< Override constant buffer fMinDisocclusionAccumulation. Increasing this value may reduce white pixel temporal flickering around swaying thin objects that are disoccluding one another often. Too high value may increase ghosting. A sufficiently negative value means for pixel coordinate at frame N that is disoccluded, add fAccumulationAddedPerFrame starting at frame N+2. Default value is -0.333. Value is clamped to [-1.0f, 1.0f]. +}; + +#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GPU_MEMORY_USAGE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x08) +struct ffxQueryDescUpscaleGetGPUMemoryUsage +{ + ffxQueryDescHeader header; ///< Header descriptor, use type FFX_API_QUERY_DESC_TYPE_UPSCALE_GPU_MEMORY_USAGE. + struct FfxApiEffectMemoryUsage* gpuMemoryUsageUpscaler; ///< Output values by Query() call. +}; + +#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GPU_MEMORY_USAGE_V2 FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x09) +struct ffxQueryDescUpscaleGetGPUMemoryUsageV2 +{ + ffxQueryDescHeader header; ///< Header descriptor, use type FFX_API_QUERY_DESC_TYPE_UPSCALE_GPU_MEMORY_USAGE_V2. + void* device; ///< For DX12: pointer to ID3D12Device. For VK, pointer to VkDeviceContext. App needs to fill out before Query() call. + struct FfxApiDimensions2D maxRenderSize; ///< App needs to fill out before Query() call. + struct FfxApiDimensions2D maxUpscaleSize; ///< App needs to fill out before Query() call. + uint32_t flags; ///< Zero or a combination of values from FfxApiCreateContextUpscaleFlags. App needs to fill out before Query() call. + struct FfxApiEffectMemoryUsage* gpuMemoryUsageUpscaler; ///< Output values by Query() call. +}; + +enum FfxApiQueryResourceIdentifiers +{ + FFX_API_QUERY_RESOURCE_INPUT_COLOR = (1 << 0), ///< Color buffer for the current frame (at render resolution). + FFX_API_QUERY_RESOURCE_INPUT_DEPTH = (1 << 1), ///< 32bit depth values for the current frame (at render resolution). + FFX_API_QUERY_RESOURCE_INPUT_MV = (1 << 2), ///< 2-dimensional motion vectors (at render resolution if FFX_FSR_ENABLE_DISPLAY_RESOLUTION_MOTION_VECTORS is not set). + FFX_API_QUERY_RESOURCE_INPUT_EXPOSURE = (1 << 3), ///< A 1x1 texture containing exposure value or the FFX_UPSCALE_ENABLE_AUTO_EXPOSURE set at context creation. + FFX_API_QUERY_RESOURCE_INPUT_REACTIVEMASK = (1 << 4), ///< An R8 UNORM texture at render resolution that controls the influence of pixel history on upscaling result. + FFX_API_QUERY_RESOURCE_INPUT_TRANSPARENCYCOMPOSITION = (1 << 5), ///< An R8 UNORM texture at render resolution that controls the blending of color in the upscaling result. +}; + +#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GET_RESOURCE_REQUIREMENTS FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x0a) +struct ffxQueryDescUpscaleGetResourceRequirements +{ + ffxQueryDescHeader header; ///< Header descriptor, use type FFX_API_QUERY_DESC_TYPE_UPSCALE_GET_RESOURCE_REQUIREMENTS. + uint64_t required_resources; ///< resources 64b bitfield, that given current context state, are required for effect correctness. + uint64_t optional_resources; ///< resources 64b bitfield, that given current context state, will be consumed if provided, but are optional. +}; + +#define FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE_VERSION FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x0b) +struct ffxCreateContextDescUpscaleVersion +{ + ffxCreateContextDescHeader header; ///< Header descriptor, use type FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE_VERSION. + uint32_t version; ///< The version of the API the application was built against. This must be set to FFX_UPSCALER_VERSION. +}; + +#ifdef __cplusplus +} +#endif From f9c76a912a1019817d46125179ffbf27839b089d Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Thu, 30 Jul 2026 01:04:50 +0800 Subject: [PATCH 18/19] removed outdated document --- native/cpp/docs/ffx_api_d3d12_prototype.md | 98 ---------------------- 1 file changed, 98 deletions(-) delete mode 100644 native/cpp/docs/ffx_api_d3d12_prototype.md diff --git a/native/cpp/docs/ffx_api_d3d12_prototype.md b/native/cpp/docs/ffx_api_d3d12_prototype.md deleted file mode 100644 index ac7420ee..00000000 --- a/native/cpp/docs/ffx_api_d3d12_prototype.md +++ /dev/null @@ -1,98 +0,0 @@ -# SRAPI Direct3D 12 / AMD FFX API prototype - -This branch adds the native API foundation needed to run AMD's current -signed-DLL upscaler through Direct3D 12. - -## Why this uses FFX API - -FSR 4.1 is exposed by AMD FSR SDK 2.x through the FFX API and the signed -`amd_fidelityfx_upscaler_dx12.dll`. The older FidelityFX SDK 1.x backend -compiled shaders directly into the application. Reviving that deleted DX12 -backend would provide an FSR 2/3 implementation, but it would not be the -correct integration path for the current FSR 4.1 provider. - -The adapter in `SRNativeFSR/src/ffx_api_upscale.cpp` therefore: - -1. loads AMD's signed upscaler DLL dynamically; -2. resolves `ffxCreateContext`, `ffxDestroyContext`, `ffxQuery`, and - `ffxDispatch`; -3. translates SRAPI context and dispatch descriptions to the FFX API ABI; and -4. exposes the adapter as provider `SR_MODULES_FFX_API_UPSCALE_ID`. - -The signed AMD DLL is not copied into this repository. Obtain it from an -official AMD FSR SDK release and pass its absolute path through the -`ffxApiDllPath` context string parameter. If the parameter is omitted, the -provider looks for `amd_fidelityfx_upscaler_dx12.dll` in the process' secure -DLL search directories. - -For a D3D12-only development build on Windows, configure CMake with -`-DSR_FSR_FFX_API_ONLY=ON`. This builds the signed FFX API provider without -the legacy Vulkan providers or their Vulkan SDK/build-time dependencies. The -option is disabled by default, so normal release builds retain the complete -FSR 2/3 provider set. - -```powershell -cmake -S . -B buildWindowsD3D12Dev ` - -DSR_FSR=ON ` - -DSR_FSR_FFX_API_ONLY=ON ` - -DSR_XESS=OFF ` - -DSR_NGX=OFF ` - -DSR_STREAMLINE=OFF ` - -DENABLE_OPT=OFF -cmake --build buildWindowsD3D12Dev --config Debug --target SR_FSR_LIB -``` - -## D3D12 SRAPI handles - -The cross-platform SRAPI ABI does not include `d3d12.h`. It carries: - -- `SRD3D12DeviceInfo.device` as an opaque `ID3D12Device*`; -- `SRCommandBufferD3D12.commandList` as an opaque - `ID3D12GraphicsCommandList*`; and -- `SRTextureResource.handle` as an opaque `ID3D12Resource*`. - -`SRTextureResource.state` describes the current resource state. It uses the -same bit values as the FFX API resource-state enum. - -The Java/JNI layer mirrors those values with `long` native addresses. Raw -D3D12 resources can be created with the `SRTextureResource(long, description, -states)` constructor. - -## Renderer interop - -`D3D12InteropAlgorithm` implements the renderer-facing half as a sibling to -`VulkanInteropAlgorithm`. Its JNI implementation lives in the reusable -`SRNativeD3D12Interop` module rather than the FSR4 provider module. The -initial implementation deliberately uses a -single serial resource set: - -1. query OpenGL's `GL_DEVICE_LUID_EXT` and create D3D12 on the matching DXGI - adapter; -2. create five D3D12-owned shared committed textures for color, depth, motion - vectors, exposure, and output; -3. import the resource handles into OpenGL with - `GL_EXT_memory_object_win32`; -4. import a shared D3D12 timeline fence with `GL_EXT_semaphore_win32`; -5. preprocess the Minecraft inputs in OpenGL, signal ownership to D3D12, - dispatch FFX API, signal ownership back to OpenGL, and flip the output into - the normal renderer texture. - -`FfxFSR4D3D12` supplies the FSR-specific context and dispatch descriptions. -The algorithm is registered as `fsr4_d3d12` on Windows when the required -OpenGL extensions are available. The signed DLL remains an explicit external -resource and must be selected by the user. - -## Prototype boundary - -The resource import, fence round trip, and FFX command recording/execution have -been validated in standalone smoke tests on an AMD Radeon RX 7900 XT. The -remaining work is integration testing inside Minecraft, including validation -of motion-vector/depth conventions and rendered image quality. A future -high-performance mode can add multiple in-flight resource sets after the -serial path is proven in game. - -## Provider lifecycle - -All FFX API creation descriptors are stored in the provider's private context -for the full FFX context lifetime, as required by AMD's API contract. The AMD -DLL remains loaded until `srDestroyUpscaleContext` destroys the FFX context. From 0963313a5f79c2622ada11996ad25aa08ba79d2c Mon Sep 17 00:00:00 2001 From: xks <2777389616@qq.com> Date: Thu, 30 Jul 2026 11:22:47 +0800 Subject: [PATCH 19/19] fixed compilation error --- native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp b/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp index f6ed2b6a..4fb82d00 100644 --- a/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp +++ b/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp @@ -18,9 +18,16 @@ #include namespace { + struct SRFfxApiFunctions { + PfnFfxCreateContext createContext; + PfnFfxDestroyContext destroyContext; + PfnFfxQuery query; + PfnFfxDispatch dispatch; + }; + struct SRFfxApiPrivateData { HMODULE module = nullptr; - FfxApiFunctions functions = {}; + SRFfxApiFunctions functions = {}; ffxContext context = nullptr; ffxCreateContextDescUpscale createDesc = {}; ffxCreateBackendDX12Desc backendDesc = {}; @@ -50,7 +57,7 @@ namespace { } } - bool loadFunctions(HMODULE module, FfxApiFunctions *outFunctions) { + bool loadFunctions(HMODULE module, SRFfxApiFunctions *outFunctions) { outFunctions->createContext = reinterpret_cast( GetProcAddress(module, "ffxCreateContext")); outFunctions->destroyContext = reinterpret_cast( @@ -203,7 +210,8 @@ extern "C" { privateData->backendDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12; privateData->backendDesc.header.pNext = &privateData->versionDesc.header; - privateData->backendDesc.device = desc->renderDeviceInfo.d3d12.device; + privateData->backendDesc.device = static_cast( + desc->renderDeviceInfo.d3d12.device); privateData->versionDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE_VERSION; privateData->versionDesc.header.pNext = nullptr;