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/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..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 @@ -33,12 +33,14 @@ 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; 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; @@ -180,6 +182,40 @@ 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) + .isTrue(NativeLibManager::d3d12InteropAvailable) + ) + .extraResources( + 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() + ) + .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 +321,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..ce8decfa --- /dev/null +++ b/common/src/main/java/io/homo/superresolution/common/upscale/D3D12InteropAlgorithm.java @@ -0,0 +1,523 @@ +/* + * Super Resolution + * 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 + * 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.SuperResolution; +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; +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 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 { + 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 U createD3D12Upscaler( + InitializationDescription desc, + D3D12InteropContext interop, + InteropSize size); + + protected abstract void destroyD3D12Upscaler(U upscaler); + + protected abstract boolean dispatchD3D12Upscale( + U upscaler, + D3D12InteropContext interop, + long commandList, + DispatchResource dispatchResource); + + protected boolean isD3D12UpscalerReady(U upscaler) { + return true; + } + + @Override + public void initialize(InitializationDescription desc) { + if (!NativeLibManager.d3d12InteropAvailable()) { + throw new IllegalStateException( + "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 { + 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 { + destroyInteropResources(resources); + } catch (Throwable cleanupFailure) { + throwable.addSuppressed(cleanupFailure); + } + throw throwable; + } + } + + 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); + Generation generation = activeGeneration; + if (lifecycleState == LifecycleState.DESTROYED || + lifecycleState == LifecycleState.REBUILDING || + generation == null || + !isD3D12UpscalerReady(generation.upscaler())) { + return false; + } + + InteropResources resources = generation.resources(); + if (!resources.size.matches(dispatchResource)) { + lifecycleState = LifecycleState.RESIZE_PENDING; + if (!resizeMismatchLogged) { + SuperResolution.LOGGER.warn( + "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(), + 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(), + resources.inputColor, + dispatchResource.resources().depthTexture(), + resources.inputDepth, + dispatchResource.resources().motionVectorsTexture(), + resources.inputMotionVectors, + dispatchResource.resources().exposureTexture(), + resources.inputExposure, + SRWorkModeManager.getCurrentState() + .motionVectorPreprocessingFunction()); + + long openGlReadyValue = resources.context.nextFenceValue(); + resources.semaphore.signal( + openGlReadyValue, + resources.sharedTextureIds, + 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 + }); + + resources.context.beginFrame(openGlReadyValue); + boolean dispatched; + long d3d12DoneValue = resources.context.nextFenceValue(); + try { + dispatched = dispatchD3D12Upscale( + 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. + resources.context.executeFrame(d3d12DoneValue); + resources.semaphore.waitFor( + d3d12DoneValue, + resources.sharedTextureIds, + 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 + }); + } + + InteropResourcesConverter.flipY( + resources.outputColor, + resources.flippedOutput); + return dispatched; + } + + @Override + public void resize(int width, int height) { + 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; + } + + lifecycleState = LifecycleState.REBUILDING; + if (previous != null) { + drainGeneration(previous); + } + + Generation replacement; + try { + replacement = createGeneration(targetSize); + } catch (Throwable throwable) { + 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() { + 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 destroyGeneration( + Generation generation, + boolean drain) { + if (drain) { + drainGeneration(generation); + } + try { + destroyD3D12Upscaler(generation.upscaler()); + } finally { + destroyInteropResources(generation.resources()); + } + } + + 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(); + } + if (flippedOutput != null) { + flippedOutput.destroy(); + } + if (outputColor != null) { + outputColor.destroy(); + } + if (inputExposure != null) { + inputExposure.destroy(); + } + if (inputMotionVectors != null) { + inputMotionVectors.destroy(); + } + if (inputDepth != null) { + inputDepth.destroy(); + } + if (inputColor != null) { + inputColor.destroy(); + } + if (semaphore != null) { + semaphore.close(); + } + if (context != null) { + context.close(); + } + } + + @Override + public IFrameBuffer getOutputFrameBuffer() { + Generation generation = activeGeneration; + return generation == null + ? null + : generation.resources().outputFramebuffer; + } + + @Override + public int getOutputTextureId() { + Generation generation = activeGeneration; + return generation == null + ? 0 + : 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 new file mode 100644 index 00000000..02cee470 --- /dev/null +++ b/common/src/main/java/io/homo/superresolution/common/upscale/ffxfsr/FfxFSR4D3D12.java @@ -0,0 +1,226 @@ +/* + * Super Resolution + * 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 + * 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.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; + + @Override + protected SRUpscaleContext createD3D12Upscaler( + InitializationDescription desc, + D3D12InteropContext interop, + InteropSize size) { + Path providerLibrary = NativeLibManager.LIB_SUPER_RESOLUTION_FSR4 + .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(), + "srGetFfxFSR4UpscaleProviders", + "srGetFfxFSR4UpscaleProvidersCount"); + 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, + SRUpscaleContextCreateFlags.ENABLE_AUTO_EXPOSURE); + if (desc.isHdrInput()) { + flags.add(SRUpscaleContextCreateFlags.ENABLE_HDR); + } + if (desc.isMotionJittered()) { + flags.add( + SRUpscaleContextCreateFlags.ENABLE_MOTION_VECTORS_JITTERED); + } + + SRUpscaleContext context = new SRUpscaleContext(0); + try (SRCreateUpscaleContextDesc createDesc = + SRCreateUpscaleContextDesc.createD3D12( + new SRD3D12DeviceInfo(interop.getDevice()), + new Vector2i( + size.screenWidth(), + size.screenHeight()), + new Vector2i( + size.renderWidth(), + size.renderHeight()), + 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) { + throw new IllegalStateException( + "Could not create the FSR 4.1 context: " + + createCode); + } + SRReturnCode initCode = + SuperResolutionNativeAPI.srInitUpscaleContext(context); + if (initCode != SRReturnCode.OK) { + context.destroy(); + throw new IllegalStateException( + "Could not initialize the FSR 4.1 context: " + + initCode); + } + } + return context; + } + } + + @Override + 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); + } + } + } + + @Override + 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( + interop.inputColor(), + SRResourceStates.COMPUTE_READ)); + desc.setDepth(resource( + interop.inputDepth(), + SRResourceStates.COMPUTE_READ)); + desc.setMotionVectors(resource( + 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( + interop.outputColor(), + SRResourceStates.COMMON)); + + 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/NativeLibManager.java b/common/src/main/java/io/homo/superresolution/core/NativeLibManager.java index fd40562b..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,7 +47,9 @@ 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; public static NativeLib LIB_SUPER_RESOLUTION_NGX = null; public static NativeLib LIB_SUPER_RESOLUTION_STREAMLINE = null; @@ -66,7 +68,10 @@ 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); LIB_SUPER_RESOLUTION_NGX = new NativeLib("SuperResolutionNGX", false, false); LIB_SUPER_RESOLUTION_STREAMLINE = new NativeLib("SuperResolutionStreamline", presentation, presentation); @@ -77,7 +82,9 @@ 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); libs.add(LIB_SUPER_RESOLUTION_NGX); libs.add(LIB_STREAMLINE_COMMON); @@ -109,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/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/core/graphics/d3d12/D3D12InteropContext.java b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropContext.java new file mode 100644 index 00000000..9924e9a8 --- /dev/null +++ b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropContext.java @@ -0,0 +1,268 @@ +/* + * Super Resolution + * 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 + * 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..492a3d5b --- /dev/null +++ b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropNative.java @@ -0,0 +1,53 @@ +/* + * Super Resolution + * 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 + * 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..e0646487 --- /dev/null +++ b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/D3D12InteropSemaphore.java @@ -0,0 +1,98 @@ +/* + * Super Resolution + * 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 + * 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..f71bd4b6 --- /dev/null +++ b/common/src/main/java/io/homo/superresolution/core/graphics/d3d12/GlD3D12ImportableTexture2D.java @@ -0,0 +1,98 @@ +/* + * Super Resolution + * 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 + * 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/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..a86d8d21 --- /dev/null +++ b/common/src/main/java/io/homo/superresolution/srapi/SRD3D12DeviceInfo.java @@ -0,0 +1,46 @@ +/* + * Super Resolution + * 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 + * 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/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,)" 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/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/CMakeLists.txt b/native/cpp/CMakeLists.txt index dc8db06c..a651ee7b 100644 --- a/native/cpp/CMakeLists.txt +++ b/native/cpp/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.15) 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) @@ -77,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() @@ -88,6 +93,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/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/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 new file mode 100644 index 00000000..ff381276 --- /dev/null +++ b/native/cpp/SRNativeD3D12Interop/src/d3d12_interop.cpp @@ -0,0 +1,195 @@ +#include + +#include "d3d12_interop_internal.h" + +#if defined(ON_WIN64) + +#include + +namespace { + +using sr::d3d12::D3D12InteropContext; +using sr::d3d12::SharedTexture; + +// 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)); +} + +SharedTexture *getResource(D3D12InteropContext *context, jint 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 context on the adapter used by OpenGL. + * + * 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) { + if (adapterLuid == 0 || renderWidth <= 0 || renderHeight <= 0 || + outputWidth <= 0 || outputHeight <= 0) { + sr::d3d12::clearError(); + sr::d3d12::setError( + "Invalid D3D12 interop context dimensions or adapter LUID."); + return 0; + } + + 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; +} + +/** + * 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( + JNIEnv *, jclass, jlong contextHandle) { + D3D12InteropContext *context = fromHandle(contextHandle); + if (!context) { + return; + } + sr::d3d12::waitForFence(context, context->lastSubmittedFenceValue); + delete context; +} + +/** + * 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 ? toHandle(context->device.get()) : 0; +} + +/** + * 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 ? toHandle(context->commandList.get()) : 0; +} + +/** + * 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 ? toHandle(texture->resource.get()) : 0; +} + +/** + * 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 ? toHandle(texture->sharedHandle.get()) : 0; +} + +/** + * 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( + JNIEnv *, jclass, jlong contextHandle, jint index) { + SharedTexture *texture = getResource(fromHandle(contextHandle), index); + return texture ? static_cast(texture->allocationSize) : 0; +} + +/** + * 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 ? toHandle(context->fenceSharedHandle.get()) : 0; +} + +/** + * 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) { + if (waitFenceValue <= 0) { + sr::d3d12::setError("Invalid D3D12 begin-frame arguments."); + return E_INVALIDARG; + } + return static_cast(sr::d3d12::beginFrame( + fromHandle(contextHandle), static_cast(waitFenceValue))); +} + +/** + * 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) { + if (signalFenceValue <= 0) { + sr::d3d12::setError("Invalid D3D12 execute-frame state or fence value."); + return E_INVALIDARG; + } + return static_cast(sr::d3d12::executeFrame( + fromHandle(contextHandle), static_cast(signalFenceValue))); +} + +/** + * Blocks until the latest submitted D3D12 frame completes. + */ +JNIEXPORT jint JNICALL +Java_io_homo_superresolution_core_graphics_d3d12_D3D12InteropNative_Nd3d12WaitIdle( + JNIEnv *, jclass, jlong contextHandle) { + return static_cast(sr::d3d12::waitIdle(fromHandle(contextHandle))); +} + +/** + * 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(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 diff --git a/native/cpp/SRNativeFSR4/CMakeLists.txt b/native/cpp/SRNativeFSR4/CMakeLists.txt new file mode 100644 index 00000000..5ac9bcce --- /dev/null +++ b/native/cpp/SRNativeFSR4/CMakeLists.txt @@ -0,0 +1,38 @@ +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>") + +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 +) + +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/SRNativeFSR4/include/sr/fsr4/ffx_api_upscale.h b/native/cpp/SRNativeFSR4/include/sr/fsr4/ffx_api_upscale.h new file mode 100644 index 00000000..98c299e3 --- /dev/null +++ b/native/cpp/SRNativeFSR4/include/sr/fsr4/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/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/SRNativeFSR4/src/ffx_api_upscale.cpp b/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp new file mode 100644 index 00000000..4fb82d00 --- /dev/null +++ b/native/cpp/SRNativeFSR4/src/ffx_api_upscale.cpp @@ -0,0 +1,380 @@ +#include "sr/fsr4/ffx_api_upscale.h" + +#if defined(ON_WIN64) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace { + struct SRFfxApiFunctions { + PfnFfxCreateContext createContext; + PfnFfxDestroyContext destroyContext; + PfnFfxQuery query; + PfnFfxDispatch dispatch; + }; + + struct SRFfxApiPrivateData { + HMODULE module = nullptr; + SRFfxApiFunctions 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, SRFfxApiFunctions *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 = static_cast( + 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) { + 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); + } + + 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 = {}; + 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; + } + 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) { + 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 = {}; + 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/SRNativeFSR4/src/sr_provider.cpp b/native/cpp/SRNativeFSR4/src/sr_provider.cpp new file mode 100644 index 00000000..19b61ddc --- /dev/null +++ b/native/cpp/SRNativeFSR4/src/sr_provider.cpp @@ -0,0 +1,30 @@ +#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 std::once_flag g_initializeOnce; + +static void ensureInitialized() { + std::call_once(g_initializeOnce, [] { + g_providers[0].providerId = SR_MODULES_FSR4_ID; + g_providers[0].callbacks = srGetFfxApiUpscaleCallbacks(); + }); +} + +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/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..a67fe55c 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_FSR4_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/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 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}"