Add frame generation through AMD's FSR SDK 2.4 as a setting to try with FSR 4, and never generate a frame without the HUD-less picture.

runtime/vk_fg4.cpp makes the frame generation swapchain and context with the libraries FSR 4 comes with
(amd_fidelityfx_loader_vk.dll and amd_fidelityfx_framegeneration_vk.dll beside the executable). vk_fg.cpp takes
that path only with fsr4_fg = 1 while FSR 4 is the upscaler; the default stays the FSR 3.1 library. The loader
picks the version for the card: the machine-learned FSR Frame Generation 4 on Radeon RX 9000 cards with Windows 11,
which is untested, and 3.1.6 elsewhere. On an RX 7900 XT (driver 26.10.1) it lists frame generation 3.1.6 and the
swapchains 4.0.0, which the driver brings and the loader chooses, and 3.1.7. The swapchain in place is wanted only
while its library is the one to use, so a change of the setting or of the upscaler, or a failure of this path,
takes it down as switching frame generation off does and the next present makes it with the other library.

The preparation gives this library the camera's position and axes, which FSR Frame Generation 4 requires, and a
scene scale of one metre a unit: with the 0.01 the FSR 3.1 path uses, version 3.1.6 drew grey translucent shapes
into the sky of its frames while the camera turned (160 captures of the screen during a camera sweep; clean with 1).

Both paths now generate a frame only when the picture without the HUD is there for it: generated from the
presented picture alone, the HUD would be warped with the scene behind it. In four configurations (21:9 with either
library, a 16:9 picture in a 21:9 window, a 1080 picture without HDR) every generated frame had it, and the log
counts the frames without. On screen, of the 6,404 pixels of the HUD's item button that stayed fixed through a
camera sweep with the FSR 3.1 library, 6,162 stayed fixed with this one.

Standing in Hyrule Field at 3440x1440 with FSR 4 Quality the game draws 43.0 frames a second with this path and
42.2 with the FSR 3.1 library, each shown twice over. One run changed the present mode, HDR, fullscreen, frame
generation, FSR 4, this setting and the upscaler in turn: 216 screenshots without a bad frame, and no failure.

The SDK's frame generation headers join runtime/third_party/ffx_sdk, unmodified. tools/make_kit.py and
tools/make_launcher.py take the third library from the --ffx4 folder when it is there, and tools/make_game.py
copies it beside the game.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
epicmeme546
2026-11-11 04:49:01 -05:00
parent 5833ad8e18
commit 5bdf583331
18 changed files with 1144 additions and 47 deletions

View File

@@ -106,7 +106,7 @@ folder's `licenses/` and in each component's own folder.
| MacNativeGames shader variant list | as in the installer release row above | MIT | `data/shader_list.tsv` |
| Weeping of the Empire menu cursor: eui's focus box read through ScreenMgr, BoxCursorMgr and the screens' node lists, AnimButton's hit test, the stale-hit-pane fallback to the parts panes, the three writes of moveBoxCursor, the structure offsets (1.0.0; the ScreenMgr instance placed in 1.4.3 by us) | https://github.com/daniel-madrid-07/weeping-of-the-empire @ `df0586cdc5702ca19bbebfa59697e0b6980f6237` (`engine/runtime/nx2navi_menu_cursor.cpp`), Copyright (c) 2026 daniel-madrid-07 | MIT | `runtime/menu_cursor.c` |
| AMD FidelityFX API headers (FSR 3.1 upscaling and frame generation) and its signed Vulkan library | https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK @ `v1.1.4` (`ffx-api/include/ffx_api/`, `PrebuiltSignedDLL/amd_fidelityfx_vk.dll`), Copyright (C) 2024 Advanced Micro Devices, Inc. | MIT | `runtime/third_party/ffx_api/` (unmodified, with `LICENSE.txt`), used by `runtime/vk_ffx.cpp`; the library ships in the build kit's `bin/` and beside the built game (never in this repository) |
| AMD FSR SDK API headers (FSR 4 upscaling) and its signed Vulkan libraries | https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK @ `v2.4.0` (`Kits/FidelityFX/api/include/`, `Kits/FidelityFX/upscalers/include/ffx_upscale.h`, `Kits/FidelityFX/signedbin/amd_fidelityfx_loader_vk.dll`, `Kits/FidelityFX/signedbin/amd_fidelityfx_upscaler_vk.dll`), Copyright (C) Advanced Micro Devices, Inc. | MIT | `runtime/third_party/ffx_sdk/` (unmodified, with `LICENSE.txt`), used by `runtime/vk_ffx4.cpp`; the libraries ship in the build kit's `bin/` and beside the built game (never in this repository) |
| AMD FSR SDK API headers (FSR 4 upscaling, frame generation) and its signed Vulkan libraries | https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK @ `v2.4.0` (`Kits/FidelityFX/api/include/`, `Kits/FidelityFX/upscalers/include/ffx_upscale.h`, `Kits/FidelityFX/framegeneration/include/`, `Kits/FidelityFX/signedbin/amd_fidelityfx_loader_vk.dll`, `amd_fidelityfx_upscaler_vk.dll`, `amd_fidelityfx_framegeneration_vk.dll`), Copyright (C) Advanced Micro Devices, Inc. | MIT | `runtime/third_party/ffx_sdk/` (unmodified, with `LICENSE.txt`), used by `runtime/vk_ffx4.cpp` and `runtime/vk_fg4.cpp`; the libraries ship in the build kit's `bin/` and beside the built game (never in this repository) |
## Distributed in the launcher release

View File

@@ -165,7 +165,8 @@ beside the game.
`py -I tools\make_game.py --rebuild --out "C:\Games\Ocarina"` to draw them again (it skips everything else).
- The build copies NVIDIA's DLSS library `nvngx_dlss.dll` (with `nvngx_dlss-NOTICE.txt`, which names its licence)
and FSR 3.1's `amd_fidelityfx_vk.dll` beside `ocarina.exe` from the kit's `bin\`, and FSR 4's
`amd_fidelityfx_loader_vk.dll` and `amd_fidelityfx_upscaler_vk.dll` when the kit has both. To use another DLSS version, put
`amd_fidelityfx_loader_vk.dll` and `amd_fidelityfx_upscaler_vk.dll` when the kit has both (and
`amd_fidelityfx_framegeneration_vk.dll` for `fsr4_fg`). To use another DLSS version, put
your own `nvngx_dlss.dll` there: later builds keep it. On Linux the library is `libnvidia-ngx-dlss.so.<version>`
(see "Linux").
- A kit with `bin\dlssg\` (made with `tools/make_kit.py --dlssg`) also brings DLSS Frame Generation on GeForce RTX 40

View File

@@ -359,6 +359,7 @@ game folder is the default `<launcher>\game`. The dump is never touched.
`--ffx` puts AMD's signed `amd_fidelityfx_vk.dll` (FidelityFX SDK 1.1.4) in the release for FSR 3.1.
`--ffx4 <folder>` puts AMD's signed `amd_fidelityfx_loader_vk.dll` and `amd_fidelityfx_upscaler_vk.dll` (FSR SDK
2.4.0, the files of its `Kits/FidelityFX/signedbin/`) in the release for FSR 4; without it the menu offers no FSR 4.
`amd_fidelityfx_framegeneration_vk.dll` goes along when it is in that folder (`fsr4_fg`).
NVIDIA's `nvngx_dlss.dll` goes in by itself: the script downloads the version `tools/dlss_pin.py` pins from
NVIDIA's DLSS SDK repository (cached in `toolchain/launcher-dl/`), checks its size and sha256, and puts it in
`bin/` with `nvngx_dlss-NOTICE.txt`; `make_game.py` copies both beside the game. `--dlss FILE` takes a copy on disk

View File

@@ -28,6 +28,13 @@ Draft notes for the release after 0.7.1. The version number is not decided yet.
`amd_fidelityfx_upscaler_vk.dll`), which ask the driver whether the card can run FSR 4: the rows appear only
where it can, and the log names the version (`ffx4:`). It works together with FSR 3.1 frame generation, and you
can switch between FSR 3.1 and FSR 4 while playing.
- **Frame generation from AMD's newer library, to try** (`fsr4_fg = 1` in `nx2navi.ini`, with FSR 4 upscaling and
frame generation on): the frames are then generated by the library FSR 4 comes with (FSR SDK 2.4,
`amd_fidelityfx_framegeneration_vk.dll`) instead of the FSR 3.1 library. It picks the version for your card: on a
Radeon RX 9000 with Windows 11 that is AMD's machine-learned FSR Frame Generation 4, **which nobody has tested in
this game yet**; on every other card it is version 3.1.6, which on an RX 7900 XT runs at the same frame rate as
before and showed clean frames in a fast camera turn. It is off unless you set it, and when it fails the FSR 3.1
library takes over. Please report how it looks on an RX 9000.
- **Add to Steam** in Customize Launcher (Windows): adds the game to your Steam library as *The Legend of Zelda
Ocarina of Time*, with Navi as its icon and the title screen's artwork and logo. Starting it from Steam starts the
game. Steam restarts once while it is added.
@@ -36,6 +43,12 @@ Draft notes for the release after 0.7.1. The version number is not decided yet.
## Fixed
- A crash with frame generation (AMD FSR 3.1) set: switching Upscaling to a choice without FSR or DLSS and back, which
the Upscaling row does on its way through its choices, ended the game in AMD's driver. Frame generation was handed
the depth picture of the last frame before the switch, which the game had replaced by then.
- Frame generation never generates a frame from a picture with the HUD in it any more: a frame for which the picture
without the HUD is missing is shown as it is, so the HUD cannot be warped along with the scene behind it. On screen
the item button stayed pixel for pixel in place through a fast camera turn, with both frame generation libraries.
- Your own background picture in the launcher is no longer partly hidden under the top bar: it now starts right
below it, and keeps its shape.

View File

@@ -404,6 +404,12 @@
# cards with AMD's driver 26.10.1 or newer. Elsewhere, or when it fails, FSR 3.1 runs and the log says why. Changes
# while the game runs take effect at once.
# fsr4 = 0
# With fsr4 = 1 and framegen = fsr3: 1 takes the frame generation of the same AMD library
# (amd_fidelityfx_framegeneration_vk.dll next to the executable) instead of the FSR 3.1 library's. The library picks
# the version for the GPU: the machine-learned FSR Frame Generation 4 on Radeon RX 9000 cards with Windows 11, which
# has not been tested, and 3.1.6 on every other, where it runs at the same frame rate as the FSR 3.1 library's. When
# it fails, the FSR 3.1 library takes over and the log says why (fg4:). Changes while the game runs take effect at once.
# fsr4_fg = 0
# The DLSS model: default (NVIDIA's advice per mode: K for dlaa, quality and balanced, M for performance, L for
# ultra_performance), k (K in every mode) or j (the other transformer model: slightly less ghosting behind moving
# characters, a little more flicker). Any other model by its letter, a to m: on slower RTX cards the older model e is

View File

@@ -62,7 +62,7 @@ static const char *const known[] = {
"SVC_SLOW_MS",
"SHOTS_FULL", "SPEED_PROBE", "SPIN", "SPIN_HANDOFF_US", "SPLIT_DRAWS", "SPLIT_IDLE", "SPLIT_IN_PASS", "SPLIT_US", "SPV_MARK_TEST", "SPV_OVERRIDE", "STICK_DEADZONE", "STICK_SENS",
"SWKBD_ANSWER",
"SET_AT", "SWKBD_TEST", "TAA", "FSR4", "TEST_PAD", "TEST_PAD_AT", "TEST_PREPARE_SHADERS", "TEX_ALIAS", "TEX_DISUSE", "TEX_GEN_UNDO", "TEX_MEMO", "TEX_VRAM_PRESSURE", "TEX_RELEASED_MB", "TEX_REUSE", "TEX_COMMIT_FREE_MB", "TEX_PROC_COMMIT_MB", "TEX_EVICT", "TEX_COMMIT_EVICT", "MIRROR_EVICT", "VRAM_BUDGET_MB", "TIMER_1MS", "TRACE", "TXD_TRANSPOSE", "UBO_SNAPSHOT", "EVO_TRACE", "EVENT_AT_SYNC", "UPSCALER", "VK_DYN_A2C", "VK_DYN_VERTEX", "VK_NAN_PRESERVE", "VK_NOOPT", "VK_NO_FLOAT64",
"SET_AT", "SWKBD_TEST", "TAA", "FSR4", "FSR4_FG", "FG4_VERSION", "FG4_SWAPCHAIN", "TEST_PAD", "TEST_PAD_AT", "TEST_PREPARE_SHADERS", "TEX_ALIAS", "TEX_DISUSE", "TEX_GEN_UNDO", "TEX_MEMO", "TEX_VRAM_PRESSURE", "TEX_RELEASED_MB", "TEX_REUSE", "TEX_COMMIT_FREE_MB", "TEX_PROC_COMMIT_MB", "TEX_EVICT", "TEX_COMMIT_EVICT", "MIRROR_EVICT", "VRAM_BUDGET_MB", "TIMER_1MS", "TRACE", "TXD_TRANSPOSE", "UBO_SNAPSHOT", "EVO_TRACE", "EVENT_AT_SYNC", "UPSCALER", "VK_DYN_A2C", "VK_DYN_VERTEX", "VK_NAN_PRESERVE", "VK_NOOPT", "VK_NO_FLOAT64",
"VK_PRESENT", "VK_RECONVERGE", "VK_NAN_BITS", "VK_RECORDER", "VK_SSBO_ALIGN", "VK_SUBGROUP", "VK_SUBGROUP_PIN", "VK_VALIDATION", "VOLUME", "VRAM_HEADROOM", "VS_OUTS",
"WALK_KEY", "WALK_SCALE", "WB_EXACT", "WB_FENCE_LIMIT", "WB_MERGE", "WB_NO_REUP", "WB_MERGE_MAX", "WB_PROBE", "WB_SPLIT", "WB_LAZY", "WB_LAZY_TOUCH", "WINDOW_SIZE", "XLATE", "XLATE_ID", "XLATE_KEEP", "XLATE_PROCS",
"ZSTD_CHECK", "ZSTD_NATIVE",
@@ -88,7 +88,7 @@ static const struct { const char *key, *section; } sections[] = {
{"HDR_HIGHLIGHTS", "display"}, {"HDR_SHADOWS", "display"}, {"HDR_CONTRAST", "display"}, {"HDR_SATURATION", "display"},
{"HDR_HIGHLIGHT_SAT", "display"}, {"HDR_BLOWOUT", "display"}, {"HDR_FLARE", "display"}, {"HDR_HUE", "display"},
{"HDR_GRADE", "display"}, {"SHADOW_RES", "display"},
{"DLAA", "display"}, {"FSR4", "display"}, {"DLSS", "display"}, {"DLSS_PRESET", "display"}, {"DLSS_IDLE_FREE", "display"}, {"DLSS_CHAR_MASK", "display"}, {"FXAA", "display"},
{"DLAA", "display"}, {"FSR4", "display"}, {"FSR4_FG", "display"}, {"DLSS", "display"}, {"DLSS_PRESET", "display"}, {"DLSS_IDLE_FREE", "display"}, {"DLSS_CHAR_MASK", "display"}, {"FXAA", "display"},
{"FRAMEGEN", "display"}, {"FRAMEGEN_MULTIPLIER", "display"}, {"FG_MULT", "display"},
{"DEVICE_PROFILE", "display"}, {"ASSUME_FOCUS", "display"},
{"DISCORD_RPC", "discord"}, {"DISCORD_APP_ID", "discord"}, {"DISCORD_IMAGE", "discord"},
@@ -153,6 +153,7 @@ static const CfgMeta metas[] = {
{"DLAA", "0", "0|1", LIVE}, {"DLSS", "dlaa", "dlaa|quality|balanced|performance|ultra_performance", LIVE},
{"DLSS_PRESET", "default", "default|k|j", LIVE}, {"DLSS_IDLE_FREE", "1", "0|1|2", RESTART}, {"TAA", "dlss", "dlss|fsr3", LIVE}, // vk_dlaa.c fsr3
{"FSR4", "0", "0|1", LIVE}, // vk_ffx.cpp vk_ffx_fsr4
{"FSR4_FG", "0", "0|1", LIVE}, // vk_fg.cpp use4
{"FRAMEGEN", "off", "off|fsr3|dlss", LIVE}, // vk_fg.cpp, vk_dlssg.c
{"FRAMEGEN_MULTIPLIER", "2", "2|3|4", LIVE}, // vk_dlssg.c: frames on screen per rendered frame with DLSS
// the same where DLSS's frame generation runs through NGX itself (gfx_present.c ng_multiplier): 2x to 6x or dynamic

View File

@@ -1,5 +1,6 @@
AMD FSR SDK 2.4.0 (https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK, tag v2.4.0), the files of
Kits/FidelityFX/api/include and Kits/FidelityFX/upscalers/include kept here at the same relative paths, unmodified.
Kits/FidelityFX/api/include, Kits/FidelityFX/upscalers/include and Kits/FidelityFX/framegeneration/include kept here at
the same relative paths, unmodified.
docs/license.md of that tag lists them among the files under this licence:
Copyright (C) Advanced Micro Devices, Inc.

View File

@@ -0,0 +1,299 @@
// 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 "ffx_framegeneration_api_types.h"
#define FFX_FRAMEGENERATION_VERSION_MAJOR 4
#define FFX_FRAMEGENERATION_VERSION_MINOR 0
#define FFX_FRAMEGENERATION_VERSION_PATCH 2
#define FFX_FRAMEGENERATION_MAKE_VERSION(major, minor, patch) (((major) << 22) | ((minor) << 12) | (patch))
#define FFX_FRAMEGENERATION_VERSION FFX_FRAMEGENERATION_MAKE_VERSION(FFX_FRAMEGENERATION_VERSION_MAJOR, FFX_FRAMEGENERATION_VERSION_MINOR, FFX_FRAMEGENERATION_VERSION_PATCH)
#define FFX_FRAMEGENERATION_PREPARE_REPLACEMENT_SUFFIX "PrepareV2"
#if defined(__cplusplus)
extern "C" {
#endif
enum FfxApiCreateContextFramegenerationFlags
{
FFX_FRAMEGENERATION_ENABLE_ASYNC_WORKLOAD_SUPPORT = (1 << 0), ///< A bit indicating that async compute workloads should be supported. Enables generation work on async compute queues.
FFX_FRAMEGENERATION_ENABLE_DISPLAY_RESOLUTION_MOTION_VECTORS = (1 << 1), ///< A bit indicating if the motion vectors are rendered at display resolution.
FFX_FRAMEGENERATION_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION = (1 << 2), ///< A bit indicating that the motion vectors have the jittering pattern applied to them.
FFX_FRAMEGENERATION_ENABLE_DEPTH_INVERTED = (1 << 3), ///< A bit indicating that the input depth buffer data provided is inverted [1..0].
FFX_FRAMEGENERATION_ENABLE_DEPTH_INFINITE = (1 << 4), ///< A bit indicating that the input depth buffer data provided is using an infinite far plane.
FFX_FRAMEGENERATION_ENABLE_HIGH_DYNAMIC_RANGE = (1 << 5), ///< A bit indicating if the input color data provided to all inputs is using a high-dynamic range.
FFX_FRAMEGENERATION_ENABLE_DEBUG_CHECKING = (1 << 6), ///< A bit indicating that the runtime should check some API values and report issues.
};
enum FfxApiDispatchFramegenerationFlags
{
FFX_FRAMEGENERATION_FLAG_DRAW_DEBUG_TEAR_LINES = (1 << 0), ///< A bit indicating that the debug tear lines will be drawn to the generated output.
FFX_FRAMEGENERATION_FLAG_DRAW_DEBUG_RESET_INDICATORS = (1 << 1), ///< A bit indicating that the debug reset indicators will be drawn to the generated output.
FFX_FRAMEGENERATION_FLAG_DRAW_DEBUG_VIEW = (1 << 2), ///< A bit indicating that the generated output resource will contain debug views with relevant information.
FFX_FRAMEGENERATION_FLAG_NO_SWAPCHAIN_CONTEXT_NOTIFY = (1 << 3), ///< A bit indicating that the context should only run frame interpolation and not modify the swapchain.
FFX_FRAMEGENERATION_FLAG_DRAW_DEBUG_PACING_LINES = (1 << 4), ///< A bit indicating that the debug pacing lines will be drawn to the generated output.
FFX_FRAMEGENERATION_FLAG_RESERVED_1 = (1 << 5), ///< Reserved for future use.
FFX_FRAMEGENERATION_FLAG_RESERVED_2 = (1 << 6), ///< Reserved for future use.
};
enum FfxApiUiCompositionFlags
{
FFX_FRAMEGENERATION_UI_COMPOSITION_FLAG_USE_PREMUL_ALPHA = (1 << 0), ///< A bit indicating that we use premultiplied alpha for UI composition.
FFX_FRAMEGENERATION_UI_COMPOSITION_FLAG_ENABLE_INTERNAL_UI_DOUBLE_BUFFERING = (1 << 1), ///< A bit indicating that the swapchain should doublebuffer the UI resource.
};
#define FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATION, 0x01)
struct ffxCreateContextDescFrameGeneration
{
ffxCreateContextDescHeader header; ///< Description header for frame generation context creation.
uint32_t flags; ///< A combination of zero or more values from FfxApiCreateContextFramegenerationFlags.
struct FfxApiDimensions2D displaySize; ///< The resolution at which both rendered and generated frames will be displayed.
struct FfxApiDimensions2D maxRenderSize; ///< The maximum rendering resolution.
uint32_t backBufferFormat; ///< The surface format for the backbuffer. One of the values from FfxApiSurfaceFormat.
};
#define FFX_API_CALLBACK_DESC_TYPE_FRAMEGENERATION_PRESENT FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATION, 0x05)
typedef struct ffxCallbackDescFrameGenerationPresent
{
ffxDispatchDescHeader header; ///< Description header for present callback dispatch.
void* device; ///< The GPU device (ID3D12Device for DX12, VkDevice for Vulkan) passed during context creation.
void* commandList; ///< A command list (ID3D12GraphicsCommandList for DX12, VkCommandBuffer for Vulkan) that will be executed before presentation.
struct FfxApiResource currentBackBuffer; ///< Backbuffer image either rendered or generated.
struct FfxApiResource currentUI; ///< UI image for composition if passed. Otherwise empty.
struct FfxApiResource outputSwapChainBuffer; ///< Output image that will be presented.
bool isGeneratedFrame; ///< true if this frame is generated, false if rendered.
uint64_t frameID; ///< Identifier used to select internal resources when async support is enabled. Must increment by exactly one (1) for each frame. Any non-exactly-one difference will reset the frame generation logic.
} ffxCallbackDescFrameGenerationPresent;
#define FFX_API_DISPATCH_DESC_TYPE_FRAMEGENERATION FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATION, 0x03)
typedef struct ffxDispatchDescFrameGeneration
{
ffxDispatchDescHeader header; ///< Description header for frame generation dispatch.
void* commandList; ///< The command list (ID3D12GraphicsCommandList for DX12, VkCommandBuffer for Vulkan) to record frame generation commands.
struct FfxApiResource presentColor; ///< The current presentation color buffer to be used as source for frame generation.
struct FfxApiResource outputs[4]; ///< Output destination targets for generated frames (1 for each frame in numGeneratedFrames).
uint32_t numGeneratedFrames; ///< The number of frames to generate from the input color target.
bool reset; ///< A boolean value which when set to true, indicates the camera has moved discontinuously and frame generation should reset.
uint32_t backbufferTransferFunction; ///< The transfer function used to convert frame generation source color data to linear RGB. One of the values from FfxApiBackbufferTransferFunction.
float minMaxLuminance[2]; ///< Min and max luminance values (in nits), used when converting HDR colors to linear RGB.
struct FfxApiRect2D generationRect; ///< The area of the backbuffer that should be used for frame generation in case only a part of the screen is active (e.g. due to movie bars).
uint64_t frameID; ///< Identifier used to select internal resources when async support is enabled. Must increment by exactly one (1) for each frame. Any non-exactly-one difference will reset the frame generation logic.
} ffxDispatchDescFrameGeneration;
typedef ffxReturnCode_t (*FfxApiPresentCallbackFunc)(ffxCallbackDescFrameGenerationPresent* params, void* pUserCtx);
typedef ffxReturnCode_t (*FfxApiFrameGenerationDispatchFunc)(ffxDispatchDescFrameGeneration* params, void* pUserCtx);
#define FFX_API_CONFIGURE_DESC_TYPE_FRAMEGENERATION FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATION, 0x02)
struct ffxConfigureDescFrameGeneration
{
ffxConfigureDescHeader header; ///< Description header for frame generation configuration.
void* swapChain; ///< The swapchain to use with frame generation (IDXGISwapChain for DX12, VkSwapchainKHR for Vulkan).
FfxApiPresentCallbackFunc presentCallback; ///< Callback function called when finalizing the frame image for presentation and UI composition.
void* presentCallbackUserContext; ///< User context pointer to be passed to the present callback function.
FfxApiFrameGenerationDispatchFunc frameGenerationCallback; ///< Callback function called to dispatch frame generation work and generate interpolated frames.
void* frameGenerationCallbackUserContext; ///< User context pointer to be passed to the frame generation callback function.
bool frameGenerationEnabled; ///< Sets the state of frame generation. Set to false to disable frame generation.
bool allowAsyncWorkloads; ///< Sets the state of async workloads. Set to true to enable generation work on async compute.
struct FfxApiResource HUDLessColor; ///< The hudless back buffer image to use for UI extraction from backbuffer resource. May be empty.
uint32_t flags; ///< Zero or combination of flags from FfxApiDispatchFrameGenerationFlags.
bool onlyPresentGenerated; ///< Set to true to only present generated frames.
struct FfxApiRect2D generationRect; ///< The area of the backbuffer that should be used for generation in case only a part of the screen is used e.g. due to movie bars
uint64_t frameID; ///< Identifier used to select internal resources when async support is enabled. Must increment by exactly one (1) for each frame. Any non-exactly-one difference will reset the frame generation logic.
};
#pragma FFX_PRAGMA_WARNING_PUSH
#pragma FFX_PRAGMA_WARNING_DISABLE_DEPRECATIONS
#define FFX_API_DISPATCH_DESC_TYPE_FRAMEGENERATION_PREPARE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATION, 0x04)
struct FFX_DEPRECATION("ffxDispatchDescFrameGenerationPrepare is deprecated, use ffxDispatchDescFrameGeneration" FFX_FRAMEGENERATION_PREPARE_REPLACEMENT_SUFFIX) ffxDispatchDescFrameGenerationPrepare
{
ffxDispatchDescHeader header; ///< Description header for frame generation prepare dispatch.
uint64_t frameID; ///< Frame identifier used to select internal resources when async support is enabled. Must increment by exactly one (1) for each frame. Any non-exactly-one difference will reset the frame generation logic.
uint32_t flags; ///< Zero or combination of values from FfxApiDispatchFrameGenerationFlags.
void* commandList; ///< The command list (ID3D12GraphicsCommandList for DX12, VkCommandBuffer for Vulkan) to record frame generation prepare commands.
struct FfxApiDimensions2D renderSize; ///< The dimensions used to render game content, dilatedDepth, dilatedMotionVectors are expected to be of ths size.
struct FfxApiFloatCoords2D jitterOffset; ///< The subpixel jitter offset applied to the camera.
struct FfxApiFloatCoords2D motionVectorScale; ///< The scale factor to convert motion vectors to pixel space. Internally: pixelSpaceMV = motionVector * motionVectorScale. Set to renderSize if motion vectors are in UV space [0,1], or to (1.0, 1.0) if motion vectors are already in pixel space. When using display-resolution motion vectors, set to displaySize.
float frameTimeDelta; ///< Time elapsed in milliseconds since the last frame.
bool unused_reset; ///< A (currently unused) boolean value which when set to true, indicates FrameGeneration will be called in reset mode
float cameraNear; ///< The distance to the near plane of the camera.
float cameraFar; ///< The distance to the far plane of the camera. This is used only used in case of non infinite depth.
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
struct FfxApiResource depth; ///< The depth buffer data for the current frame.
struct FfxApiResource motionVectors; ///< The motion vector data for the current frame.
};
#pragma FFX_PRAGMA_WARNING_POP
#define FFX_API_CONFIGURE_DESC_TYPE_FRAMEGENERATION_KEYVALUE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATION, 0x06)
struct ffxConfigureDescFrameGenerationKeyValue
{
ffxConfigureDescHeader header; ///< Description header for frame generation key-value configuration.
uint64_t key; ///< Configuration key, member of the FfxApiConfigureFrameGenerationKey enumeration.
uint64_t u64; ///< Integer value or enum value to set.
void* ptr; ///< Pointer value to set or pointer to value to set.
};
enum FfxApiConfigureFrameGenerationKey
{
FFX_API_CONFIGURE_FRAMEGENERATION_KEY_DEBUG_VIEW_MODE = 0, ///< A key to set a debug view mode of display.
FFX_API_CONFIGURE_FRAMEGENERATION_KEY_DEBUG_VIEW_FLOW_SCALE = 1, ///< A key to set a debug view scale for flow resources.
};
#define FFX_API_QUERY_DESC_TYPE_FRAMEGENERATION_GPU_MEMORY_USAGE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATION, 0x07)
struct ffxQueryDescFrameGenerationGetGPUMemoryUsage
{
ffxQueryDescHeader header; ///< Description header for GPU memory usage query.
struct FfxApiEffectMemoryUsage* gpuMemoryUsageFrameGeneration; ///< Output pointer to receive GPU memory usage information for frame generation.
};
#define FFX_API_CONFIGURE_DESC_TYPE_FRAMEGENERATION_REGISTERDISTORTIONRESOURCE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATION, 0x08)
//Pass this optional linked struct after ffxConfigureDescFrameGeneration
struct ffxConfigureDescFrameGenerationRegisterDistortionFieldResource
{
ffxConfigureDescHeader header; ///< Description header for distortion field resource configuration.
struct FfxApiResource distortionField; ///< A resource containing distortion offset data. Needs to be 2-component (ie. RG). Read by FG shaders via Sample. Pixel value encodes [UV coordinate of pixel after lens distortion effect - UV coordinate of pixel before lens distortion].
};
#define FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION_HUDLESS FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATION, 0x09)
//Pass this optional linked struct at FG context creation to enable app to use different hudlessBackBufferformat (IE.RGBA8_UNORM) from backBufferFormat (IE. BGRA8_UNORM)
struct ffxCreateContextDescFrameGenerationHudless
{
ffxCreateContextDescHeader header; ///< Description header for hudless context creation.
uint32_t hudlessBackBufferFormat; ///< The surface format for the hudless back buffer. One of the values from FfxApiSurfaceFormat.
};
#pragma FFX_PRAGMA_WARNING_PUSH
#pragma FFX_PRAGMA_WARNING_DISABLE_DEPRECATIONS
#define FFX_API_DISPATCH_DESC_TYPE_FRAMEGENERATION_PREPARE_CAMERAINFO FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATION, 0x0a)
// Link this struct after ffxDispatchDescFrameGenerationPrepare. This is a required input to FSR3.1.4 and FSR3.1.5.
// These fields are now embedded in ffxDispatchDescFrameGenerationPrepareV2.
struct FFX_DEPRECATION("ffxDispatchDescFrameGenerationPrepareCameraInfo is deprecated, use ffxDispatchDescFrameGeneration" FFX_FRAMEGENERATION_PREPARE_REPLACEMENT_SUFFIX " instead") ffxDispatchDescFrameGenerationPrepareCameraInfo
{
ffxConfigureDescHeader header; ///< Description header for camera info configuration.
float cameraPosition[3]; ///< The camera position in world space.
float cameraUp[3]; ///< The camera up normalized vector in world space.
float cameraRight[3]; ///< The camera right normalized vector in world space.
float cameraForward[3]; ///< The camera forward normalized vector in world space.
};
#pragma FFX_PRAGMA_WARNING_POP
#define FFX_API_QUERY_DESC_TYPE_FRAMEGENERATION_GPU_MEMORY_USAGE_V2 FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATION, 0x0b)
struct ffxQueryDescFrameGenerationGetGPUMemoryUsageV2
{
ffxQueryDescHeader header; ///< Description header for GPU memory usage query.
void* device; ///< The GPU device. For DX12: pointer to ID3D12Device. For VK: pointer to VkDeviceContext. App needs to fill out before Query() call.
struct FfxApiDimensions2D maxRenderSize; ///< The maximum rendering resolution. App needs to fill out before Query() call.
struct FfxApiDimensions2D displaySize; ///< The resolution at which both rendered and generated frames will be displayed. App needs to fill out before Query() call.
uint32_t createFlags; ///< Context creation flags. A combination of zero or more values from FfxApiCreateContextFramegenerationFlags. App needs to fill out before Query() call.
uint32_t dispatchFlags; ///< Dispatch flags. A combination of zero or more values from FfxApiDispatchFrameGenerationFlags. App needs to fill out before Query() call.
uint32_t backBufferFormat; ///< The surface format for the backbuffer. One of the values from FfxApiSurfaceFormat. App needs to fill out before Query() call.
uint32_t hudlessBackBufferFormat; ///< The surface format for HUDLessColor resource if used. One of the values from FfxApiSurfaceFormat. Otherwise set value to FFX_API_SURFACE_FORMAT_UNKNOWN(0). App needs to fill out before Query() call.
struct FfxApiEffectMemoryUsage* gpuMemoryUsageFrameGeneration; ///< Output pointer to receive GPU memory usage information for frame generation. Populated by Query() call.
};
#define FFX_API_DISPATCH_DESC_TYPE_FRAMEGENERATION_PREPARE_V2 FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATION, 0x0c)
struct ffxDispatchDescFrameGenerationPrepareV2
{
ffxDispatchDescHeader header; ///< Description header for frame generation prepare dispatch.
uint64_t frameID; ///< Frame identifier used to select internal resources when async support is enabled. Must increment by exactly one (1) for each frame. Any non-exactly-one difference will reset the frame generation logic.
uint32_t flags; ///< Zero or combination of values from FfxApiDispatchFrameGenerationFlags.
void* commandList; ///< The command list (ID3D12GraphicsCommandList for DX12, VkCommandBuffer for Vulkan) to record frame generation prepare commands.
struct FfxApiDimensions2D renderSize; ///< The dimensions used to render game content. dilatedDepth and dilatedMotionVectors are expected to be of this size.
struct FfxApiFloatCoords2D jitterOffset; ///< The subpixel jitter offset applied to the camera.
struct FfxApiFloatCoords2D motionVectorScale; ///< The scale factor to convert motion vectors to pixel space. Internally: pixelSpaceMV = motionVector * motionVectorScale. Set to renderSize if motion vectors are in UV space [0,1], or to (1.0, 1.0) if motion vectors are already in pixel space. When using display-resolution motion vectors, set to displaySize.
float frameTimeDelta; ///< Time elapsed in milliseconds since the last frame.
bool reset; ///< A boolean value which when set to true, indicates the camera has moved discontinuously and frame generation should reset its internal state.
float cameraNear; ///< The distance to the near plane of the camera.
float cameraFar; ///< The distance to the far plane of the camera. This is used only in case of non-infinite depth.
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.
struct FfxApiResource depth; ///< The depth buffer data for the current frame.
struct FfxApiResource motionVectors; ///< The motion vector data for the current frame.
float cameraPosition[3]; ///< The camera position in world space.
float cameraUp[3]; ///< The camera up normalized vector in world space.
float cameraRight[3]; ///< The camera right normalized vector in world space.
float cameraForward[3]; ///< The camera forward normalized vector in world space.
};
#define FFX_API_CALLBACK_DESC_TYPE_FRAMEGENERATION_PRESENT_PREMUL_ALPHA FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATION, 0x0d)
// This is a structure that is attached to header.pNext of ffxCallbackDescFrameGenerationPresent
// Provides whether to use premultiplied alpha blending or not for the UI
typedef struct ffxCallbackDescFrameGenerationPresentPremulAlpha
{
ffxApiHeader header; ///< Header for versioning & ABI stability.
bool usePremulAlpha; ///< Toggles whether UI gets premultiplied alpha blending or not.
} ffxCallbackDescFrameGenerationPresentPremulAlpha;
/// Callback function for waiting on a named fence to reach a specific value.
/// @param fenceName The name of the fence to wait on.
/// @param fenceValueToWaitFor The fence value to wait for.
/// @return 0 on success, non-zero error code on failure.
typedef int32_t (*FfxWaitCallbackFunc)(wchar_t* fenceName, uint64_t fenceValueToWaitFor);
#define FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION_VERSION FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATION, 0x0e)
// Pass this linked struct at FG context creation to enable new API features - omitting this will prevent new APIs from functioning.
struct ffxCreateContextDescFrameGenerationVersion
{
ffxCreateContextDescHeader header; ///< Description header for frame generation version context creation.
uint32_t version; ///< The version of the API the application was built against. This must be set to FFX_FRAMEGENERATION_VERSION.
};
#define FFX_API_FRAME_GENERATION_CONFIG FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATION, 0x0f)
/// A structure representing the configuration options to pass to FrameInterpolationSwapChain
///
/// @ingroup FfxInterface
typedef struct FfxFrameGenerationConfig
{
ffxApiHeader header; ///< Header for versioning & ABI stability.
void* swapChain; ///< The <c><i>FfxSwapchain</i></c> to use with frame interpolation.
FfxApiPresentCallbackFunc presentCallback; ///< A UI composition callback to call when finalizing the frame image.
void* presentCallbackContext; ///< A pointer to be passed to the UI composition callback.
FfxApiFrameGenerationDispatchFunc frameGenerationCallback; ///< The frame generation callback to use to generate the interpolated frame.
void* frameGenerationCallbackContext; ///< A pointer to be passed to the frame generation callback.
bool frameGenerationEnabled; ///< Sets the state of frame generation. Set to false to disable frame generation.
bool allowAsyncWorkloads; ///< Sets the state of async workloads. Set to true to enable interpolation work on async compute.
bool allowAsyncPresent; ///< Sets the state of async presentation (console only). Set to true to enable present from async command queue.
struct FfxApiResource HUDLessColor; ///< The hudless back buffer image to use for UI extraction from backbuffer resource.
uint32_t flags; ///< Zero or combination of flags from FfxApiDispatchFrameGenerationFlags.
bool onlyPresentInterpolated; ///< Set to true to only present interpolated frame.
struct FfxApiRect2D interpolationRect; ///< Set the area in the backbuffer that will be interpolated.
uint64_t frameID; ///< A frame identifier used to synchronize resource usage in workloads.
bool drawDebugPacingLines; ///< Sets the state of pacing debug lines. Set to true to display debug lines.
} FfxFrameGenerationConfig;
#if defined(__cplusplus)
} // extern "C"
#endif

View File

@@ -0,0 +1,72 @@
// 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_types.h"
/*
Tuning varianceFactor and safetyMarginInMs Tips:
Calculation of frame pacing algorithm's next target timestamp:
target frametime delta = average Frametime - (variance * varianceFactor) - safetyMarginInMs
Default Tuning uses safetyMarginInMs==0.1ms and varianceFactor==0.1.
Say Tuning set A uses safetyMarginInMs==0.75ms, and varianceFactor==0.1.
Say Tuning Set B uses safetyMarginInMs==0.01ms and varianceFactor==0.3.
Example #1 - Actual Game Cutscene. Game's framerate after FG ON during camera pan from normal (19ms avg frametime) to complex (37ms avg frametime over 1 sec) and back to normal scene complexity (19ms avg frametime).
After the panning is done,
- Default Tuning now gets stuck at targeting ~33ms after panning to a complex scene. GPU utilization significantly lower in this case.
- Tuning Set B and Tuning Set A are able to recover close to ~19ms because of these 2 tuning result in lower "target frametime delta" than Default Tuning.
However, larger varianceFactor or safetyMarginInMs results in higher variance. As seen in Example #2 below.
Example #2 - FFX_API_FSR sample. Set app fps cap to 33.33ms. Use OCAT to capture 10s at default camera position.
FSR 3.1.0 FG msbetweenpresents ping-pong between 16.552 (5th-percentile) and 16.832 (95th-percentile). Variance is 0.01116.
Tuning set A FG msbetweenpresents ping-pong between 15.901 (5th-percentile) and 17.500 (95th-percentile). Variance is 0.057674.
Tuning Set B FG msbetweenpresents ping-pong between 16.589 (5th-percentile) and 16.971 (95th-percentile). Variance is 0.014452.
| FG output Frames timestamp | n | n+1 | n+2 | n+3 | frame delta n+1 to n+2 | frame delta n+2 to n+3 |
| -------------------------- | - | ------ | ----- | ------ | ---------------------- | ---------------------- |
| App real frame presents | 0 | | 33.33 | | | |
| Default Tuning | 0 | 16.552 | 33.33 | 49.882 | 16.778 | 16.552 |
| Tuning Set A | 0 | 15.901 | 33.33 | 49.231 | 17.429 | 15.901 |
| Tuning Set B | 0 | 16.589 | 33.33 | 49.919 | 16.741 | 16.589 |
Analysis of table data in words:
Ignoring the cost of FI,
"Tuning set A"'s "target frametime delta" of 15.901 results in larger frame to frame delta (or in other words larger variance) vs Default tuning.
"Tuning set B"'s "target frametime delta" of 16.589 results in a bit larger frame to frame delta (or in other words a bit larger variance) vs Default tuning.
TLDR:
If your game when using FG, frame rate is running at an unexpectedly low frame rate, after gradual transition from rendering complex to easy scene complexity, you could try setting "Tuning Set B" to recover lost FPS at cost of a bit higher variance.
*/
//struct definition matches FfxSwapchainFramePacingTuning
typedef struct FfxApiSwapchainFramePacingTuning
{
float safetyMarginInMs; ///< Frame pacing sleep time's safety margin in milliseconds. Default is 0.1ms.
float varianceFactor; ///< Variance factor for consecutive frame times. Valid range [0.0,1.0], default is 0.1.
bool allowHybridSpin; ///< Allows pacing spinlock to sleep. Less precise, but power saving. Default is false.
uint32_t hybridSpinTime; ///< How long to spin if allowHybridSpin is true, in timer resolution units. Not recommended to go below 2, as it will result in frequent overshoots. Default is 2.
bool allowWaitForSingleObjectOnFence; ///< Allows WaitForSingleObject instead of spinning for fence value. Default is false.
} FfxApiSwapchainFramePacingTuning;

View File

@@ -0,0 +1,150 @@
// 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/vk/ffx_api_vk.h"
#define FFX_FRAMEGENERATION_SWAPCHAIN_VK_VERSION_MAJOR 4
#define FFX_FRAMEGENERATION_SWAPCHAIN_VK_VERSION_MINOR 0
#define FFX_FRAMEGENERATION_SWAPCHAIN_VK_VERSION_PATCH 0
#define FFX_FRAMEGENERATION_SWAPCHAIN_VK_MAKE_VERSION(major, minor, patch) (((major) << 22) | ((minor) << 12) | (patch))
#define FFX_FRAMEGENERATION_SWAPCHAIN_VK_VERSION FFX_FRAMEGENERATION_SWAPCHAIN_VK_MAKE_VERSION(FFX_FRAMEGENERATION_SWAPCHAIN_VK_VERSION_MAJOR, FFX_FRAMEGENERATION_SWAPCHAIN_VK_VERSION_MINOR, FFX_FRAMEGENERATION_SWAPCHAIN_VK_VERSION_PATCH)
#define FFX_API_CREATE_CONTEXT_DESC_TYPE_FGSWAPCHAIN_VK FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN_VK, 0x01)
struct ffxCreateContextDescFrameGenerationSwapChainVK
{
ffxCreateContextDescHeader header;
VkPhysicalDevice physicalDevice; ///< the physicak device used by the program.
VkDevice device; ///< the logical device used by the program.
VkSwapchainKHR* swapchain; ///< the current swapchain to be replaced. Will be destroyed when the context is created. This can be VK_NULL_HANDLE. Will contain the new swapchain on return.
VkAllocationCallbacks* allocator; ///< optional allocation callbacks.
VkSwapchainCreateInfoKHR createInfo; ///< the description of the desired swapchain. If its VkSwapchainCreateInfoKHR::oldSwapchain field isn't VK_NULL_HANDLE, it should be the same as the ffxCreateContextDescFrameGenerationSwapChainVK::swapchain field above.
VkQueueInfoFFXAPI gameQueue; ///< the main graphics queue, where Present is called.
VkQueueInfoFFXAPI asyncComputeQueue; ///< A queue with Compute capability.
VkQueueInfoFFXAPI presentQueue; ///< A queue with Transfer and Present capabilities.
VkQueueInfoFFXAPI imageAcquireQueue; ///< A queue with no capability required.
};
#define FFX_API_CONFIGURE_DESC_TYPE_FGSWAPCHAIN_REGISTERUIRESOURCE_VK FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN_VK, 0x02)
struct ffxConfigureDescFrameGenerationSwapChainRegisterUiResourceVK
{
ffxConfigureDescHeader header;
struct FfxApiResource uiResource; ///< Resource containing user interface for composition. May be empty.
uint32_t flags; ///< Zero or combination of values from FfxApiUiCompositionFlags.
};
#define FFX_API_QUERY_DESC_TYPE_FGSWAPCHAIN_INTERPOLATIONCOMMANDLIST_VK FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN_VK, 0x03)
struct ffxQueryDescFrameGenerationSwapChainInterpolationCommandListVK
{
ffxQueryDescHeader header;
void** pOutCommandList; ///< Output command nuffer (VkCommandBuffer) to be used for frame generation dispatch.
};
#define FFX_API_QUERY_DESC_TYPE_FGSWAPCHAIN_INTERPOLATIONTEXTURE_VK FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN_VK, 0x04)
struct ffxQueryDescFrameGenerationSwapChainInterpolationTextureVK
{
ffxQueryDescHeader header;
struct FfxApiResource* pOutTexture; ///< Output resource in which the frame interpolation result should be placed.
};
#define FFX_API_DISPATCH_DESC_TYPE_FGSWAPCHAIN_WAIT_FOR_PRESENTS_VK FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN_VK, 0x07)
struct ffxDispatchDescFrameGenerationSwapChainWaitForPresentsVK
{
ffxDispatchDescHeader header;
};
#define FFX_API_CONFIGURE_DESC_TYPE_FRAMEGENERATIONSWAPCHAIN_KEYVALUE_VK FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN_VK, 0x08)
struct ffxConfigureDescFrameGenerationSwapChainKeyValueVK
{
ffxConfigureDescHeader header;
uint64_t key; ///< Configuration key, member of the FfxApiConfigureFrameGenerationSwapChainKeyVK enumeration.
uint64_t u64; ///< Integer value or enum value to set.
void* ptr; ///< Pointer to set or pointer to value to set.
};
//enum value matches enum FfxFrameInterpolationSwapchainConfigureKey
enum FfxApiConfigureFrameGenerationSwapChainKeyVK
{
FFX_API_CONFIGURE_FG_SWAPCHAIN_KEY_WAITCALLBACK = 0, ///< Sets FfxWaitCallbackFunc
FFX_API_CONFIGURE_FG_SWAPCHAIN_KEY_FRAMEPACINGTUNING = 2, ///< Sets FfxApiSwapchainFramePacingTuning casted from ptr
};
#define FFX_API_QUERY_DESC_TYPE_FRAMEGENERATIONSWAPCHAIN_GPU_MEMORY_USAGE_VK FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN_VK, 0x09)
struct ffxQueryFrameGenerationSwapChainGetGPUMemoryUsageVK
{
ffxQueryDescHeader header;
struct FfxApiEffectMemoryUsage* gpuMemoryUsageFrameGenerationSwapchain; ///< Output pointer. Requires an existing swapchain context.
};
/// Pre-context GPU memory estimate for a Vulkan frame-generation swapchain.
/// Does not require an ffxContext. Pass the device and the swapchain configuration the application intends to create.
#define FFX_API_QUERY_DESC_TYPE_FRAMEGENERATIONSWAPCHAIN_GPU_MEMORY_USAGE_VK_V2 FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN_VK, 0x0b)
struct ffxQueryFrameGenerationSwapChainGetGPUMemoryUsageVKV2
{
ffxQueryDescHeader header; ///< Description header for the pre-context GPU memory query.
VkDevice device; ///< Logical device used to size images. Required.
VkPhysicalDevice physicalDevice; ///< Physical device that owns the logical device. Required. Used to select an external provider.
struct FfxApiDimensions2D displaySize; ///< Resolution of rendered and generated frames. Required.
uint32_t backBufferFormat; ///< Backbuffer format. One of the values from FfxApiSurfaceFormat. Required.
uint32_t backBufferCount; ///< Number of application backbuffers, from 1 through 6. Required.
struct FfxApiDimensions2D uiResourceSize; ///< UI resource resolution. Required when flags requests internal UI double buffering, otherwise ignored.
uint32_t uiResourceFormat; ///< UI resource format. One of the values from FfxApiSurfaceFormat. Required when flags requests internal UI double buffering.
uint32_t flags; ///< UI composition flags. Zero or a combination of values from FfxApiUiCompositionFlags.
uint32_t composeOnGameQueue; ///< Nonzero when UI composition runs on the game queue. Adds the composition output image.
uint32_t reserved; ///< Must be zero.
struct FfxApiEffectMemoryUsage* gpuMemoryUsageFrameGenerationSwapchain; ///< Output. totalUsageInBytes is the sum of the swapchain images. aliasableUsageInBytes is zero because those images stay live together.
};
/// Function to get the number of presents. This is useful when using frame interpolation
typedef uint64_t(*PFN_getLastPresentCountFFXAPI)(VkSwapchainKHR);
/// FFX API specific functions to create and destroy a swapchain
typedef VkResult(*PFN_vkCreateSwapchainFFXAPI)(VkDevice device, const VkSwapchainCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain, void* pContext);
typedef void(*PFN_vkDestroySwapchainFFXAPI)(VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks* pAllocator, void* pContext);
#define FFX_API_QUERY_DESC_TYPE_FGSWAPCHAIN_FUNCTIONS_VK FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN_VK, 0x05)
struct ffxQueryDescSwapchainReplacementFunctionsVK
{
ffxQueryDescHeader header;
PFN_vkCreateSwapchainFFXAPI pOutCreateSwapchainFFXAPI; ///< Replacement of vkCreateSwapchainKHR. Recreates the swapchain in place (for example on resize or vsync toggle); pContext and the returned handle are the existing frame generation swapchain.
PFN_vkDestroySwapchainFFXAPI pOutDestroySwapchainFFXAPI; ///< Replacement of vkDestroySwapchainKHR. Must be called after the swapchain context is destroyed; pContext is the frame generation swapchain. The handle is invalid afterwards.
PFN_vkGetSwapchainImagesKHR pOutGetSwapchainImagesKHR; ///< Replacement of vkGetSwapchainImagesKHR.
PFN_vkAcquireNextImageKHR pOutAcquireNextImageKHR; ///< Replacement of vkAcquireNextImageKHR.
PFN_vkQueuePresentKHR pOutQueuePresentKHR; ///< Replacement of vkQueuePresentKHR.
PFN_vkSetHdrMetadataEXT pOutSetHdrMetadataEXT; ///< Replacement of vkSetHdrMetadataEXT.
PFN_getLastPresentCountFFXAPI pOutGetLastPresentCountFFXAPI; ///< Additional function to get the number of times present has been called since the swapchain creation.
};
#define FFX_API_CREATE_CONTEXT_DESC_TYPE_FGSWAPCHAIN_MODE_VK FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN_VK, 0x10)
struct ffxCreateContextDescFrameGenerationSwapChainModeVK
{
ffxCreateContextDescHeader header;
bool composeOnPresentQueue; ///< flags indicating that composition will happen on the present queue
};
#define FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATIONSWAPCHAIN_VERSION_VK FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN_VK, 0x0a)
struct ffxCreateContextDescFrameGenerationSwapChainVersionVK
{
ffxCreateContextDescHeader header; ///< Description header for frame generation swapchain version context creation.
uint32_t version; ///< The API version the application was built against. This must be set to FFX_FRAMEGENERATION_SWAPCHAIN_VK_VERSION.
};

View File

@@ -38,9 +38,8 @@ extern "C" void vk_ffx4_release(void) {}
#include <string.h>
#include <wchar.h>
#include "third_party/ffx_sdk/api/include/ffx_api.h"
#include "third_party/ffx_sdk/api/include/vk/ffx_api_vk.h"
#include "third_party/ffx_sdk/upscalers/include/ffx_upscale.h"
#include "vk_ffx4_api.h"
extern "C" {
#include "cfg.h"
@@ -88,19 +87,20 @@ int file_there(const char *path) {
return attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY);
}
// Loads the loader and asks it for the device's upscaler versions. Under g_lock, once the device exists.
int probe(void) {
int g_loader = -1; // under g_lock: -1 not tried, 0 the loader is not there or does not load, 1 its entry points are in A
// Loads the SDK's loader from beside the executable. Under g_lock, once the device exists (-1 before).
int load(void) {
if (g_loader >= 0) return g_loader;
const VkCtx *c = vk_ctx();
if (!c || !c->device) return -1; // asked before the device: again later
char loader[MAX_PATH], upscaler[MAX_PATH];
if (!beside_exe("amd_fidelityfx_loader_vk.dll", loader, sizeof loader) ||
!beside_exe("amd_fidelityfx_upscaler_vk.dll", upscaler, sizeof upscaler) || !file_there(loader) ||
!file_there(upscaler))
return 0; // not shipped beside this build: nothing is said, FSR 3.1 is what the menu offers
char loader[MAX_PATH];
if (!beside_exe("amd_fidelityfx_loader_vk.dll", loader, sizeof loader) || !file_there(loader))
return g_loader = 0; // not shipped beside this build: nothing is said, FSR 3.1 is what the menu offers
HMODULE m = LoadLibraryA(loader);
if (!m) {
rt_log("ffx4: %s does not load (error %lu): FSR 4 off", loader, (unsigned long)GetLastError());
return 0;
return g_loader = 0;
}
A.create = (PfnFfxCreateContext)(void *)GetProcAddress(m, "ffxCreateContext");
A.destroy = (PfnFfxDestroyContext)(void *)GetProcAddress(m, "ffxDestroyContext");
@@ -109,36 +109,52 @@ int probe(void) {
A.dispatch = (PfnFfxDispatch)(void *)GetProcAddress(m, "ffxDispatch");
if (!A.create || !A.destroy || !A.configure || !A.query || !A.dispatch) {
rt_log("ffx4: amd_fidelityfx_loader_vk.dll lacks the API entry points: FSR 4 off");
return 0;
return g_loader = 0;
}
return g_loader = 1;
}
// The versions the loader lists for one kind of context on this device, in its order: their count, at most cap.
// Under g_lock, the loader loaded.
int versions(uint64_t type, uint64_t *ids, const char **names, int cap) {
const VkCtx *c = vk_ctx();
VkDeviceContext dc = {c->device, c->phys, vkGetDeviceProcAddr};
uint64_t ids[16] = {}, n = 0;
const char *names[16] = {};
uint64_t n = 0;
ffxQueryDescGetVersions q = {};
q.header.type = FFX_API_QUERY_DESC_TYPE_GET_VERSIONS;
q.createDescType = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE;
q.createDescType = type;
q.device = &dc;
q.outputCount = &n;
ffxReturnCode_t r = A.query(nullptr, &q.header); // the count
if (r == FFX_API_RETURN_OK && n) {
if (n > 16) n = 16;
q.versionIds = ids;
q.versionNames = names;
r = A.query(nullptr, &q.header);
}
if (r != FFX_API_RETURN_OK || !n) {
rt_log("ffx4: the FSR SDK's loader lists no upscaler for this device (%u): FSR 4 off", (unsigned)r);
if (A.query(nullptr, &q.header) != FFX_API_RETURN_OK || !n) return 0; // the count
if (n > (uint64_t)cap) n = (uint64_t)cap;
q.versionIds = ids;
q.versionNames = names;
if (A.query(nullptr, &q.header) != FFX_API_RETURN_OK) return 0;
return (int)n;
}
// Asks the loader for the device's upscaler versions. Under g_lock; -1 before the device exists.
int probe(void) {
int loaded = load();
if (loaded <= 0) return loaded;
char upscaler[MAX_PATH];
if (!beside_exe("amd_fidelityfx_upscaler_vk.dll", upscaler, sizeof upscaler) || !file_there(upscaler)) return 0;
uint64_t ids[16] = {};
const char *names[16] = {};
int n = versions(FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE, ids, names, 16);
if (!n) {
rt_log("ffx4: the FSR SDK's loader lists no upscaler for this device: FSR 4 off");
return 0;
}
char list[200] = "";
size_t len = 0;
int best = -1, best_major = 0;
for (uint64_t i = 0; i < n; i++) {
for (int i = 0; i < n; i++) {
const char *nm = names[i] ? names[i] : "?";
len += (size_t)snprintf(list + len, sizeof list - len, "%s%s", i ? ", " : "", nm);
if (len >= sizeof list) len = sizeof list - 1;
int major = atoi(nm); // "4.1.2", and "4.1.2 *" for a version the driver brings
if (major >= 4 && major > best_major) best = (int)i, best_major = major;
if (major >= 4 && major > best_major) best = i, best_major = major;
}
if (best < 0) {
rt_log("ffx4: upscaler versions for this device: %s; none is FSR 4 (it needs a Radeon RX 6000, 7000 or 9000 card "
@@ -310,6 +326,31 @@ extern "C" int vk_ffx4_upscale(VkCommandBuffer cb, const VkFfxImage *color, cons
return 1;
}
// The loader's calls for vk_fg4.cpp (vk_ffx4_api.h).
extern "C" int vk_ffx4_api_file(const char *name) {
char path[MAX_PATH];
return beside_exe(name, path, sizeof path) && file_there(path);
}
extern "C" int vk_ffx4_api_versions(uint64_t create_type, uint64_t *ids, const char **names, int cap) {
std::lock_guard<std::mutex> hold(g_lock);
return load() > 0 ? versions(create_type, ids, names, cap) : 0;
}
extern "C" ffxReturnCode_t vk_ffx4_api_create(ffxContext *ctx, ffxCreateContextDescHeader *desc) {
return A.create ? A.create(ctx, desc, nullptr) : FFX_API_RETURN_ERROR;
}
extern "C" ffxReturnCode_t vk_ffx4_api_destroy(ffxContext *ctx) {
return A.destroy ? A.destroy(ctx, nullptr) : FFX_API_RETURN_ERROR;
}
extern "C" ffxReturnCode_t vk_ffx4_api_configure(ffxContext *ctx, const ffxConfigureDescHeader *desc) {
return A.configure ? A.configure(ctx, desc) : FFX_API_RETURN_ERROR;
}
extern "C" ffxReturnCode_t vk_ffx4_api_query(ffxContext *ctx, ffxQueryDescHeader *desc) {
return A.query ? A.query(ctx, desc) : FFX_API_RETURN_ERROR;
}
extern "C" ffxReturnCode_t vk_ffx4_api_dispatch(ffxContext *ctx, const ffxDispatchDescHeader *desc) {
return A.dispatch ? A.dispatch(ctx, desc) : FFX_API_RETURN_ERROR;
}
extern "C" void vk_ffx4_release(void) {
if (!A.destroy) return;
for (Up &u : U)

22
runtime/vk_ffx4_api.h Normal file
View File

@@ -0,0 +1,22 @@
// The entry points of AMD's FSR SDK 2.4 loader (amd_fidelityfx_loader_vk.dll, which vk_ffx4.cpp loads), for
// vk_fg4.cpp. C++ and Windows only, and never in one file with vk_ffx.h's API section: the two sets of headers name
// the same types with other layouts.
#pragma once
#include <stdint.h>
#include "third_party/ffx_sdk/api/include/ffx_api.h"
#include "third_party/ffx_sdk/api/include/vk/ffx_api_vk.h"
extern "C" {
// The versions the loader lists for one kind of context (a FFX_API_CREATE_CONTEXT_DESC_TYPE_*) on this device, in
// its order, newest first: their count, at most cap. The names are the library's memory. 0 without the loader, before
// the device exists, or when it lists none (an effect whose library is not beside the executable).
int vk_ffx4_api_versions(uint64_t create_type, uint64_t *ids, const char **names, int cap);
int vk_ffx4_api_file(const char *name); // a file of that name is next to the executable (one of the SDK's libraries)
// The API's calls (0 = FFX_API_RETURN_OK), valid once vk_ffx4_api_versions returned a count.
ffxReturnCode_t vk_ffx4_api_create(ffxContext *ctx, ffxCreateContextDescHeader *desc);
ffxReturnCode_t vk_ffx4_api_destroy(ffxContext *ctx);
ffxReturnCode_t vk_ffx4_api_configure(ffxContext *ctx, const ffxConfigureDescHeader *desc);
ffxReturnCode_t vk_ffx4_api_query(ffxContext *ctx, ffxQueryDescHeader *desc);
ffxReturnCode_t vk_ffx4_api_dispatch(ffxContext *ctx, const ffxDispatchDescHeader *desc);
}

View File

@@ -43,7 +43,7 @@ struct {
ffxQueryDescSwapchainReplacementFunctionsVK fn = {};
uint64_t frame = 0;
int failed = 0;
uint64_t generated = 0, paused = 0, no_inputs = 0, hudless = 0;
uint64_t generated = 0, paused = 0, no_inputs = 0, hudless = 0, no_hudless = 0;
int on = 0; // the last configured frame had generation on (vk_fg_generating)
} F;
@@ -85,10 +85,23 @@ void fail(const char *what, unsigned r) {
// NVIDIA's driver when the menu switched from NVIDIA's frame generation to AMD's. That switch waits for a restart,
// as the menu says (menu.cpp row_framegen). Not after a failure (fail): the present code then takes AMD's swapchain
// down as it does for a switch in the menu, and a plain one follows.
//
// Two libraries can make the swapchain: the FSR 3.1 library, here, or AMD's FSR SDK 2.4 (vk_fg4.cpp) where the
// setting asks for it while FSR 4 is the upscaler (use4). The swapchain in place is wanted only while its library is
// still the one to use: a change of that setting or of the upscaler between FSR 3.1 and FSR 4, or a failure of the SDK
// 2.4 path, takes it down as switching frame generation off does, and the next present makes it with the other library.
static int use4(void) { // NX2NAVI_FSR4_FG (default 0, live): 1 takes the SDK 2.4's frame generation with FSR 4
return cfg_bool("NX2NAVI_FSR4_FG") && vk_ffx_fsr4() && !vk_fg4_failed() && vk_fg4_available();
}
extern "C" int vk_fg_wanted(void) {
if (F.failed || strcmp(cfg_value("NX2NAVI_FRAMEGEN"), "fsr3") || !cfg_bool("NX2NAVI_DLAA") || !vk_dlaa_wanted() ||
vk_dlssg_dpa())
if (strcmp(cfg_value("NX2NAVI_FRAMEGEN"), "fsr3") || !cfg_bool("NX2NAVI_DLAA") || !vk_dlaa_wanted() || vk_dlssg_dpa())
return 0;
int four = use4();
if (vk_fg4_active()) return four;
if (F.sc) return !F.failed && !four;
if (four) return 1;
if (F.failed) return 0;
if (vk_ffx_framegen_available()) return 1;
static int told;
if (!__atomic_exchange_n(&told, 1, __ATOMIC_RELAXED))
@@ -99,16 +112,28 @@ extern "C" int vk_fg_wanted(void) {
// Always the three queues: the setting can be switched on during a run, and idle queues cost nothing.
extern "C" uint32_t vk_fg_queue_count(void) { return 3; }
extern "C" void vk_fg_queues(VkQueue present, VkQueue acquire) { F.present = present, F.acquire = acquire; }
extern "C" void vk_fg_queues(VkQueue present, VkQueue acquire) {
F.present = present, F.acquire = acquire;
vk_fg4_queues(present, acquire);
}
// AMD's swapchain is in place: the window's swapchain handle is AMD's object, and every swapchain call has to go
// through AMD's functions. That holds after a failure too, until the present code has replaced the swapchain
// (vk_fg_wanted): the plain Vulkan calls must never get that handle.
extern "C" int vk_fg_active(void) { return F.sc != nullptr; }
extern "C" int vk_fg_active(void) { return F.sc != nullptr || vk_fg4_active(); }
extern "C" int vk_fg_generating(void) { return vk_fg_active() && !F.failed && __atomic_load_n(&F.on, __ATOMIC_RELAXED); }
extern "C" int vk_fg_generating(void) {
if (vk_fg4_active()) return vk_fg4_generating();
return F.sc && !F.failed && __atomic_load_n(&F.on, __ATOMIC_RELAXED);
}
extern "C" VkResult vk_fg_create_swapchain(const VkSwapchainCreateInfoKHR *ci, VkSwapchainKHR *sc) {
if (vk_fg4_active()) return vk_fg4_create_swapchain(ci, sc);
if (!F.sc && use4()) {
VkResult r = vk_fg4_create_swapchain(ci, sc);
if (r == VK_SUCCESS || vk_fg4_active()) return r;
// it failed before its swapchain existed and said so (use4 is 0 from here on): the FSR 3.1 library's below
}
if (F.sc) return F.fn.pOutCreateSwapchainFFXAPI(vk_ctx()->device, ci, nullptr, sc, F.sc);
if (!vk_fg_wanted() || F.failed || !F.present || !vk_ffx_load()) return VK_ERROR_FEATURE_NOT_PRESENT;
const VkCtx *c = vk_ctx();
@@ -142,28 +167,34 @@ extern "C" VkResult vk_fg_create_swapchain(const VkSwapchainCreateInfoKHR *ci, V
}
extern "C" void vk_fg_destroy_swapchain(VkSwapchainKHR sc) {
if (vk_fg4_active()) return vk_fg4_destroy_swapchain(sc);
if (F.sc && sc) F.fn.pOutDestroySwapchainFFXAPI(vk_ctx()->device, sc, nullptr, F.sc);
}
extern "C" VkResult vk_fg_get_images(VkSwapchainKHR sc, uint32_t *n, VkImage *img) {
if (vk_fg4_active()) return vk_fg4_get_images(sc, n, img);
return F.fn.pOutGetSwapchainImagesKHR(vk_ctx()->device, sc, n, img);
}
extern "C" VkResult vk_fg_acquire(VkSwapchainKHR sc, uint64_t timeout, VkSemaphore sem, uint32_t *idx) {
if (vk_fg4_active()) return vk_fg4_acquire(sc, timeout, sem, idx);
return F.fn.pOutAcquireNextImageKHR(vk_ctx()->device, sc, timeout, sem, VK_NULL_HANDLE, idx);
}
extern "C" VkResult vk_fg_present(const VkPresentInfoKHR *pi) {
if (vk_fg4_active()) return vk_fg4_present(pi);
return F.fn.pOutQueuePresentKHR(vk_ctx()->gfx_queue, pi);
}
extern "C" uint64_t vk_fg_presents(VkSwapchainKHR sc) {
return vk_fg_active() && F.fn.pOutGetLastPresentCountFFXAPI && sc ? F.fn.pOutGetLastPresentCountFFXAPI(sc) : 0;
if (vk_fg4_active()) return vk_fg4_presents(sc);
return F.sc && F.fn.pOutGetLastPresentCountFFXAPI && sc ? F.fn.pOutGetLastPresentCountFFXAPI(sc) : 0;
}
// The HDR10 metadata goes to AMD's swapchain through its own call (vk_hdr.c vk_hdr_metadata).
extern "C" PFN_vkSetHdrMetadataEXT vk_fg_hdr_metadata_fn(void) {
return vk_fg_active() ? F.fn.pOutSetHdrMetadataEXT : nullptr;
if (vk_fg4_active()) return vk_fg4_hdr_metadata_fn();
return F.sc ? F.fn.pOutSetHdrMetadataEXT : nullptr;
}
// An HDR10 swapchain (10-bit PQ or 16-bit float): frame generation is told its inputs are HDR.
@@ -175,7 +206,8 @@ static bool hdr_format(VkFormat f) {
extern "C" void vk_fg_frame(VkCommandBuffer cb, VkSwapchainKHR sc, VkFormat fmt, VkExtent2D ext, VkImage hudless,
VkImageView hudless_view, const int pic[4]) {
(void)hudless_view;
if (!vk_fg_active() || F.failed) return;
if (vk_fg4_active()) return vk_fg4_frame(cb, sc, fmt, ext, hudless, pic);
if (!F.sc || F.failed) return;
vk_dlaa_hudless_wanted = 1; // the upscaling pass keeps a copy of the picture before the HUD (vk_dlaa.c hudless)
const VkCtx *c = vk_ctx();
VkFfxImage depth, mv;
@@ -226,9 +258,12 @@ extern "C" void vk_fg_frame(VkCommandBuffer cb, VkSwapchainKHR sc, VkFormat fmt,
depth.height, F.fg_max.width, F.fg_max.height);
have = 0;
}
int on = have && !menu_is_open();
// Only with the picture without the HUD: generated from the presented picture alone, the HUD would be warped along
// with the scene behind it and trail when the camera turns. A frame without that picture is shown as it is.
int on = have && hudless && !menu_is_open();
__atomic_store_n(&F.on, on, __ATOMIC_RELAXED);
if (!have) F.no_inputs++;
else if (!hudless) F.no_hudless++;
else if (!on) F.paused++;
F.frame++;
ffxConfigureDescFrameGeneration cfg = {};
@@ -242,7 +277,7 @@ extern "C" void vk_fg_frame(VkCommandBuffer cb, VkSwapchainKHR sc, VkFormat fmt,
// whole swapchain warped the picture's edges into the bars
cfg.generationRect = pic && pic[2] > 0 && pic[3] > 0 ? FfxApiRect2D{pic[0], pic[1], pic[2], pic[3]}
: FfxApiRect2D{0, 0, (int32_t)ext.width, (int32_t)ext.height};
if (on && hudless) {
if (on) {
VkFfxImage h = {hudless, fmt, ext.width, ext.height, 1};
cfg.HUDLessColor = image(&h);
F.hudless++;
@@ -273,11 +308,13 @@ extern "C" void vk_fg_frame(VkCommandBuffer cb, VkSwapchainKHR sc, VkFormat fmt,
if (r != FFX_API_RETURN_OK) return fail("frame generation preparation", r);
if (F.frame == 2 || F.frame % 3600 == 0)
rt_log("fg: %llu frames presented, %llu generated (%llu with the HUD kept out); paused %llu (menu), %llu without depth "
"and motion", (unsigned long long)F.frame, (unsigned long long)F.generated, (unsigned long long)F.hudless,
(unsigned long long)F.paused, (unsigned long long)F.no_inputs);
"and motion, %llu without the HUD-less picture", (unsigned long long)F.frame, (unsigned long long)F.generated,
(unsigned long long)F.hudless, (unsigned long long)F.paused, (unsigned long long)F.no_inputs,
(unsigned long long)F.no_hudless);
}
extern "C" void vk_fg_release(void) {
vk_fg4_release();
if (F.fg) vk_ffx_destroy(&F.fg), F.fg = nullptr;
if (F.sc) vk_ffx_destroy(&F.sc), F.sc = nullptr;
__atomic_store_n(&F.on, 0, __ATOMIC_RELAXED);

View File

@@ -26,3 +26,20 @@ void vk_fg_frame(VkCommandBuffer cb, VkSwapchainKHR sc, VkFormat fmt, VkExtent2D
uint64_t vk_fg_presents(VkSwapchainKHR sc); // frames the swapchain presented, generated ones included (0: off)
PFN_vkSetHdrMetadataEXT vk_fg_hdr_metadata_fn(void); // AMD's replacement for vkSetHdrMetadataEXT while active, else NULL
void vk_fg_release(void);
// The same through AMD's FSR SDK 2.4 (vk_fg4.cpp), for vk_fg.cpp alone: it takes this path while FSR 4 is the upscaler,
// and every call above goes to these while this path holds the swapchain.
int vk_fg4_available(void); // its libraries are beside the executable and list a frame generation for the device
int vk_fg4_failed(void); // it failed in this run: FSR 3.1's library generates the frames
int vk_fg4_active(void);
int vk_fg4_generating(void);
void vk_fg4_queues(VkQueue present, VkQueue acquire);
VkResult vk_fg4_create_swapchain(const VkSwapchainCreateInfoKHR *ci, VkSwapchainKHR *sc);
void vk_fg4_destroy_swapchain(VkSwapchainKHR sc);
VkResult vk_fg4_get_images(VkSwapchainKHR sc, uint32_t *n, VkImage *img);
VkResult vk_fg4_acquire(VkSwapchainKHR sc, uint64_t timeout, VkSemaphore sem, uint32_t *idx);
VkResult vk_fg4_present(const VkPresentInfoKHR *pi);
void vk_fg4_frame(VkCommandBuffer cb, VkSwapchainKHR sc, VkFormat fmt, VkExtent2D ext, VkImage hudless, const int pic[4]);
uint64_t vk_fg4_presents(VkSwapchainKHR sc);
PFN_vkSetHdrMetadataEXT vk_fg4_hdr_metadata_fn(void);
void vk_fg4_release(void);

427
runtime/vk_fg4.cpp Normal file
View File

@@ -0,0 +1,427 @@
// Frame generation through AMD's FSR SDK 2.4: amd_fidelityfx_loader_vk.dll (vk_ffx4.cpp loads it) with
// amd_fidelityfx_framegeneration_vk.dll next to the executable. vk_fg.cpp takes this path instead of the FSR 3.1
// library's where the setting asks for it while FSR 4 is the upscaler (vk_fg.cpp use4, NX2NAVI_FSR4_FG), and its
// functions here are called by vk_fg.cpp alone. The
// loader picks the frame generation and the swapchain for the card: the machine-learned FSR Frame Generation 4.x on
// Radeon RX 9000 cards with Windows 11, FSR Frame Generation 3.1.x on every other. The log names both versions.
//
// What happens each frame is what vk_fg.cpp does (the configuration, then the preparation from the frame's depth and
// motion vectors, then the generation from the swapchain's callback at present, the order FSR Frame Generation 4
// requires), in this SDK's layouts and with what it adds: the camera's position and axes in the preparation, which
// FSR Frame Generation 4 needs, and the version each context is made for.
//
// The swapchain's life differs from the FSR 3.1 library's:
// - A new size remakes the swapchain in place: its create function takes the swapchain's own handle, and the handle
// stays. vk_fg4_destroy_swapchain therefore does nothing while the swapchain context lives.
// - Taking it down (vk_fg4_release) waits for its presents, destroys the frame generation context, then the swapchain
// context, and calls its destroy function last, as the SDK asks.
#define VK_FFX_NO_API // vk_ffx.h: without the API declarations of the other set of headers
#ifndef _WIN32
// AMD publishes the SDK's libraries for Windows only: in any other build this path is never available.
extern "C" {
#include "vk.h"
#include "vk_ffx.h"
#include "vk_fg.h"
}
extern "C" int vk_fg4_available(void) { return 0; }
extern "C" int vk_fg4_failed(void) { return 0; }
extern "C" int vk_fg4_active(void) { return 0; }
extern "C" int vk_fg4_generating(void) { return 0; }
extern "C" void vk_fg4_queues(VkQueue, VkQueue) {}
extern "C" VkResult vk_fg4_create_swapchain(const VkSwapchainCreateInfoKHR *, VkSwapchainKHR *) {
return VK_ERROR_FEATURE_NOT_PRESENT;
}
extern "C" void vk_fg4_destroy_swapchain(VkSwapchainKHR) {}
extern "C" VkResult vk_fg4_get_images(VkSwapchainKHR, uint32_t *, VkImage *) { return VK_ERROR_FEATURE_NOT_PRESENT; }
extern "C" VkResult vk_fg4_acquire(VkSwapchainKHR, uint64_t, VkSemaphore, uint32_t *) { return VK_ERROR_FEATURE_NOT_PRESENT; }
extern "C" VkResult vk_fg4_present(const VkPresentInfoKHR *) { return VK_ERROR_FEATURE_NOT_PRESENT; }
extern "C" uint64_t vk_fg4_presents(VkSwapchainKHR) { return 0; }
extern "C" PFN_vkSetHdrMetadataEXT vk_fg4_hdr_metadata_fn(void) { return nullptr; }
extern "C" void vk_fg4_frame(VkCommandBuffer, VkSwapchainKHR, VkFormat, VkExtent2D, VkImage, const int *) {}
extern "C" void vk_fg4_release(void) {}
#else
#include <windows.h>
#include <mutex>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include "third_party/ffx_sdk/framegeneration/include/ffx_framegeneration.h"
#include "third_party/ffx_sdk/framegeneration/include/vk/ffx_api_framegeneration_vk.h"
#include "vk_ffx4_api.h"
extern "C" {
#include "cfg.h"
#include "host.h"
#include "platform/platform.h"
#include "vk.h"
#include "vk_dlaa.h"
#include "vk_ffx.h"
#include "vk_fg.h"
#include "ui/menu.h"
}
namespace {
struct {
VkQueue present = VK_NULL_HANDLE, acquire = VK_NULL_HANDLE;
ffxContext sc = nullptr; // the swapchain context
ffxContext fg = nullptr; // the frame generation context
VkSwapchainKHR handle = VK_NULL_HANDLE; // the swapchain the context made: remade in place, destroyed after the context
VkExtent2D fg_ext = {0, 0};
VkExtent2D fg_max = {0, 0}; // the context's largest render size
VkFormat fg_fmt = VK_FORMAT_UNDEFINED;
ffxQueryDescSwapchainReplacementFunctionsVK fn = {};
// what the contexts were made from: the API asks that it stays until the context is destroyed
ffxCreateContextDescFrameGenerationSwapChainVK sc_desc = {};
ffxCreateContextDescFrameGenerationSwapChainVersionVK sc_ver = {};
ffxOverrideVersion sc_pin = {};
ffxCreateContextDescFrameGeneration fg_desc = {};
ffxCreateBackendVKDesc fg_be = {};
ffxCreateContextDescFrameGenerationHudless fg_hl = {};
ffxCreateContextDescFrameGenerationVersion fg_ver = {};
ffxOverrideVersion fg_pin = {};
uint64_t frame = 0;
int failed = 0;
uint64_t generated = 0, paused = 0, no_inputs = 0, hudless = 0, no_hudless = 0;
int on = 0; // the last configured frame had generation on (vk_fg4_generating)
} F;
std::mutex g_lock; // the probe: the menu asks from its thread, the present from its own
int g_avail = -1; // -1 not asked yet; 0 this path does not run here; 1 it does
char g_sc_list[96]; // the swapchain versions the loader listed, for the log
uint64_t g_fg_pin, g_sc_pin; // NX2NAVI_FG4_VERSION, NX2NAVI_FG4_SWAPCHAIN (tests): the version asked for by its id; 0 the loader's choice
VkResult submit_game(uint32_t n, const VkSubmitInfo *si, VkFence fence) {
vk_queue_lock(); // the game's queue is shared with the recorder and the present path
VkResult r = vkQueueSubmit(vk_ctx()->gfx_queue, n, si, fence);
vk_queue_unlock();
return r;
}
ffxReturnCode_t generate(ffxDispatchDescFrameGeneration *d, void *user) {
F.generated++;
return vk_ffx4_api_dispatch((ffxContext *)user, &d->header);
}
FfxApiResource image(const VkFfxImage *i) {
VkImageCreateInfo ci = {};
ci.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
ci.imageType = VK_IMAGE_TYPE_2D;
ci.format = i->format;
ci.extent = {i->width, i->height, 1};
ci.mipLevels = 1;
ci.arrayLayers = 1;
return ffxApiGetResourceVK((void *)i->image, ffxApiGetImageResourceDescriptionVK(i->image, ci, 0),
FFX_API_RESOURCE_STATE_UNORDERED_ACCESS);
}
// After this the FSR 3.1 library generates the frames: vk_fg.cpp's vk_fg_wanted stops wanting this swapchain, the
// present code takes it down as it does for a switch in the menu, and the next one is made by the other path.
void fail(const char *what, unsigned r) {
rt_log("fg4: %s failed (%u): the FSR SDK's frame generation is off for this run, FSR 3.1's takes its place", what, r);
F.failed = 1;
}
// The list the loader gives for one kind of context, as text, and the id of the version a test asks for by the start
// of its name (0: none asked, or not listed).
int list_versions(uint64_t type, const char *want, char *out, size_t size, uint64_t *pin) {
uint64_t ids[8] = {};
const char *names[8] = {};
int n = vk_ffx4_api_versions(type, ids, names, 8);
size_t len = 0;
out[0] = 0;
for (int i = 0; i < n; i++) {
const char *nm = names[i] ? names[i] : "?";
len += (size_t)snprintf(out + len, size - len, "%s%s", i ? ", " : "", nm);
if (len >= size) len = size - 1;
if (want && *want && !*pin && !strncmp(nm, want, strlen(want))) *pin = ids[i];
}
return n;
}
// The version a context runs, as its provider names it ("3.1.6"); "?" when it does not say.
const char *provider(ffxContext *ctx) {
ffxQueryGetProviderVersion v = {};
v.header.type = FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION;
vk_ffx4_api_query(ctx, &v.header);
return v.versionName ? v.versionName : "?";
}
// An HDR10 swapchain (10-bit PQ or 16-bit float): frame generation is told its inputs are HDR.
bool hdr_format(VkFormat f) {
return f == VK_FORMAT_A2B10G10R10_UNORM_PACK32 || f == VK_FORMAT_A2R10G10B10_UNORM_PACK32 ||
f == VK_FORMAT_R16G16B16A16_SFLOAT;
}
// The swapchain's own presents are done before it is remade or taken down.
void wait_presents(void) {
ffxDispatchDescFrameGenerationSwapChainWaitForPresentsVK w = {};
w.header.type = FFX_API_DISPATCH_DESC_TYPE_FGSWAPCHAIN_WAIT_FOR_PRESENTS_VK;
vk_ffx4_api_dispatch(&F.sc, &w.header);
}
} // namespace
// The loader and its frame generation library are beside the executable and list a frame generation and a swapchain
// for the device. Asked once, with the device; 0 before it exists.
extern "C" int vk_fg4_available(void) {
int a = __atomic_load_n(&g_avail, __ATOMIC_ACQUIRE); // asked several times a frame
if (a >= 0) return a;
std::lock_guard<std::mutex> hold(g_lock);
if (g_avail >= 0) return g_avail;
const VkCtx *c = vk_ctx();
if (!c || !c->device) return 0;
if (!vk_ffx4_api_file("amd_fidelityfx_framegeneration_vk.dll")) { // not shipped beside this build
__atomic_store_n(&g_avail, 0, __ATOMIC_RELEASE);
return 0;
}
char fg[96];
char *sc = g_sc_list;
int nf = list_versions(FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION, cfg_get("NX2NAVI_FG4_VERSION"), fg, sizeof fg, &g_fg_pin);
int ns = list_versions(FFX_API_CREATE_CONTEXT_DESC_TYPE_FGSWAPCHAIN_VK, cfg_get("NX2NAVI_FG4_SWAPCHAIN"), sc, sizeof g_sc_list,
&g_sc_pin);
a = nf > 0 && ns > 0;
__atomic_store_n(&g_avail, a, __ATOMIC_RELEASE);
if (a) rt_log("fg4: the FSR SDK lists for this device: frame generation %s; its swapchain %s", fg, sc);
else rt_log("fg4: the FSR SDK lists no frame generation for this device: FSR 3.1's library generates the frames");
return a;
}
extern "C" int vk_fg4_failed(void) { return F.failed; }
extern "C" void vk_fg4_queues(VkQueue present, VkQueue acquire) { F.present = present, F.acquire = acquire; }
// The SDK's swapchain is in place: the window's swapchain handle is its object, and every swapchain call has to go
// through its functions, after a failure too, until the present code has taken it down.
extern "C" int vk_fg4_active(void) { return F.sc != nullptr; }
extern "C" int vk_fg4_generating(void) { return vk_fg4_active() && !F.failed && __atomic_load_n(&F.on, __ATOMIC_RELAXED); }
extern "C" VkResult vk_fg4_create_swapchain(const VkSwapchainCreateInfoKHR *ci, VkSwapchainKHR *sc) {
const VkCtx *c = vk_ctx();
if (F.sc) { // a new size: the swapchain is remade in place and keeps its handle
wait_presents();
VkSwapchainCreateInfoKHR in = *ci;
in.oldSwapchain = VK_NULL_HANDLE;
VkSwapchainKHR h = F.handle;
VkResult r = F.fn.pOutCreateSwapchainFFXAPI(c->device, &in, nullptr, &h, (void *)F.handle);
if (r == VK_SUCCESS) *sc = F.handle = h;
return r;
}
if (F.failed || !F.present || !vk_fg4_available()) return VK_ERROR_FEATURE_NOT_PRESENT;
F.handle = VK_NULL_HANDLE;
F.sc_desc = {};
F.sc_desc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FGSWAPCHAIN_VK;
F.sc_desc.physicalDevice = c->phys;
F.sc_desc.device = c->device;
F.sc_desc.swapchain = &F.handle;
F.sc_desc.createInfo = *ci;
F.sc_desc.createInfo.oldSwapchain = VK_NULL_HANDLE;
F.sc_desc.gameQueue = {c->gfx_queue, c->gfx_family, submit_game};
F.sc_desc.asyncComputeQueue = {VK_NULL_HANDLE, c->gfx_family, nullptr};
F.sc_desc.presentQueue = {F.present, c->gfx_family, nullptr};
F.sc_desc.imageAcquireQueue = {F.acquire, c->gfx_family, nullptr};
// the version this code was built against, which the SDK asks for with every context, and a test's version
F.sc_ver = {};
F.sc_ver.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATIONSWAPCHAIN_VERSION_VK;
F.sc_ver.version = FFX_FRAMEGENERATION_SWAPCHAIN_VK_VERSION;
F.sc_desc.header.pNext = &F.sc_ver.header;
if (g_sc_pin) {
F.sc_pin = {};
F.sc_pin.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION;
F.sc_pin.versionId = g_sc_pin;
F.sc_ver.header.pNext = &F.sc_pin.header;
}
ffxReturnCode_t r = vk_ffx4_api_create(&F.sc, &F.sc_desc.header);
if (r != FFX_API_RETURN_OK || !F.sc) {
F.sc = nullptr, F.handle = VK_NULL_HANDLE;
fail("the frame generation swapchain", r);
return VK_ERROR_INITIALIZATION_FAILED;
}
F.fn = {};
F.fn.header.type = FFX_API_QUERY_DESC_TYPE_FGSWAPCHAIN_FUNCTIONS_VK;
// The swapchain the driver brings (listed with a star, the loader's choice where it is listed) hands out no destroy
// function: its context's destruction is the swapchain's too (vk_fg4_release).
if (vk_ffx4_api_query(&F.sc, &F.fn.header) != FFX_API_RETURN_OK || !F.fn.pOutQueuePresentKHR || !F.fn.pOutCreateSwapchainFFXAPI ||
!F.fn.pOutGetSwapchainImagesKHR || !F.fn.pOutAcquireNextImageKHR || !F.handle) {
VkSwapchainKHR h = F.handle;
PFN_vkDestroySwapchainFFXAPI destroy = F.fn.pOutDestroySwapchainFFXAPI;
vk_ffx4_api_destroy(&F.sc), F.sc = nullptr, F.handle = VK_NULL_HANDLE;
if (h && destroy) destroy(c->device, h, nullptr, nullptr); // nothing was handed out yet
fail("the swapchain functions", 0);
return VK_ERROR_INITIALIZATION_FAILED;
}
*sc = F.handle;
rt_log("fg4: AMD FSR frame generation swapchain, %ux%u (%s%s)", ci->imageExtent.width, ci->imageExtent.height,
g_sc_pin ? "the version a test asked for, of " : "the loader's choice of ", g_sc_list);
return VK_SUCCESS;
}
// Nothing while the swapchain context lives: a new size remakes the swapchain in place (vk_fg4_create_swapchain), and
// vk_fg4_release destroys it after its context.
extern "C" void vk_fg4_destroy_swapchain(VkSwapchainKHR sc) { (void)sc; }
extern "C" VkResult vk_fg4_get_images(VkSwapchainKHR sc, uint32_t *n, VkImage *img) {
return F.fn.pOutGetSwapchainImagesKHR(vk_ctx()->device, sc, n, img);
}
extern "C" VkResult vk_fg4_acquire(VkSwapchainKHR sc, uint64_t timeout, VkSemaphore sem, uint32_t *idx) {
return F.fn.pOutAcquireNextImageKHR(vk_ctx()->device, sc, timeout, sem, VK_NULL_HANDLE, idx);
}
extern "C" VkResult vk_fg4_present(const VkPresentInfoKHR *pi) { return F.fn.pOutQueuePresentKHR(vk_ctx()->gfx_queue, pi); }
extern "C" uint64_t vk_fg4_presents(VkSwapchainKHR sc) {
return vk_fg4_active() && F.fn.pOutGetLastPresentCountFFXAPI && sc ? F.fn.pOutGetLastPresentCountFFXAPI(sc) : 0;
}
extern "C" PFN_vkSetHdrMetadataEXT vk_fg4_hdr_metadata_fn(void) { return vk_fg4_active() ? F.fn.pOutSetHdrMetadataEXT : nullptr; }
extern "C" void vk_fg4_frame(VkCommandBuffer cb, VkSwapchainKHR sc, VkFormat fmt, VkExtent2D ext, VkImage hudless, const int pic[4]) {
if (!vk_fg4_active() || F.failed) return;
vk_dlaa_hudless_wanted = 1; // the upscaling pass keeps a copy of the picture before the HUD (vk_dlaa.c hudless)
const VkCtx *c = vk_ctx();
VkFfxImage depth, mv;
float jx = 0, jy = 0;
int reset = 0;
int have = vk_dlaa_fg_inputs(&depth, &mv, &jx, &jy, &reset);
// The context's largest render size covers the depth and motion vectors it is given: the picture's size, which
// the scene never exceeds (vk_fg.cpp vk_fg_frame has the history of this rule).
unsigned pw, ph;
wide_picture(&pw, &ph);
VkExtent2D max_render = {ext.width > pw ? ext.width : pw, ext.height > ph ? ext.height : ph};
if (F.fg && (F.fg_ext.width != ext.width || F.fg_ext.height != ext.height || F.fg_fmt != fmt)) {
vk_queue_lock(), vkDeviceWaitIdle(c->device), vk_queue_unlock(); // a new window size: the context is made for its display size
vk_ffx4_api_destroy(&F.fg);
F.fg = nullptr;
}
if (!F.fg) {
F.fg_be = {};
F.fg_be.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_VK;
F.fg_be.deviceContext = {c->device, c->phys, vkGetDeviceProcAddr};
// the HUD-less picture has the swapchain's format (gfx_present.c hudless_record)
F.fg_hl = {};
F.fg_hl.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION_HUDLESS;
F.fg_hl.header.pNext = &F.fg_be.header;
F.fg_hl.hudlessBackBufferFormat = ffxApiGetSurfaceFormatVK(fmt);
F.fg_ver = {};
F.fg_ver.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION_VERSION;
F.fg_ver.header.pNext = &F.fg_hl.header;
F.fg_ver.version = FFX_FRAMEGENERATION_VERSION;
if (g_fg_pin) {
F.fg_pin = {};
F.fg_pin.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION;
F.fg_pin.versionId = g_fg_pin;
F.fg_be.header.pNext = &F.fg_pin.header;
}
F.fg_desc = {};
F.fg_desc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION;
F.fg_desc.header.pNext = &F.fg_ver.header;
F.fg_desc.flags = FFX_FRAMEGENERATION_ENABLE_DEPTH_INFINITE | (hdr_format(fmt) ? FFX_FRAMEGENERATION_ENABLE_HIGH_DYNAMIC_RANGE : 0);
F.fg_desc.displaySize = {ext.width, ext.height};
F.fg_desc.maxRenderSize = {max_render.width, max_render.height};
F.fg_desc.backBufferFormat = ffxApiGetSurfaceFormatVK(fmt);
uint64_t t0 = plat_now_ns();
ffxReturnCode_t r = vk_ffx4_api_create(&F.fg, &F.fg_desc.header);
if (r != FFX_API_RETURN_OK || !F.fg) return F.fg = nullptr, fail("the frame generation context", r);
F.fg_ext = ext, F.fg_fmt = fmt, F.fg_max = max_render;
FfxApiEffectMemoryUsage mem = {};
ffxQueryDescFrameGenerationGetGPUMemoryUsage mq = {};
mq.header.type = FFX_API_QUERY_DESC_TYPE_FRAMEGENERATION_GPU_MEMORY_USAGE;
mq.gpuMemoryUsageFrameGeneration = &mem;
vk_ffx4_api_query(&F.fg, &mq.header);
rt_log("fg4: AMD FSR frame generation %s for %ux%u%s, render size up to %ux%u, made in %.0f ms, %.0f MiB of video memory",
provider(&F.fg), ext.width, ext.height, hdr_format(fmt) ? " (HDR)" : "", max_render.width, max_render.height,
(double)(plat_now_ns() - t0) / 1e6, (double)mem.totalUsageInBytes / 1048576.0);
}
if (have && (depth.width > F.fg_max.width || depth.height > F.fg_max.height)) {
static int told;
if (!told++)
rt_log("fg4: a %ux%u scene is larger than the context's %ux%u: those frames are not generated", depth.width,
depth.height, F.fg_max.width, F.fg_max.height);
have = 0;
}
// Only with the picture without the HUD: generated from the presented picture alone, the HUD would be warped along
// with the scene behind it and trail when the camera turns. A frame without that picture is shown as it is.
int on = have && hudless && !menu_is_open();
__atomic_store_n(&F.on, on, __ATOMIC_RELAXED);
if (!have) F.no_inputs++;
else if (!hudless) F.no_hudless++;
else if (!on) F.paused++;
F.frame++;
ffxConfigureDescFrameGeneration cfg = {};
cfg.header.type = FFX_API_CONFIGURE_DESC_TYPE_FRAMEGENERATION;
cfg.swapChain = (void *)sc;
cfg.frameGenerationCallback = generate;
cfg.frameGenerationCallbackUserContext = &F.fg;
cfg.frameGenerationEnabled = on != 0;
cfg.allowAsyncWorkloads = false;
// the picture's rectangle: with black bars beside or below it (a window of another shape) a generation over the
// whole swapchain warped the picture's edges into the bars
cfg.generationRect = pic && pic[2] > 0 && pic[3] > 0 ? FfxApiRect2D{pic[0], pic[1], pic[2], pic[3]}
: FfxApiRect2D{0, 0, (int32_t)ext.width, (int32_t)ext.height};
if (on) {
VkFfxImage h = {hudless, fmt, ext.width, ext.height, 1};
cfg.HUDLessColor = image(&h);
F.hudless++;
}
cfg.frameID = F.frame;
ffxReturnCode_t r = vk_ffx4_api_configure(&F.fg, &cfg.header);
if (r != FFX_API_RETURN_OK) return fail("frame generation configuration", r);
if (!on) return;
ffxDispatchDescFrameGenerationPrepareV2 p = {};
p.header.type = FFX_API_DISPATCH_DESC_TYPE_FRAMEGENERATION_PREPARE_V2;
p.frameID = F.frame;
p.commandList = cb;
p.renderSize = {depth.width, depth.height};
p.jitterOffset = {jx, jy};
p.motionVectorScale = {1.0f, 1.0f}; // render pixels, current to previous (vk_dlaa.c mv_pass), as FSR takes them
static uint64_t last_ns;
uint64_t now = plat_now_ns();
double dt = last_ns ? (now - last_ns) / 1e6 : 16.67;
last_ns = now;
p.frameTimeDelta = dt > 0 && dt < 100 ? (float)dt : 16.67f;
p.reset = reset != 0;
p.cameraNear = 0.1f; // Ocarina's projection: near 0.1, far at infinity (vk_ffx.cpp)
p.cameraFar = 1e8f;
p.cameraFovAngleVertical = 1.0f;
// The game's world is taken to be in metres (its near plane is at 0.1). With 0.01 here, the value vk_fg.cpp gives
// the FSR 3.1 library, frame generation 3.1.6 drew grey translucent shapes into the sky of its frames while the
// camera turned, and with 0 it smeared them; with 1 its frames were clean in the same camera sweep.
p.viewSpaceToMetersFactor = 1.0f;
p.depth = image(&depth);
p.motionVectors = image(&mv);
// the camera in the world: the game's own, or one at the origin looking down -z on a frame without it
VkDlaaCamera cam;
if (vk_dlaa_fg_camera(&cam)) {
memcpy(p.cameraPosition, cam.pos, sizeof p.cameraPosition), memcpy(p.cameraUp, cam.up, sizeof p.cameraUp);
memcpy(p.cameraRight, cam.right, sizeof p.cameraRight), memcpy(p.cameraForward, cam.fwd, sizeof p.cameraForward);
if (cam.fov_y > 0) p.cameraFovAngleVertical = cam.fov_y;
} else {
p.cameraUp[1] = 1.0f, p.cameraRight[0] = 1.0f, p.cameraForward[2] = -1.0f;
}
r = vk_ffx4_api_dispatch(&F.fg, &p.header);
if (r != FFX_API_RETURN_OK) return fail("frame generation preparation", r);
if (F.frame == 2 || F.frame % 3600 == 0)
rt_log("fg4: %llu frames presented, %llu generated (%llu with the HUD kept out); paused %llu (menu), %llu without depth "
"and motion, %llu without the HUD-less picture", (unsigned long long)F.frame, (unsigned long long)F.generated,
(unsigned long long)F.hudless, (unsigned long long)F.paused, (unsigned long long)F.no_inputs,
(unsigned long long)F.no_hudless);
}
extern "C" void vk_fg4_release(void) {
__atomic_store_n(&F.on, 0, __ATOMIC_RELAXED);
if (!F.sc && !F.fg) return;
if (F.sc) wait_presents();
if (F.fg) vk_ffx4_api_destroy(&F.fg), F.fg = nullptr;
if (F.sc) {
VkSwapchainKHR h = F.handle;
vk_ffx4_api_destroy(&F.sc), F.sc = nullptr, F.handle = VK_NULL_HANDLE;
if (h && F.fn.pOutDestroySwapchainFFXAPI) F.fn.pOutDestroySwapchainFFXAPI(vk_ctx()->device, h, nullptr, nullptr);
}
F.fn = {};
}
#endif // _WIN32

View File

@@ -127,6 +127,7 @@ XLATE_NAME = 'nx2navi_xlate.exe' if WIN else 'nx2navi_xlate'
GENERATED_HEADERS = ('layout.h', 'patches.h', 'romfs_config.h')
RUNTIME_LIB = 'libnx2navirt.a'
FFX4_DLLS = ('amd_fidelityfx_loader_vk.dll', 'amd_fidelityfx_upscaler_vk.dll') # AMD's FSR SDK 2.4, for FSR 4
FFX4_FG_DLL = 'amd_fidelityfx_framegeneration_vk.dll' # ... and its frame generation, with the two above
RUNTIME_PATTERNS = ('*.c', '*.cpp', 'platform/*.c', 'ui/*.cpp', 'third_party/imgui/*.cpp',
'third_party/imgui/backends/*.cpp')
INCLUDE = re.compile(r'^[ \t]*#[ \t]*include[ \t]+"([^"]+)"', re.M)
@@ -1359,9 +1360,11 @@ def step_compile(out, cache_dir, msys, jobs, romfs_job=None, update=False):
copy_if_changed(xlate / name, out / name)
if (KIT / 'bin' / 'amd_fidelityfx_vk.dll').is_file(): # FSR 3.1 (runtime/vk_ffx.cpp)
copy_if_changed(KIT / 'bin' / 'amd_fidelityfx_vk.dll', out / 'amd_fidelityfx_vk.dll')
for name in FFX4_DLLS: # FSR 4 (runtime/vk_ffx4.cpp): both or neither
if all((KIT / 'bin' / n).is_file() for n in FFX4_DLLS):
if all((KIT / 'bin' / n).is_file() for n in FFX4_DLLS): # FSR 4 (runtime/vk_ffx4.cpp): both or neither
for name in FFX4_DLLS:
copy_if_changed(KIT / 'bin' / name, out / name)
if (KIT / 'bin' / FFX4_FG_DLL).is_file(): # its frame generation (runtime/vk_fg4.cpp)
copy_if_changed(KIT / 'bin' / FFX4_FG_DLL, out / FFX4_FG_DLL)
kept = place_dlss(out, linux=not WIN) # NVIDIA DLSS (runtime/vk_dlaa.c): this platform's library from the kit
if kept:
say(f' {kept}')

View File

@@ -52,7 +52,8 @@ def main():
ap.add_argument('--xlate', type=Path, required=True, help='folder with nx2navi_xlate.exe and nx2navi_xlate.id')
ap.add_argument('--ffx', type=Path, help="AMD's signed amd_fidelityfx_vk.dll (FidelityFX SDK 1.1.4, MIT) for FSR 3.1")
ap.add_argument('--ffx4', type=Path, help="the folder with AMD's signed amd_fidelityfx_loader_vk.dll and "
'amd_fidelityfx_upscaler_vk.dll (FSR SDK 2.4.0, MIT) for FSR 4')
'amd_fidelityfx_upscaler_vk.dll (FSR SDK 2.4.0, MIT) for FSR 4, and its '
'amd_fidelityfx_framegeneration_vk.dll when it is there')
ap.add_argument('--dlss', type=Path, help="NVIDIA's nvngx_dlss.dll from disk instead of downloading it from "
"NVIDIA's DLSS SDK repository")
ap.add_argument('--no-dlss', action='store_true', help='leave NVIDIA DLSS out of the kit')
@@ -98,6 +99,8 @@ def main():
if a.ffx4: # FSR 4 loads them from beside the game (runtime/vk_ffx4.cpp); their licence is runtime/third_party/ffx_sdk
for n in ('amd_fidelityfx_loader_vk.dll', 'amd_fidelityfx_upscaler_vk.dll'):
shutil.copy2(a.ffx4 / n, kit / 'bin' / n)
if (a.ffx4 / 'amd_fidelityfx_framegeneration_vk.dll').is_file(): # its frame generation (runtime/vk_fg4.cpp)
shutil.copy2(a.ffx4 / 'amd_fidelityfx_framegeneration_vk.dll', kit / 'bin' / 'amd_fidelityfx_framegeneration_vk.dll')
if dlss: # DLSS: NGX loads it from beside the game (runtime/vk_dlaa.c); make_game.py copies it there
dlss_pin.place(dlss, kit / 'bin')
if sdk: # DLSS Frame Generation (runtime/vk_dlssg.c): make_game.py place_dlssg copies it beside the game

View File

@@ -203,6 +203,8 @@ def build_app(app, ver, xlate, feed_repo, lock, ffx=None, dlss=None, art=None, d
if ffx4: # FSR 4 loads them from beside the game (runtime/vk_ffx4.cpp); make_game.py copies them there
for n in ('amd_fidelityfx_loader_vk.dll', 'amd_fidelityfx_upscaler_vk.dll'):
shutil.copy2(ffx4 / n, app / 'bin' / n)
if (ffx4 / 'amd_fidelityfx_framegeneration_vk.dll').is_file(): # its frame generation (runtime/vk_fg4.cpp)
shutil.copy2(ffx4 / 'amd_fidelityfx_framegeneration_vk.dll', app / 'bin' / 'amd_fidelityfx_framegeneration_vk.dll')
if dlss: # DLSS: NGX loads it from beside the game (runtime/vk_dlaa.c); make_game.py copies it there
dlss_pin.place(dlss, app / 'bin')
if dlssg: # DLSS Frame Generation (runtime/vk_dlssg.c): make_game.py place_dlssg copies it beside the game
@@ -320,7 +322,8 @@ def main():
ap.add_argument('--xlate', type=Path, required=True, help='folder with nx2navi_xlate.exe and nx2navi_xlate.id')
ap.add_argument('--ffx', type=Path, help="AMD's signed amd_fidelityfx_vk.dll (FidelityFX SDK 1.1.4, MIT) for FSR 3.1")
ap.add_argument('--ffx4', type=Path, help="the folder with AMD's signed amd_fidelityfx_loader_vk.dll and "
'amd_fidelityfx_upscaler_vk.dll (FSR SDK 2.4.0, MIT) for FSR 4')
'amd_fidelityfx_upscaler_vk.dll (FSR SDK 2.4.0, MIT) for FSR 4, and its '
'amd_fidelityfx_framegeneration_vk.dll when it is there')
ap.add_argument('--dlss', type=Path, help=f"NVIDIA's nvngx_dlss.dll (DLSS {dlss_pin.VERSION}) from disk instead of "
"downloading it from NVIDIA's DLSS SDK repository")
ap.add_argument('--no-dlss', action='store_true', help='leave NVIDIA DLSS out of the release')