aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-11-03 01:20:05 +0100
committercirakg <cirakg@cirakg.xyz>2026-11-03 01:20:05 +0100
commite9211374ad3728cad12e6520dbaa8f90c3cce280 (patch)
treeff6fd65114cd4e1f1abf6e1e589d1b657e1f878b
parentb3244c282332506d904c866b76945ce9872329b2 (diff)
parentebec180bf1125b4f29b068922d1220c8af9b63b0 (diff)
Merge upstream Djihads80/Eglantine nx2 into nx2
-rw-r--r--.github/workflows/release-binaries.yml4
-rw-r--r--.reuse/dep54
-rw-r--r--externals/FidelityFX-SDK/LICENSE.txt21
-rw-r--r--externals/FidelityFX-SDK/README.md15
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_common_types.h558
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core.h80
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_glsl.h1916
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_gpu_common.h2736
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_gpu_common_half.h2981
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_portability.h46
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr1/ffx_fsr1.h1252
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_accumulate.h172
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_callbacks_glsl.h903
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_common.h403
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_luma_instability.h115
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_luma_pyramid.h192
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_prepare_inputs.h152
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_prepare_reactivity.h283
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_rcas.h112
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_reproject.h79
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_resources.h100
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_sample.h628
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_shading_change.h68
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_shading_change_pyramid.h297
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_upsample.h644
-rw-r--r--externals/FidelityFX-SDK/include/FidelityFX/gpu/spd/ffx_spd.h1014
-rw-r--r--externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_accumulate_pass.glsl75
-rw-r--r--externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_luma_instability_pass.glsl62
-rw-r--r--externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_luma_pyramid_pass.glsl65
-rw-r--r--externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_prepare_inputs_pass.glsl61
-rw-r--r--externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_prepare_reactivity_pass.glsl65
-rw-r--r--externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_rcas_pass.glsl59
-rw-r--r--externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_shading_change_pass.glsl55
-rw-r--r--externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_shading_change_pyramid_pass.glsl67
-rw-r--r--src/core/file_sys/nup_writer.cpp7
-rw-r--r--src/core/hle/service/nx2_gsv.h105
-rw-r--r--src/qt_common/config/shared_translation.cpp12
-rw-r--r--src/qt_common/config/shared_translation.h6
-rw-r--r--src/shader_recompiler/frontend/ampere/lower_to_maxwell.h53
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/engines/kepler_compute.cpp18
-rw-r--r--src/video_core/engines/kepler_compute.h7
-rw-r--r--src/video_core/host1x/codec_types.h8
-rw-r--r--src/video_core/host1x/codecs/vp9.cpp43
-rw-r--r--src/video_core/host1x/nx2_video_decoder.cpp78
-rw-r--r--src/video_core/host1x/nx2_video_decoder.h3
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt66
-rw-r--r--src/video_core/host_shaders/temporal_sharpen.frag49
-rw-r--r--src/video_core/host_shaders/temporal_upscale.frag85
-rw-r--r--src/video_core/rasterizer_interface.h9
-rw-r--r--src/video_core/renderer_vulkan/blit_image.cpp175
-rw-r--r--src/video_core/renderer_vulkan/blit_image.h25
-rw-r--r--src/video_core/renderer_vulkan/vk_descriptor_pool.cpp7
-rw-r--r--src/video_core/renderer_vulkan/vk_descriptor_pool.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_fsr3_upscaler.cpp693
-rw-r--r--src/video_core/renderer_vulkan/vk_fsr3_upscaler.h119
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp6
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h1
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp353
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h34
-rw-r--r--src/video_core/texture_cache/texture_cache.h63
-rw-r--r--src/video_core/texture_cache/texture_cache_base.h3
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h5
63 files changed, 17100 insertions, 221 deletions
diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml
index 2c5ff102dc..db663a98a1 100644
--- a/.github/workflows/release-binaries.yml
+++ b/.github/workflows/release-binaries.yml
@@ -34,9 +34,11 @@ env:
PGO_LLVM_VERSION: '23'
PLATFORM: linux-x86_64-pgo
+# A running build is never cancelled: it finishes and publishes. A push meanwhile queues one build;
+# a later push replaces it, so the newest commit builds as soon as the running one is done.
concurrency:
group: release-binaries-${{ github.ref }}
- cancel-in-progress: true
+ cancel-in-progress: false
jobs:
build:
diff --git a/.reuse/dep5 b/.reuse/dep5
index 5d6fd4379e..801ea51698 100644
--- a/.reuse/dep5
+++ b/.reuse/dep5
@@ -108,6 +108,10 @@ Files: externals/FidelityFX-FSR/*
Copyright: 2021 Advanced Micro Devices, Inc.
License: MIT
+Files: externals/FidelityFX-SDK/*
+Copyright: 2024 Advanced Micro Devices, Inc.
+License: MIT
+
Files: src/yuzu/*.ui
Copyright: 2018-2022 yuzu Emulator Project
License: GPL-2.0-or-later
diff --git a/externals/FidelityFX-SDK/LICENSE.txt b/externals/FidelityFX-SDK/LICENSE.txt
new file mode 100644
index 0000000000..3712e34ade
--- /dev/null
+++ b/externals/FidelityFX-SDK/LICENSE.txt
@@ -0,0 +1,21 @@
+This file is part of the FidelityFX SDK.
+
+Copyright (C) 2024 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.
diff --git a/externals/FidelityFX-SDK/README.md b/externals/FidelityFX-SDK/README.md
new file mode 100644
index 0000000000..810f9dbe02
--- /dev/null
+++ b/externals/FidelityFX-SDK/README.md
@@ -0,0 +1,15 @@
+# FidelityFX SDK (subset)
+
+The FSR 3.1 upscaler's GPU code from AMD's FidelityFX SDK, used in place of a game's DLSS
+evaluation (`src/video_core/renderer_vulkan/vk_fsr3_upscaler.cpp`).
+
+- Source: https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK, tag `v1.1.4`
+ (commit `c6efa6bf7f2027b3ec94f28578bb5965eabb9e55`), FSR 3.1.4 upscaler.
+- Licence: MIT, `LICENSE.txt` (the SDK's `sdk/LICENSE.txt`).
+- `include/FidelityFX/gpu/`: from `sdk/include/FidelityFX/gpu/` (the core headers, `fsr3upscaler/`
+ without the HLSL callbacks and the debug view, `spd/ffx_spd.h`, `fsr1/ffx_fsr1.h`).
+- `shaders/fsr3upscaler/`: the Vulkan pass files from `sdk/src/backends/vk/shaders/fsr3upscaler/`
+ (the eight upscaling passes; not the autogen-reactive or debug-view pass).
+
+The files are unmodified. They are compiled to SPIR-V by `src/video_core/host_shaders`; no
+prebuilt binary or AMD tool from the SDK is used.
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_common_types.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_common_types.h
new file mode 100644
index 0000000000..2c4f0ba02d
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_common_types.h
@@ -0,0 +1,558 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#ifndef FFX_COMMON_TYPES_H
+#define FFX_COMMON_TYPES_H
+
+#if defined(FFX_CPU)
+#define FFX_PARAMETER_IN
+#define FFX_PARAMETER_OUT
+#define FFX_PARAMETER_INOUT
+#define FFX_PARAMETER_UNIFORM
+#elif defined(FFX_HLSL)
+#define FFX_PARAMETER_IN in
+#define FFX_PARAMETER_OUT out
+#define FFX_PARAMETER_INOUT inout
+#define FFX_PARAMETER_UNIFORM uniform
+#elif defined(FFX_GLSL)
+#define FFX_PARAMETER_IN in
+#define FFX_PARAMETER_OUT out
+#define FFX_PARAMETER_INOUT inout
+#define FFX_PARAMETER_UNIFORM const //[cacao_placeholder] until a better fit is found!
+#endif // #if defined(FFX_CPU)
+
+#if defined(FFX_CPU)
+/// A typedef for a boolean value.
+///
+/// @ingroup CPUTypes
+typedef bool FfxBoolean;
+
+/// A typedef for a unsigned 8bit integer.
+///
+/// @ingroup CPUTypes
+typedef uint8_t FfxUInt8;
+
+/// A typedef for a unsigned 16bit integer.
+///
+/// @ingroup CPUTypes
+typedef uint16_t FfxUInt16;
+
+/// A typedef for a unsigned 32bit integer.
+///
+/// @ingroup CPUTypes
+typedef uint32_t FfxUInt32;
+
+/// A typedef for a unsigned 64bit integer.
+///
+/// @ingroup CPUTypes
+typedef uint64_t FfxUInt64;
+
+/// A typedef for a signed 8bit integer.
+///
+/// @ingroup CPUTypes
+typedef int8_t FfxInt8;
+
+/// A typedef for a signed 16bit integer.
+///
+/// @ingroup CPUTypes
+typedef int16_t FfxInt16;
+
+/// A typedef for a signed 32bit integer.
+///
+/// @ingroup CPUTypes
+typedef int32_t FfxInt32;
+
+/// A typedef for a signed 64bit integer.
+///
+/// @ingroup CPUTypes
+typedef int64_t FfxInt64;
+
+/// A typedef for a floating point value.
+///
+/// @ingroup CPUTypes
+typedef float FfxFloat32;
+
+/// A typedef for a 2-dimensional floating point value.
+///
+/// @ingroup CPUTypes
+typedef float FfxFloat32x2[2];
+
+/// A typedef for a 3-dimensional floating point value.
+///
+/// @ingroup CPUTypes
+typedef float FfxFloat32x3[3];
+
+/// A typedef for a 4-dimensional floating point value.
+///
+/// @ingroup CPUTypes
+typedef float FfxFloat32x4[4];
+
+/// A typedef for a 2x2 floating point matrix.
+///
+/// @ingroup CPUTypes
+typedef float FfxFloat32x2x2[4];
+
+/// A typedef for a 3x3 floating point matrix.
+///
+/// @ingroup CPUTypes
+typedef float FfxFloat32x3x3[9];
+
+/// A typedef for a 3x4 floating point matrix.
+///
+/// @ingroup CPUTypes
+typedef float FfxFloat32x3x4[12];
+
+/// A typedef for a 4x4 floating point matrix.
+///
+/// @ingroup CPUTypes
+typedef float FfxFloat32x4x4[16];
+
+/// A typedef for a 2-dimensional 32bit signed integer.
+///
+/// @ingroup CPUTypes
+typedef int32_t FfxInt32x2[2];
+
+/// A typedef for a 3-dimensional 32bit signed integer.
+///
+/// @ingroup CPUTypes
+typedef int32_t FfxInt32x3[3];
+
+/// A typedef for a 4-dimensional 32bit signed integer.
+///
+/// @ingroup CPUTypes
+typedef int32_t FfxInt32x4[4];
+
+/// A typedef for a 2-dimensional 32bit usigned integer.
+///
+/// @ingroup CPUTypes
+typedef uint32_t FfxUInt32x2[2];
+
+/// A typedef for a 3-dimensional 32bit unsigned integer.
+///
+/// @ingroup CPUTypes
+typedef uint32_t FfxUInt32x3[3];
+
+/// A typedef for a 4-dimensional 32bit unsigned integer.
+///
+/// @ingroup CPUTypes
+typedef uint32_t FfxUInt32x4[4];
+#endif // #if defined(FFX_CPU)
+
+#if defined(FFX_HLSL)
+
+#define FfxFloat32Mat4 matrix <float, 4, 4>
+#define FfxFloat32Mat3 matrix <float, 3, 3>
+
+/// A typedef for a boolean value.
+///
+/// @ingroup HLSLTypes
+typedef bool FfxBoolean;
+
+#if FFX_HLSL_SM>=62
+
+/// @defgroup HLSL62Types HLSL 6.2 And Above Types
+/// HLSL 6.2 and above type defines for all commonly used variables
+///
+/// @ingroup HLSLTypes
+
+/// A typedef for a floating point value.
+///
+/// @ingroup HLSL62Types
+typedef float32_t FfxFloat32;
+
+/// A typedef for a 2-dimensional floating point value.
+///
+/// @ingroup HLSL62Types
+typedef float32_t2 FfxFloat32x2;
+
+/// A typedef for a 3-dimensional floating point value.
+///
+/// @ingroup HLSL62Types
+typedef float32_t3 FfxFloat32x3;
+
+/// A typedef for a 4-dimensional floating point value.
+///
+/// @ingroup HLSL62Types
+typedef float32_t4 FfxFloat32x4;
+
+/// A [cacao_placeholder] typedef for matrix type until confirmed.
+typedef float4x4 FfxFloat32x4x4;
+typedef float3x4 FfxFloat32x3x4;
+typedef float3x3 FfxFloat32x3x3;
+typedef float2x2 FfxFloat32x2x2;
+
+/// A typedef for a unsigned 32bit integer.
+///
+/// @ingroup HLSL62Types
+typedef uint32_t FfxUInt32;
+
+/// A typedef for a 2-dimensional 32bit unsigned integer.
+///
+/// @ingroup HLSL62Types
+typedef uint32_t2 FfxUInt32x2;
+
+/// A typedef for a 3-dimensional 32bit unsigned integer.
+///
+/// @ingroup HLSL62Types
+typedef uint32_t3 FfxUInt32x3;
+
+/// A typedef for a 4-dimensional 32bit unsigned integer.
+///
+/// @ingroup HLSL62Types
+typedef uint32_t4 FfxUInt32x4;
+
+/// A typedef for a signed 32bit integer.
+///
+/// @ingroup HLSL62Types
+typedef int32_t FfxInt32;
+
+/// A typedef for a 2-dimensional signed 32bit integer.
+///
+/// @ingroup HLSL62Types
+typedef int32_t2 FfxInt32x2;
+
+/// A typedef for a 3-dimensional signed 32bit integer.
+///
+/// @ingroup HLSL62Types
+typedef int32_t3 FfxInt32x3;
+
+/// A typedef for a 4-dimensional signed 32bit integer.
+///
+/// @ingroup HLSL62Types
+typedef int32_t4 FfxInt32x4;
+
+#else // #if FFX_HLSL_SM>=62
+
+/// @defgroup HLSLBaseTypes HLSL 6.1 And Below Types
+/// HLSL 6.1 and below type defines for all commonly used variables
+///
+/// @ingroup HLSLTypes
+
+#define FfxFloat32 float
+#define FfxFloat32x2 float2
+#define FfxFloat32x3 float3
+#define FfxFloat32x4 float4
+
+/// A [cacao_placeholder] typedef for matrix type until confirmed.
+#define FfxFloat32x4x4 float4x4
+#define FfxFloat32x3x4 float3x4
+#define FfxFloat32x3x3 float3x3
+#define FfxFloat32x2x2 float2x2
+
+/// A typedef for a unsigned 32bit integer.
+///
+/// @ingroup GPU
+typedef uint FfxUInt32;
+typedef uint2 FfxUInt32x2;
+typedef uint3 FfxUInt32x3;
+typedef uint4 FfxUInt32x4;
+
+typedef int FfxInt32;
+typedef int2 FfxInt32x2;
+typedef int3 FfxInt32x3;
+typedef int4 FfxInt32x4;
+
+#endif // #if FFX_HLSL_SM>=62
+
+#if FFX_HALF
+
+#if FFX_HLSL_SM >= 62
+
+typedef float16_t FfxFloat16;
+typedef float16_t2 FfxFloat16x2;
+typedef float16_t3 FfxFloat16x3;
+typedef float16_t4 FfxFloat16x4;
+
+/// A typedef for an unsigned 16bit integer.
+///
+/// @ingroup HLSLTypes
+typedef uint16_t FfxUInt16;
+typedef uint16_t2 FfxUInt16x2;
+typedef uint16_t3 FfxUInt16x3;
+typedef uint16_t4 FfxUInt16x4;
+
+/// A typedef for a signed 16bit integer.
+///
+/// @ingroup HLSLTypes
+typedef int16_t FfxInt16;
+typedef int16_t2 FfxInt16x2;
+typedef int16_t3 FfxInt16x3;
+typedef int16_t4 FfxInt16x4;
+#else // #if FFX_HLSL_SM>=62
+typedef min16float FfxFloat16;
+typedef min16float2 FfxFloat16x2;
+typedef min16float3 FfxFloat16x3;
+typedef min16float4 FfxFloat16x4;
+
+/// A typedef for an unsigned 16bit integer.
+///
+/// @ingroup HLSLTypes
+typedef min16uint FfxUInt16;
+typedef min16uint2 FfxUInt16x2;
+typedef min16uint3 FfxUInt16x3;
+typedef min16uint4 FfxUInt16x4;
+
+/// A typedef for a signed 16bit integer.
+///
+/// @ingroup HLSLTypes
+typedef min16int FfxInt16;
+typedef min16int2 FfxInt16x2;
+typedef min16int3 FfxInt16x3;
+typedef min16int4 FfxInt16x4;
+#endif // #if FFX_HLSL_SM>=62
+
+#endif // FFX_HALF
+
+#endif // #if defined(FFX_HLSL)
+
+#if defined(FFX_GLSL)
+
+#define FfxFloat32Mat4 mat4
+#define FfxFloat32Mat3 mat3
+
+/// A typedef for a boolean value.
+///
+/// @ingroup GLSLTypes
+#define FfxBoolean bool
+#define FfxFloat32 float
+#define FfxFloat32x2 vec2
+#define FfxFloat32x3 vec3
+#define FfxFloat32x4 vec4
+#define FfxUInt32 uint
+#define FfxUInt32x2 uvec2
+#define FfxUInt32x3 uvec3
+#define FfxUInt32x4 uvec4
+#define FfxInt32 int
+#define FfxInt32x2 ivec2
+#define FfxInt32x3 ivec3
+#define FfxInt32x4 ivec4
+
+/// A [cacao_placeholder] typedef for matrix type until confirmed.
+#define FfxFloat32x4x4 mat4
+#define FfxFloat32x3x4 mat4x3
+#define FfxFloat32x3x3 mat3
+#define FfxFloat32x2x2 mat2
+
+#if FFX_HALF
+#define FfxFloat16 float16_t
+#define FfxFloat16x2 f16vec2
+#define FfxFloat16x3 f16vec3
+#define FfxFloat16x4 f16vec4
+#define FfxUInt16 uint16_t
+#define FfxUInt16x2 u16vec2
+#define FfxUInt16x3 u16vec3
+#define FfxUInt16x4 u16vec4
+#define FfxInt16 int16_t
+#define FfxInt16x2 i16vec2
+#define FfxInt16x3 i16vec3
+#define FfxInt16x4 i16vec4
+#endif // FFX_HALF
+#endif // #if defined(FFX_GLSL)
+
+// Global toggles:
+// #define FFX_HALF (1)
+// #define FFX_HLSL_SM (62)
+
+#if FFX_HALF
+
+#if FFX_HLSL_SM >= 62
+
+#define FFX_MIN16_SCALAR( TypeName, BaseComponentType ) typedef BaseComponentType##16_t TypeName;
+#define FFX_MIN16_VECTOR( TypeName, BaseComponentType, COL ) typedef vector<BaseComponentType##16_t, COL> TypeName;
+#define FFX_MIN16_MATRIX( TypeName, BaseComponentType, ROW, COL ) typedef matrix<BaseComponentType##16_t, ROW, COL> TypeName;
+
+#define FFX_16BIT_SCALAR( TypeName, BaseComponentType ) typedef BaseComponentType##16_t TypeName;
+#define FFX_16BIT_VECTOR( TypeName, BaseComponentType, COL ) typedef vector<BaseComponentType##16_t, COL> TypeName;
+#define FFX_16BIT_MATRIX( TypeName, BaseComponentType, ROW, COL ) typedef matrix<BaseComponentType##16_t, ROW, COL> TypeName;
+
+#else //FFX_HLSL_SM>=62
+
+#define FFX_MIN16_SCALAR( TypeName, BaseComponentType ) typedef min16##BaseComponentType TypeName;
+#define FFX_MIN16_VECTOR( TypeName, BaseComponentType, COL ) typedef vector<min16##BaseComponentType, COL> TypeName;
+#define FFX_MIN16_MATRIX( TypeName, BaseComponentType, ROW, COL ) typedef matrix<min16##BaseComponentType, ROW, COL> TypeName;
+
+#define FFX_16BIT_SCALAR( TypeName, BaseComponentType ) FFX_MIN16_SCALAR( TypeName, BaseComponentType );
+#define FFX_16BIT_VECTOR( TypeName, BaseComponentType, COL ) FFX_MIN16_VECTOR( TypeName, BaseComponentType, COL );
+#define FFX_16BIT_MATRIX( TypeName, BaseComponentType, ROW, COL ) FFX_MIN16_MATRIX( TypeName, BaseComponentType, ROW, COL );
+
+#endif //FFX_HLSL_SM>=62
+
+#else //FFX_HALF
+
+#define FFX_MIN16_SCALAR( TypeName, BaseComponentType ) typedef BaseComponentType TypeName;
+#define FFX_MIN16_VECTOR( TypeName, BaseComponentType, COL ) typedef vector<BaseComponentType, COL> TypeName;
+#define FFX_MIN16_MATRIX( TypeName, BaseComponentType, ROW, COL ) typedef matrix<BaseComponentType, ROW, COL> TypeName;
+
+#define FFX_16BIT_SCALAR( TypeName, BaseComponentType ) typedef BaseComponentType TypeName;
+#define FFX_16BIT_VECTOR( TypeName, BaseComponentType, COL ) typedef vector<BaseComponentType, COL> TypeName;
+#define FFX_16BIT_MATRIX( TypeName, BaseComponentType, ROW, COL ) typedef matrix<BaseComponentType, ROW, COL> TypeName;
+
+#endif //FFX_HALF
+
+#if defined(FFX_GPU)
+// Common typedefs:
+#if defined(FFX_HLSL)
+FFX_MIN16_SCALAR( FFX_MIN16_F , float );
+FFX_MIN16_VECTOR( FFX_MIN16_F2, float, 2 );
+FFX_MIN16_VECTOR( FFX_MIN16_F3, float, 3 );
+FFX_MIN16_VECTOR( FFX_MIN16_F4, float, 4 );
+
+FFX_MIN16_SCALAR( FFX_MIN16_I, int );
+FFX_MIN16_VECTOR( FFX_MIN16_I2, int, 2 );
+FFX_MIN16_VECTOR( FFX_MIN16_I3, int, 3 );
+FFX_MIN16_VECTOR( FFX_MIN16_I4, int, 4 );
+
+FFX_MIN16_SCALAR( FFX_MIN16_U, uint );
+FFX_MIN16_VECTOR( FFX_MIN16_U2, uint, 2 );
+FFX_MIN16_VECTOR( FFX_MIN16_U3, uint, 3 );
+FFX_MIN16_VECTOR( FFX_MIN16_U4, uint, 4 );
+
+FFX_16BIT_SCALAR( FFX_F16_t , float );
+FFX_16BIT_VECTOR( FFX_F16_t2, float, 2 );
+FFX_16BIT_VECTOR( FFX_F16_t3, float, 3 );
+FFX_16BIT_VECTOR( FFX_F16_t4, float, 4 );
+
+FFX_16BIT_SCALAR( FFX_I16_t, int );
+FFX_16BIT_VECTOR( FFX_I16_t2, int, 2 );
+FFX_16BIT_VECTOR( FFX_I16_t3, int, 3 );
+FFX_16BIT_VECTOR( FFX_I16_t4, int, 4 );
+
+FFX_16BIT_SCALAR( FFX_U16_t, uint );
+FFX_16BIT_VECTOR( FFX_U16_t2, uint, 2 );
+FFX_16BIT_VECTOR( FFX_U16_t3, uint, 3 );
+FFX_16BIT_VECTOR( FFX_U16_t4, uint, 4 );
+
+#define TYPEDEF_MIN16_TYPES(Prefix) \
+typedef FFX_MIN16_F Prefix##_F; \
+typedef FFX_MIN16_F2 Prefix##_F2; \
+typedef FFX_MIN16_F3 Prefix##_F3; \
+typedef FFX_MIN16_F4 Prefix##_F4; \
+typedef FFX_MIN16_I Prefix##_I; \
+typedef FFX_MIN16_I2 Prefix##_I2; \
+typedef FFX_MIN16_I3 Prefix##_I3; \
+typedef FFX_MIN16_I4 Prefix##_I4; \
+typedef FFX_MIN16_U Prefix##_U; \
+typedef FFX_MIN16_U2 Prefix##_U2; \
+typedef FFX_MIN16_U3 Prefix##_U3; \
+typedef FFX_MIN16_U4 Prefix##_U4;
+
+#define TYPEDEF_16BIT_TYPES(Prefix) \
+typedef FFX_16BIT_F Prefix##_F; \
+typedef FFX_16BIT_F2 Prefix##_F2; \
+typedef FFX_16BIT_F3 Prefix##_F3; \
+typedef FFX_16BIT_F4 Prefix##_F4; \
+typedef FFX_16BIT_I Prefix##_I; \
+typedef FFX_16BIT_I2 Prefix##_I2; \
+typedef FFX_16BIT_I3 Prefix##_I3; \
+typedef FFX_16BIT_I4 Prefix##_I4; \
+typedef FFX_16BIT_U Prefix##_U; \
+typedef FFX_16BIT_U2 Prefix##_U2; \
+typedef FFX_16BIT_U3 Prefix##_U3; \
+typedef FFX_16BIT_U4 Prefix##_U4;
+
+#define TYPEDEF_FULL_PRECISION_TYPES(Prefix) \
+typedef FfxFloat32 Prefix##_F; \
+typedef FfxFloat32x2 Prefix##_F2; \
+typedef FfxFloat32x3 Prefix##_F3; \
+typedef FfxFloat32x4 Prefix##_F4; \
+typedef FfxInt32 Prefix##_I; \
+typedef FfxInt32x2 Prefix##_I2; \
+typedef FfxInt32x3 Prefix##_I3; \
+typedef FfxInt32x4 Prefix##_I4; \
+typedef FfxUInt32 Prefix##_U; \
+typedef FfxUInt32x2 Prefix##_U2; \
+typedef FfxUInt32x3 Prefix##_U3; \
+typedef FfxUInt32x4 Prefix##_U4;
+#endif // #if defined(FFX_HLSL)
+
+#if defined(FFX_GLSL)
+
+#if FFX_HALF
+
+#define FFX_MIN16_F float16_t
+#define FFX_MIN16_F2 f16vec2
+#define FFX_MIN16_F3 f16vec3
+#define FFX_MIN16_F4 f16vec4
+
+#define FFX_MIN16_I int16_t
+#define FFX_MIN16_I2 i16vec2
+#define FFX_MIN16_I3 i16vec3
+#define FFX_MIN16_I4 i16vec4
+
+#define FFX_MIN16_U uint16_t
+#define FFX_MIN16_U2 u16vec2
+#define FFX_MIN16_U3 u16vec3
+#define FFX_MIN16_U4 u16vec4
+
+#define FFX_16BIT_F float16_t
+#define FFX_16BIT_F2 f16vec2
+#define FFX_16BIT_F3 f16vec3
+#define FFX_16BIT_F4 f16vec4
+
+#define FFX_16BIT_I int16_t
+#define FFX_16BIT_I2 i16vec2
+#define FFX_16BIT_I3 i16vec3
+#define FFX_16BIT_I4 i16vec4
+
+#define FFX_16BIT_U uint16_t
+#define FFX_16BIT_U2 u16vec2
+#define FFX_16BIT_U3 u16vec3
+#define FFX_16BIT_U4 u16vec4
+
+#else // FFX_HALF
+
+#define FFX_MIN16_F float
+#define FFX_MIN16_F2 vec2
+#define FFX_MIN16_F3 vec3
+#define FFX_MIN16_F4 vec4
+
+#define FFX_MIN16_I int
+#define FFX_MIN16_I2 ivec2
+#define FFX_MIN16_I3 ivec3
+#define FFX_MIN16_I4 ivec4
+
+#define FFX_MIN16_U uint
+#define FFX_MIN16_U2 uvec2
+#define FFX_MIN16_U3 uvec3
+#define FFX_MIN16_U4 uvec4
+
+#define FFX_16BIT_F float
+#define FFX_16BIT_F2 vec2
+#define FFX_16BIT_F3 vec3
+#define FFX_16BIT_F4 vec4
+
+#define FFX_16BIT_I int
+#define FFX_16BIT_I2 ivec2
+#define FFX_16BIT_I3 ivec3
+#define FFX_16BIT_I4 ivec4
+
+#define FFX_16BIT_U uint
+#define FFX_16BIT_U2 uvec2
+#define FFX_16BIT_U3 uvec3
+#define FFX_16BIT_U4 uvec4
+
+#endif // FFX_HALF
+
+#endif // #if defined(FFX_GLSL)
+
+#endif // #if defined(FFX_GPU)
+#endif // #ifndef FFX_COMMON_TYPES_H
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core.h
new file mode 100644
index 0000000000..d1ed14419b
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core.h
@@ -0,0 +1,80 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+/// @defgroup FfxGPU GPU
+/// The FidelityFX SDK GPU References
+///
+/// @ingroup ffxSDK
+
+/// @defgroup FfxHLSL HLSL References
+/// FidelityFX SDK HLSL GPU References
+///
+/// @ingroup FfxGPU
+
+/// @defgroup FfxGLSL GLSL References
+/// FidelityFX SDK GLSL GPU References
+///
+/// @ingroup FfxGPU
+
+/// @defgroup FfxGPUEffects FidelityFX GPU References
+/// FidelityFX Effect GPU Reference Documentation
+///
+/// @ingroup FfxGPU
+
+/// @defgroup GPUCore GPU Core
+/// GPU defines and functions
+///
+/// @ingroup FfxGPU
+
+#if !defined(FFX_CORE_H)
+#define FFX_CORE_H
+
+#ifdef __hlsl_dx_compiler
+#pragma dxc diagnostic push
+#pragma dxc diagnostic ignored "-Wambig-lit-shift"
+#endif //__hlsl_dx_compiler
+
+#include "ffx_common_types.h"
+
+#if defined(FFX_CPU)
+ #include "ffx_core_cpu.h"
+#endif // #if defined(FFX_CPU)
+
+#if defined(FFX_GLSL) && defined(FFX_GPU)
+ #include "ffx_core_glsl.h"
+#endif // #if defined(FFX_GLSL) && defined(FFX_GPU)
+
+#if defined(FFX_HLSL) && defined(FFX_GPU)
+ #include "ffx_core_hlsl.h"
+#endif // #if defined(FFX_HLSL) && defined(FFX_GPU)
+
+#if defined(FFX_GPU)
+ #include "ffx_core_gpu_common.h"
+ #include "ffx_core_gpu_common_half.h"
+ #include "ffx_core_portability.h"
+#endif // #if defined(FFX_GPU)
+
+#ifdef __hlsl_dx_compiler
+#pragma dxc diagnostic pop
+#endif //__hlsl_dx_compiler
+
+#endif // #if !defined(FFX_CORE_H)
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_glsl.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_glsl.h
new file mode 100644
index 0000000000..c8dccacdbc
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_glsl.h
@@ -0,0 +1,1916 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+/// @defgroup GLSLCore GLSL Core
+/// GLSL core defines and functions
+///
+/// @ingroup FfxGLSL
+
+/// A define for abstracting select functionality for pre/post HLSL 21
+///
+/// @ingroup GLSLCore
+#define FFX_SELECT(cond, arg1, arg2) cond ? arg1 : arg2
+
+/// A define for abstracting shared memory between shading languages.
+///
+/// @ingroup GLSLCore
+#define FFX_GROUPSHARED shared
+
+/// A define for abstracting compute memory barriers between shading languages.
+///
+/// @ingroup GLSLCore
+#define FFX_GROUP_MEMORY_BARRIER groupMemoryBarrier(); barrier()
+
+/// A define for abstracting compute atomic additions between shading languages.
+///
+/// @ingroup GLSLCore
+#define FFX_ATOMIC_ADD(x, y) atomicAdd(x, y)
+
+/// A define for abstracting compute atomic additions between shading languages.
+///
+/// @ingroup GLSLCore
+#define FFX_ATOMIC_ADD_RETURN(x, y, r) r = atomicAdd(x, y)
+
+/// A define for abstracting compute atomic OR between shading languages.
+///
+/// @ingroup GLSLCore
+#define FFX_ATOMIC_OR(x, y) atomicOr(x, y)
+
+/// A define for abstracting compute atomic min between shading languages.
+///
+/// @ingroup GLSLCore
+#define FFX_ATOMIC_MIN(x, y) atomicMin(x, y)
+
+/// A define for abstracting compute atomic max between shading languages.
+///
+/// @ingroup GLSLCore
+#define FFX_ATOMIC_MAX(x, y) atomicMax(x, y)
+
+/// A define added to accept static markup on functions to aid CPU/GPU portability of code.
+///
+/// @ingroup GLSLCore
+#define FFX_STATIC
+
+/// A define for abstracting loop unrolling between shading languages.
+///
+/// @ingroup GLSLCore
+#define FFX_UNROLL
+
+/// A define for abstracting a 'greater than' comparison operator between two types.
+///
+/// @ingroup GLSLCore
+#define FFX_GREATER_THAN(x, y) greaterThan(x, y)
+
+/// A define for abstracting a 'greater than or equal' comparison operator between two types.
+///
+/// @ingroup GLSLCore
+#define FFX_GREATER_THAN_EQUAL(x, y) greaterThanEqual(x, y)
+
+/// A define for abstracting a 'less than' comparison operator between two types.
+///
+/// @ingroup GLSLCore
+#define FFX_LESS_THAN(x, y) lessThan(x, y)
+
+/// A define for abstracting a 'less than or equal' comparison operator between two types.
+///
+/// @ingroup GLSLCore
+#define FFX_LESS_THAN_EQUAL(x, y) lessThanEqual(x, y)
+
+/// A define for abstracting an 'equal' comparison operator between two types.
+///
+/// @ingroup GLSLCore
+#define FFX_EQUAL(x, y) equal(x, y)
+
+/// A define for abstracting a 'not equal' comparison operator between two types.
+///
+/// @ingroup GLSLCore
+#define FFX_NOT_EQUAL(x, y) notEqual(x, y)
+
+/// A define for abstracting matrix multiply operations between shading languages.
+///
+/// @ingroup GLSLCore
+#define FFX_MATRIX_MULTIPLY(a, b) (a * b)
+
+/// A define for abstracting vector transformations between shading languages.
+///
+/// @ingroup GLSLCore
+#define FFX_TRANSFORM_VECTOR(a, b) (a * b)
+
+/// A define for abstracting modulo operations between shading languages.
+///
+/// @ingroup GLSLCore
+#define FFX_MODULO(a, b) (mod(a, b))
+
+/// Broadcast a scalar value to a 1-dimensional floating point vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_FLOAT32(x) FfxFloat32(x)
+
+/// Broadcast a scalar value to a 2-dimensional floating point vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_FLOAT32X2(x) FfxFloat32x2(FfxFloat32(x))
+
+/// Broadcast a scalar value to a 3-dimensional floating point vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_FLOAT32X3(x) FfxFloat32x3(FfxFloat32(x))
+
+/// Broadcast a scalar value to a 4-dimensional floating point vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_FLOAT32X4(x) FfxFloat32x4(FfxFloat32(x))
+
+/// Broadcast a scalar value to a 1-dimensional unsigned integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_UINT32(x) FfxUInt32(x)
+
+/// Broadcast a scalar value to a 2-dimensional unsigned integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_UINT32X2(x) FfxUInt32x2(FfxUInt32(x))
+
+/// Broadcast a scalar value to a 3-dimensional unsigned integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_UINT32X3(x) FfxUInt32x3(FfxUInt32(x))
+
+/// Broadcast a scalar value to a 4-dimensional unsigned integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_UINT32X4(x) FfxUInt32x4(FfxUInt32(x))
+
+/// Broadcast a scalar value to a 1-dimensional signed integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_INT32(x) FfxInt32(x)
+
+/// Broadcast a scalar value to a 2-dimensional signed integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_INT32X2(x) FfxInt32x2(FfxInt32(x))
+
+/// Broadcast a scalar value to a 3-dimensional signed integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_INT32X3(x) FfxInt32x3(FfxInt32(x))
+
+/// Broadcast a scalar value to a 4-dimensional signed integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_INT32X4(x) FfxInt32x4(FfxInt32(x))
+
+/// Broadcast a scalar value to a 1-dimensional half-precision floating point vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_MIN_FLOAT16(x) FFX_MIN16_F(x)
+
+/// Broadcast a scalar value to a 2-dimensional half-precision floating point vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_MIN_FLOAT16X2(x) FFX_MIN16_F2(FFX_MIN16_F(x))
+
+/// Broadcast a scalar value to a 3-dimensional half-precision floating point vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_MIN_FLOAT16X3(x) FFX_MIN16_F3(FFX_MIN16_F(x))
+
+/// Broadcast a scalar value to a 4-dimensional half-precision floating point vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_MIN_FLOAT16X4(x) FFX_MIN16_F4(FFX_MIN16_F(x))
+
+/// Broadcast a scalar value to a 1-dimensional half-precision unsigned integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_MIN_UINT16(x) FFX_MIN16_U(x)
+
+/// Broadcast a scalar value to a 2-dimensional half-precision unsigned integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_MIN_UINT16X2(x) FFX_MIN16_U2(FFX_MIN16_U(x))
+
+/// Broadcast a scalar value to a 3-dimensional half-precision unsigned integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_MIN_UINT16X3(x) FFX_MIN16_U3(FFX_MIN16_U(x))
+
+/// Broadcast a scalar value to a 4-dimensional half-precision unsigned integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_MIN_UINT16X4(x) FFX_MIN16_U4(FFX_MIN16_U(x))
+
+/// Broadcast a scalar value to a 1-dimensional half-precision signed integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_MIN_INT16(x) FFX_MIN16_I(x)
+
+/// Broadcast a scalar value to a 2-dimensional half-precision signed integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_MIN_INT16X2(x) FFX_MIN16_I2(FFX_MIN16_I(x))
+
+/// Broadcast a scalar value to a 3-dimensional half-precision signed integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_MIN_INT16X3(x) FFX_MIN16_I3(FFX_MIN16_I(x))
+
+/// Broadcast a scalar value to a 4-dimensional half-precision signed integer vector.
+///
+/// @ingroup GLSLCore
+#define FFX_BROADCAST_MIN_INT16X4(x) FFX_MIN16_I4(FFX_MIN16_I(x))
+
+ #extension GL_EXT_shader_explicit_arithmetic_types : require
+#if !defined(FFX_SKIP_EXT)
+#if FFX_HALF
+ #extension GL_EXT_shader_16bit_storage : require
+#endif // FFX_HALF
+
+#if defined(FFX_LONG)
+ #extension GL_ARB_gpu_shader_int64 : require
+ #extension GL_NV_shader_atomic_int64 : require
+#endif // #if defined(FFX_LONG)
+
+#if defined(FFX_WAVE)
+ #extension GL_KHR_shader_subgroup_arithmetic : require
+ #extension GL_KHR_shader_subgroup_ballot : require
+ #extension GL_KHR_shader_subgroup_quad : require
+ #extension GL_KHR_shader_subgroup_shuffle : require
+#endif // #if defined(FFX_WAVE)
+#endif // #if !defined(FFX_SKIP_EXT)
+
+// Forward declarations
+FfxFloat32 ffxSqrt(FfxFloat32 x);
+FfxFloat32x2 ffxSqrt(FfxFloat32x2 x);
+FfxFloat32x3 ffxSqrt(FfxFloat32x3 x);
+FfxFloat32x4 ffxSqrt(FfxFloat32x4 x);
+
+/// Interprets the bit pattern of x as a floating-point number.
+///
+/// @param [in] x The input value.
+///
+/// @returns
+/// The input interpreted as a floating-point number.
+///
+/// @ingroup GLSLCore
+FfxFloat32 ffxAsFloat(FfxUInt32 x)
+{
+ return uintBitsToFloat(x);
+}
+
+/// Interprets the bit pattern of x as a floating-point number.
+///
+/// @param [in] x The input value.
+///
+/// @returns
+/// The input interpreted as a floating-point number.
+///
+/// @ingroup GLSLCore
+FfxFloat32x2 ffxAsFloat(FfxUInt32x2 x)
+{
+ return uintBitsToFloat(x);
+}
+
+/// Interprets the bit pattern of x as a floating-point number.
+///
+/// @param [in] x The input value.
+///
+/// @returns
+/// The input interpreted as a floating-point number.
+///
+/// @ingroup GLSLCore
+FfxFloat32x3 ffxAsFloat(FfxUInt32x3 x)
+{
+ return uintBitsToFloat(x);
+}
+
+/// Interprets the bit pattern of x as a floating-point number.
+///
+/// @param [in] x The input value.
+///
+/// @returns
+/// The input interpreted as a floating-point number.
+///
+/// @ingroup GLSLCore
+FfxFloat32x4 ffxAsFloat(FfxUInt32x4 x)
+{
+ return uintBitsToFloat(x);
+}
+
+/// Interprets the bit pattern of x as an unsigned integer.
+///
+/// @param [in] x The input value.
+///
+/// @returns
+/// The input interpreted as an unsigned integer.
+///
+/// @ingroup GLSLCore
+FfxUInt32 ffxAsUInt32(FfxFloat32 x)
+{
+ return floatBitsToUint(x);
+}
+
+/// Interprets the bit pattern of x as an unsigned integer.
+///
+/// @param [in] x The input value.
+///
+/// @returns
+/// The input interpreted as an unsigned integer.
+///
+/// @ingroup GLSLCore
+FfxUInt32x2 ffxAsUInt32(FfxFloat32x2 x)
+{
+ return floatBitsToUint(x);
+}
+
+/// Interprets the bit pattern of x as an unsigned integer.
+///
+/// @param [in] x The input value.
+///
+/// @returns
+/// The input interpreted as an unsigned integer.
+///
+/// @ingroup GLSLCore
+FfxUInt32x3 ffxAsUInt32(FfxFloat32x3 x)
+{
+ return floatBitsToUint(x);
+}
+
+/// Interprets the bit pattern of x as an unsigned integer.
+///
+/// @param [in] x The input value.
+///
+/// @returns
+/// The input interpreted as an unsigned integer.
+///
+/// @ingroup GLSLCore
+FfxUInt32x4 ffxAsUInt32(FfxFloat32x4 x)
+{
+ return floatBitsToUint(x);
+}
+
+/// Pack 2x32-bit floating point values in a single 32bit value.
+///
+/// This function first converts each component of <c><i>value</i></c> into their nearest 16-bit floating
+/// point representation, and then stores the X and Y components in the lower and upper 16 bits of the
+/// 32bit unsigned integer respectively.
+///
+/// @param [in] value A 2-dimensional floating point value to convert and pack.
+///
+/// @returns
+/// A packed 32bit value containing 2 16bit floating point values.
+///
+/// @ingroup GLSLCore
+FfxUInt32 ffxPackHalf2x16(FfxFloat32x2 value)
+{
+ return packHalf2x16(value);
+}
+
+/// Convert a 32bit IEEE 754 floating point value to its nearest 16bit equivalent.
+///
+/// @param [in] value The value to convert.
+///
+/// @returns
+/// The nearest 16bit equivalent of <c><i>value</i></c>.
+///
+/// @ingroup GLSLCore
+FfxUInt32 ffxF32ToF16(FfxFloat32 value)
+{
+ return packHalf2x16(FfxFloat32x2(value, 0.0));
+}
+
+/// Broadcast a scalar value to a 2-dimensional floating point vector.
+///
+/// @param [in] value The value to to broadcast.
+///
+/// @returns
+/// A 2-dimensional floating point vector with <c><i>value</i></c> in each component.
+///
+/// @ingroup GLSLCore
+FfxFloat32x2 ffxBroadcast2(FfxFloat32 value)
+{
+ return FfxFloat32x2(value, value);
+}
+
+/// Broadcast a scalar value to a 3-dimensional floating point vector.
+///
+/// @param [in] value The value to to broadcast.
+///
+/// @returns
+/// A 3-dimensional floating point vector with <c><i>value</i></c> in each component.
+///
+/// @ingroup GLSLCore
+FfxFloat32x3 ffxBroadcast3(FfxFloat32 value)
+{
+ return FfxFloat32x3(value, value, value);
+}
+
+/// Broadcast a scalar value to a 4-dimensional floating point vector.
+///
+/// @param [in] value The value to to broadcast.
+///
+/// @returns
+/// A 4-dimensional floating point vector with <c><i>value</i></c> in each component.
+///
+/// @ingroup GLSLCore
+FfxFloat32x4 ffxBroadcast4(FfxFloat32 value)
+{
+ return FfxFloat32x4(value, value, value, value);
+}
+
+/// Broadcast a scalar value to a 2-dimensional signed integer vector.
+///
+/// @param [in] value The value to to broadcast.
+///
+/// @returns
+/// A 2-dimensional signed integer vector with <c><i>value</i></c> in each component.
+///
+/// @ingroup GLSLCore
+FfxInt32x2 ffxBroadcast2(FfxInt32 value)
+{
+ return FfxInt32x2(value, value);
+}
+
+/// Broadcast a scalar value to a 3-dimensional signed integer vector.
+///
+/// @param [in] value The value to to broadcast.
+///
+/// @returns
+/// A 3-dimensional signed integer vector with <c><i>value</i></c> in each component.
+///
+/// @ingroup GLSLCore
+FfxInt32x3 ffxBroadcast3(FfxInt32 value)
+{
+ return FfxInt32x3(value, value, value);
+}
+
+/// Broadcast a scalar value to a 4-dimensional signed integer vector.
+///
+/// @param [in] value The value to to broadcast.
+///
+/// @returns
+/// A 4-dimensional signed integer vector with <c><i>value</i></c> in each component.
+///
+/// @ingroup GLSLCore
+FfxInt32x4 ffxBroadcast4(FfxInt32 value)
+{
+ return FfxInt32x4(value, value, value, value);
+}
+
+/// Broadcast a scalar value to a 2-dimensional unsigned integer vector.
+///
+/// @param [in] value The value to to broadcast.
+///
+/// @returns
+/// A 2-dimensional unsigned integer vector with <c><i>value</i></c> in each component.
+///
+/// @ingroup GLSLCore
+FfxUInt32x2 ffxBroadcast2(FfxUInt32 value)
+{
+ return FfxUInt32x2(value, value);
+}
+
+/// Broadcast a scalar value to a 3-dimensional unsigned integer vector.
+///
+/// @param [in] value The value to to broadcast.
+///
+/// @returns
+/// A 3-dimensional unsigned integer vector with <c><i>value</i></c> in each component.
+///
+/// @ingroup GLSLCore
+FfxUInt32x3 ffxBroadcast3(FfxUInt32 value)
+{
+ return FfxUInt32x3(value, value, value);
+}
+
+/// Broadcast a scalar value to a 4-dimensional unsigned integer vector.
+///
+/// @param [in] value The value to to broadcast.
+///
+/// @returns
+/// A 4-dimensional unsigned integer vector with <c><i>value</i></c> in each component.
+///
+/// @ingroup GLSLCore
+FfxUInt32x4 ffxBroadcast4(FfxUInt32 value)
+{
+ return FfxUInt32x4(value, value, value, value);
+}
+
+///
+///
+/// @ingroup GLSLCore
+FfxUInt32 ffxBitfieldExtract(FfxUInt32 src, FfxUInt32 off, FfxUInt32 bits)
+{
+ return bitfieldExtract(src, FfxInt32(off), FfxInt32(bits));
+}
+
+///
+///
+/// @ingroup GLSLCore
+FfxUInt32 ffxBitfieldInsert(FfxUInt32 src, FfxUInt32 ins, FfxUInt32 mask)
+{
+ return (ins & mask) | (src & (~mask));
+}
+
+// Proxy for V_BFI_B32 where the 'mask' is set as 'bits', 'mask=(1<<bits)-1', and 'bits' needs to be an immediate.
+///
+///
+/// @ingroup GLSLCore
+FfxUInt32 ffxBitfieldInsertMask(FfxUInt32 src, FfxUInt32 ins, FfxUInt32 bits)
+{
+ return bitfieldInsert(src, ins, 0, FfxInt32(bits));
+}
+
+/// Compute the linear interopation between two values.
+///
+/// Implemented by calling the GLSL <c><i>mix</i></c> instrinsic function. Implements the
+/// following math:
+///
+/// (1 - t) * x + t * y
+///
+/// @param [in] x The first value to lerp between.
+/// @param [in] y The second value to lerp between.
+/// @param [in] t The value to determine how much of <c><i>x</i></c> and how much of <c><i>y</i></c>.
+///
+/// @returns
+/// A linearly interpolated value between <c><i>x</i></c> and <c><i>y</i></c> according to <c><i>t</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32 ffxLerp(FfxFloat32 x, FfxFloat32 y, FfxFloat32 t)
+{
+ return mix(x, y, t);
+}
+
+/// Compute the linear interopation between two values.
+///
+/// Implemented by calling the GLSL <c><i>mix</i></c> instrinsic function. Implements the
+/// following math:
+///
+/// (1 - t) * x + t * y
+///
+/// @param [in] x The first value to lerp between.
+/// @param [in] y The second value to lerp between.
+/// @param [in] t The value to determine how much of <c><i>x</i></c> and how much of <c><i>y</i></c>.
+///
+/// @returns
+/// A linearly interpolated value between <c><i>x</i></c> and <c><i>y</i></c> according to <c><i>t</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x2 ffxLerp(FfxFloat32x2 x, FfxFloat32x2 y, FfxFloat32 t)
+{
+ return mix(x, y, t);
+}
+
+/// Compute the linear interopation between two values.
+///
+/// Implemented by calling the GLSL <c><i>mix</i></c> instrinsic function. Implements the
+/// following math:
+///
+/// (1 - t) * x + t * y
+///
+/// @param [in] x The first value to lerp between.
+/// @param [in] y The second value to lerp between.
+/// @param [in] t The value to determine how much of <c><i>x</i></c> and how much of <c><i>y</i></c>.
+///
+/// @returns
+/// A linearly interpolated value between <c><i>x</i></c> and <c><i>y</i></c> according to <c><i>t</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x2 ffxLerp(FfxFloat32x2 x, FfxFloat32x2 y, FfxFloat32x2 t)
+{
+ return mix(x, y, t);
+}
+
+/// Compute the linear interopation between two values.
+///
+/// Implemented by calling the GLSL <c><i>mix</i></c> instrinsic function. Implements the
+/// following math:
+///
+/// (1 - t) * x + t * y
+///
+/// @param [in] x The first value to lerp between.
+/// @param [in] y The second value to lerp between.
+/// @param [in] t The value to determine how much of <c><i>x</i></c> and how much of <c><i>y</i></c>.
+///
+/// @returns
+/// A linearly interpolated value between <c><i>x</i></c> and <c><i>y</i></c> according to <c><i>t</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x3 ffxLerp(FfxFloat32x3 x, FfxFloat32x3 y, FfxFloat32 t)
+{
+ return mix(x, y, t);
+}
+
+/// Compute the linear interopation between two values.
+///
+/// Implemented by calling the GLSL <c><i>mix</i></c> instrinsic function. Implements the
+/// following math:
+///
+/// (1 - t) * x + t * y
+///
+/// @param [in] x The first value to lerp between.
+/// @param [in] y The second value to lerp between.
+/// @param [in] t The value to determine how much of <c><i>x</i></c> and how much of <c><i>y</i></c>.
+///
+/// @returns
+/// A linearly interpolated value between <c><i>x</i></c> and <c><i>y</i></c> according to <c><i>t</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x3 ffxLerp(FfxFloat32x3 x, FfxFloat32x3 y, FfxFloat32x3 t)
+{
+ return mix(x, y, t);
+}
+
+/// Compute the linear interopation between two values.
+///
+/// Implemented by calling the GLSL <c><i>mix</i></c> instrinsic function. Implements the
+/// following math:
+///
+/// (1 - t) * x + t * y
+///
+/// @param [in] x The first value to lerp between.
+/// @param [in] y The second value to lerp between.
+/// @param [in] t The value to determine how much of <c><i>x</i></c> and how much of <c><i>y</i></c>.
+///
+/// @returns
+/// A linearly interpolated value between <c><i>x</i></c> and <c><i>y</i></c> according to <c><i>t</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x4 ffxLerp(FfxFloat32x4 x, FfxFloat32x4 y, FfxFloat32 t)
+{
+ return mix(x, y, t);
+}
+
+/// Compute the linear interopation between two values.
+///
+/// Implemented by calling the GLSL <c><i>mix</i></c> instrinsic function. Implements the
+/// following math:
+///
+/// (1 - t) * x + t * y
+///
+/// @param [in] x The first value to lerp between.
+/// @param [in] y The second value to lerp between.
+/// @param [in] t The value to determine how much of <c><i>x</i></c> and how much of <c><i>y</i></c>.
+///
+/// @returns
+/// A linearly interpolated value between <c><i>x</i></c> and <c><i>y</i></c> according to <c><i>t</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x4 ffxLerp(FfxFloat32x4 x, FfxFloat32x4 y, FfxFloat32x4 t)
+{
+ return mix(x, y, t);
+}
+
+/// Compute the maximum of three values.
+///
+/// NOTE: This function should compile down to a single V_MAX3_F32 operation on
+/// GCN or RDNA hardware.
+///
+/// @param [in] x The first value to include in the max calculation.
+/// @param [in] y The second value to include in the max calcuation.
+/// @param [in] z The third value to include in the max calcuation.
+///
+/// @returns
+/// The maximum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32 ffxMax3(FfxFloat32 x, FfxFloat32 y, FfxFloat32 z)
+{
+ return max(x, max(y, z));
+}
+
+/// Compute the maximum of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MAX3_F32</i></c> operation on
+/// GCN or RDNA hardware.
+///
+/// @param [in] x The first value to include in the max calculation.
+/// @param [in] y The second value to include in the max calcuation.
+/// @param [in] z The third value to include in the max calcuation.
+///
+/// @returns
+/// The maximum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x2 ffxMax3(FfxFloat32x2 x, FfxFloat32x2 y, FfxFloat32x2 z)
+{
+ return max(x, max(y, z));
+}
+
+/// Compute the maximum of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MAX3_F32</i></c> operation on
+/// GCN or RDNA hardware.
+///
+/// @param [in] x The first value to include in the max calculation.
+/// @param [in] y The second value to include in the max calcuation.
+/// @param [in] z The third value to include in the max calcuation.
+///
+/// @returns
+/// The maximum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x3 ffxMax3(FfxFloat32x3 x, FfxFloat32x3 y, FfxFloat32x3 z)
+{
+ return max(x, max(y, z));
+}
+
+/// Compute the maximum of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MAX3_F32</i></c> operation on
+/// GCN or RDNA hardware.
+///
+/// @param [in] x The first value to include in the max calculation.
+/// @param [in] y The second value to include in the max calcuation.
+/// @param [in] z The third value to include in the max calcuation.
+///
+/// @returns
+/// The maximum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x4 ffxMax3(FfxFloat32x4 x, FfxFloat32x4 y, FfxFloat32x4 z)
+{
+ return max(x, max(y, z));
+}
+
+/// Compute the maximum of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MAX3_F32</i></c> operation on
+/// GCN or RDNA hardware.
+///
+/// @param [in] x The first value to include in the max calculation.
+/// @param [in] y The second value to include in the max calcuation.
+/// @param [in] z The third value to include in the max calcuation.
+///
+/// @returns
+/// The maximum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxUInt32 ffxMax3(FfxUInt32 x, FfxUInt32 y, FfxUInt32 z)
+{
+ return max(x, max(y, z));
+}
+
+/// Compute the maximum of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MAX3_F32</i></c> operation on
+/// GCN or RDNA hardware.
+///
+/// @param [in] x The first value to include in the max calculation.
+/// @param [in] y The second value to include in the max calcuation.
+/// @param [in] z The third value to include in the max calcuation.
+///
+/// @returns
+/// The maximum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxUInt32x2 ffxMax3(FfxUInt32x2 x, FfxUInt32x2 y, FfxUInt32x2 z)
+{
+ return max(x, max(y, z));
+}
+
+/// Compute the maximum of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MAX3_F32</i></c> operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the max calculation.
+/// @param [in] y The second value to include in the max calcuation.
+/// @param [in] z The third value to include in the max calcuation.
+///
+/// @returns
+/// The maximum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxUInt32x3 ffxMax3(FfxUInt32x3 x, FfxUInt32x3 y, FfxUInt32x3 z)
+{
+ return max(x, max(y, z));
+}
+
+/// Compute the maximum of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MAX3_F32</i></c> operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the max calculation.
+/// @param [in] y The second value to include in the max calcuation.
+/// @param [in] z The third value to include in the max calcuation.
+///
+/// @returns
+/// The maximum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxUInt32x4 ffxMax3(FfxUInt32x4 x, FfxUInt32x4 y, FfxUInt32x4 z)
+{
+ return max(x, max(y, z));
+}
+
+/// Compute the median of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MED3_F32</i></c> operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the median calculation.
+/// @param [in] y The second value to include in the median calcuation.
+/// @param [in] z The third value to include in the median calcuation.
+///
+/// @returns
+/// The median value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32 ffxMed3(FfxFloat32 x, FfxFloat32 y, FfxFloat32 z)
+{
+ return max(min(x, y), min(max(x, y), z));
+}
+
+/// Compute the median of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MED3_F32</i></c> operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the median calculation.
+/// @param [in] y The second value to include in the median calcuation.
+/// @param [in] z The third value to include in the median calcuation.
+///
+/// @returns
+/// The median value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x2 ffxMed3(FfxFloat32x2 x, FfxFloat32x2 y, FfxFloat32x2 z)
+{
+ return max(min(x, y), min(max(x, y), z));
+}
+
+/// Compute the median of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MED3_F32</i></c> operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the median calculation.
+/// @param [in] y The second value to include in the median calcuation.
+/// @param [in] z The third value to include in the median calcuation.
+///
+/// @returns
+/// The median value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x3 ffxMed3(FfxFloat32x3 x, FfxFloat32x3 y, FfxFloat32x3 z)
+{
+ return max(min(x, y), min(max(x, y), z));
+}
+
+/// Compute the median of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MED3_F32</i></c> operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the median calculation.
+/// @param [in] y The second value to include in the median calcuation.
+/// @param [in] z The third value to include in the median calcuation.
+///
+/// @returns
+/// The median value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x4 ffxMed3(FfxFloat32x4 x, FfxFloat32x4 y, FfxFloat32x4 z)
+{
+ return max(min(x, y), min(max(x, y), z));
+}
+
+/// Compute the median of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MED3_I32</i></c> operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the median calculation.
+/// @param [in] y The second value to include in the median calcuation.
+/// @param [in] z The third value to include in the median calcuation.
+///
+/// @returns
+/// The median value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxInt32 ffxMed3(FfxInt32 x, FfxInt32 y, FfxInt32 z)
+{
+ return max(min(x, y), min(max(x, y), z));
+}
+
+/// Compute the median of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MED3_I32</i></c> operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the median calculation.
+/// @param [in] y The second value to include in the median calcuation.
+/// @param [in] z The third value to include in the median calcuation.
+///
+/// @returns
+/// The median value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxInt32x2 ffxMed3(FfxInt32x2 x, FfxInt32x2 y, FfxInt32x2 z)
+{
+ return max(min(x, y), min(max(x, y), z));
+}
+
+/// Compute the median of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MED3_I32</i></c> operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the median calculation.
+/// @param [in] y The second value to include in the median calcuation.
+/// @param [in] z The third value to include in the median calcuation.
+///
+/// @returns
+/// The median value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxInt32x3 ffxMed3(FfxInt32x3 x, FfxInt32x3 y, FfxInt32x3 z)
+{
+ return max(min(x, y), min(max(x, y), z));
+}
+
+/// Compute the median of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MED3_I32</i></c> operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the median calculation.
+/// @param [in] y The second value to include in the median calcuation.
+/// @param [in] z The third value to include in the median calcuation.
+///
+/// @returns
+/// The median value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxInt32x4 ffxMed3(FfxInt32x4 x, FfxInt32x4 y, FfxInt32x4 z)
+{
+ return max(min(x, y), min(max(x, y), z));
+}
+
+
+/// Compute the minimum of three values.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MIN3_F32</i></c> operation on
+/// GCN and RDNA hardware.
+///
+/// @param [in] x The first value to include in the min calculation.
+/// @param [in] y The second value to include in the min calcuation.
+/// @param [in] z The third value to include in the min calcuation.
+///
+/// @returns
+/// The minimum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32 ffxMin3(FfxFloat32 x, FfxFloat32 y, FfxFloat32 z)
+{
+ return min(x, min(y, z));
+}
+
+/// Compute the minimum of three values.
+///
+/// NOTE: This function should compile down to a single V_MIN3_F32 operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the min calculation.
+/// @param [in] y The second value to include in the min calcuation.
+/// @param [in] z The third value to include in the min calcuation.
+///
+/// @returns
+/// The minimum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x2 ffxMin3(FfxFloat32x2 x, FfxFloat32x2 y, FfxFloat32x2 z)
+{
+ return min(x, min(y, z));
+}
+
+/// Compute the minimum of three values.
+///
+/// NOTE: This function should compile down to a single V_MIN3_F32 operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the min calculation.
+/// @param [in] y The second value to include in the min calcuation.
+/// @param [in] z The third value to include in the min calcuation.
+///
+/// @returns
+/// The minimum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x3 ffxMin3(FfxFloat32x3 x, FfxFloat32x3 y, FfxFloat32x3 z)
+{
+ return min(x, min(y, z));
+}
+
+/// Compute the minimum of three values.
+///
+/// NOTE: This function should compile down to a single V_MIN3_F32 operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the min calculation.
+/// @param [in] y The second value to include in the min calcuation.
+/// @param [in] z The third value to include in the min calcuation.
+///
+/// @returns
+/// The minimum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x4 ffxMin3(FfxFloat32x4 x, FfxFloat32x4 y, FfxFloat32x4 z)
+{
+ return min(x, min(y, z));
+}
+
+/// Compute the minimum of three values.
+///
+/// NOTE: This function should compile down to a single V_MIN3_F32 operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the min calculation.
+/// @param [in] y The second value to include in the min calcuation.
+/// @param [in] z The third value to include in the min calcuation.
+///
+/// @returns
+/// The minimum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxUInt32 ffxMin3(FfxUInt32 x, FfxUInt32 y, FfxUInt32 z)
+{
+ return min(x, min(y, z));
+}
+
+/// Compute the minimum of three values.
+///
+/// NOTE: This function should compile down to a single V_MIN3_F32 operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the min calculation.
+/// @param [in] y The second value to include in the min calcuation.
+/// @param [in] z The third value to include in the min calcuation.
+///
+/// @returns
+/// The minimum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxUInt32x2 ffxMin3(FfxUInt32x2 x, FfxUInt32x2 y, FfxUInt32x2 z)
+{
+ return min(x, min(y, z));
+}
+
+/// Compute the minimum of three values.
+///
+/// NOTE: This function should compile down to a single V_MIN3_F32 operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the min calculation.
+/// @param [in] y The second value to include in the min calcuation.
+/// @param [in] z The third value to include in the min calcuation.
+///
+/// @returns
+/// The minimum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxUInt32x3 ffxMin3(FfxUInt32x3 x, FfxUInt32x3 y, FfxUInt32x3 z)
+{
+ return min(x, min(y, z));
+}
+
+/// Compute the minimum of three values.
+///
+/// NOTE: This function should compile down to a single V_MIN3_F32 operation on
+/// GCN/RDNA hardware.
+///
+/// @param [in] x The first value to include in the min calculation.
+/// @param [in] y The second value to include in the min calcuation.
+/// @param [in] z The third value to include in the min calcuation.
+///
+/// @returns
+/// The minimum value of <c><i>x</i></c>, <c><i>y</i></c>, and <c><i>z</i></c>.
+///
+/// @ingroup GLSLCore
+FfxUInt32x4 ffxMin3(FfxUInt32x4 x, FfxUInt32x4 y, FfxUInt32x4 z)
+{
+ return min(x, min(y, z));
+}
+
+/// Compute the reciprocal of a value.
+///
+/// NOTE: This function is only provided for GLSL. In HLSL the intrinsic function <c><i>rcp</i></c> can be used.
+///
+/// @param [in] x The value to compute the reciprocal for.
+///
+/// @returns
+/// The reciprocal value of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32 ffxReciprocal(FfxFloat32 x)
+{
+ return FfxFloat32(1.0) / x;
+}
+
+/// Compute the reciprocal of a value.
+///
+/// NOTE: This function is only provided for GLSL. In HLSL the intrinsic function <c><i>rcp</i></c> can be used.
+///
+/// @param [in] x The value to compute the reciprocal for.
+///
+/// @returns
+/// The reciprocal value of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x2 ffxReciprocal(FfxFloat32x2 x)
+{
+ return ffxBroadcast2(1.0) / x;
+}
+
+/// Compute the reciprocal of a value.
+///
+/// NOTE: This function is only provided for GLSL. In HLSL the intrinsic function <c><i>rcp</i></c> can be used.
+///
+/// @param [in] x The value to compute the reciprocal for.
+///
+/// @returns
+/// The reciprocal value of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x3 ffxReciprocal(FfxFloat32x3 x)
+{
+ return ffxBroadcast3(1.0) / x;
+}
+
+/// Compute the reciprocal of a value.
+///
+/// NOTE: This function is only provided for GLSL. In HLSL the intrinsic function <c><i>rcp</i></c> can be used.
+///
+/// @param [in] x The value to compute the reciprocal for.
+///
+/// @returns
+/// The reciprocal value of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x4 ffxReciprocal(FfxFloat32x4 x)
+{
+ return ffxBroadcast4(1.0) / x;
+}
+
+/// Compute the reciprocal square root of a value.
+///
+/// NOTE: This function is only provided for GLSL. In HLSL the intrinsic function <c><i>rsqrt</i></c> can be used.
+///
+/// @param [in] x The value to compute the reciprocal for.
+///
+/// @returns
+/// The reciprocal square root value of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32 ffxRsqrt(FfxFloat32 x)
+{
+ return FfxFloat32(1.0) / ffxSqrt(x);
+}
+
+/// Compute the reciprocal square root of a value.
+///
+/// NOTE: This function is only provided for GLSL. In HLSL the intrinsic function <c><i>rsqrt</i></c> can be used.
+///
+/// @param [in] x The value to compute the reciprocal for.
+///
+/// @returns
+/// The reciprocal square root value of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x2 ffxRsqrt(FfxFloat32x2 x)
+{
+ return ffxBroadcast2(1.0) / ffxSqrt(x);
+}
+
+/// Compute the reciprocal square root of a value.
+///
+/// NOTE: This function is only provided for GLSL. In HLSL the intrinsic function <c><i>rsqrt</i></c> can be used.
+///
+/// @param [in] x The value to compute the reciprocal for.
+///
+/// @returns
+/// The reciprocal square root value of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x3 ffxRsqrt(FfxFloat32x3 x)
+{
+ return ffxBroadcast3(1.0) / ffxSqrt(x);
+}
+
+/// Compute the reciprocal square root of a value.
+///
+/// NOTE: This function is only provided for GLSL. In HLSL the intrinsic function <c><i>rsqrt</i></c> can be used.
+///
+/// @param [in] x The value to compute the reciprocal for.
+///
+/// @returns
+/// The reciprocal square root value of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x4 rsqrt(FfxFloat32x4 x)
+{
+ return ffxBroadcast4(1.0) / ffxSqrt(x);
+}
+
+/// Clamp a value to a [0..1] range.
+///
+/// @param [in] x The value to clamp to [0..1] range.
+///
+/// @returns
+/// The clamped version of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32 ffxSaturate(FfxFloat32 x)
+{
+ return clamp(x, FfxFloat32(0.0), FfxFloat32(1.0));
+}
+
+/// Clamp a value to a [0..1] range.
+///
+/// @param [in] x The value to clamp to [0..1] range.
+///
+/// @returns
+/// The clamped version of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x2 ffxSaturate(FfxFloat32x2 x)
+{
+ return clamp(x, ffxBroadcast2(0.0), ffxBroadcast2(1.0));
+}
+
+/// Clamp a value to a [0..1] range.
+///
+/// @param [in] x The value to clamp to [0..1] range.
+///
+/// @returns
+/// The clamped version of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x3 ffxSaturate(FfxFloat32x3 x)
+{
+ return clamp(x, ffxBroadcast3(0.0), ffxBroadcast3(1.0));
+}
+
+/// Clamp a value to a [0..1] range.
+///
+/// @param [in] x The value to clamp to [0..1] range.
+///
+/// @returns
+/// The clamped version of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x4 ffxSaturate(FfxFloat32x4 x)
+{
+ return clamp(x, ffxBroadcast4(0.0), ffxBroadcast4(1.0));
+}
+
+/// Compute the factional part of a decimal value.
+///
+/// This function calculates <c><i>x - floor(x)</i></c>. Where <c><i>floor</i></c> is the intrinsic HLSL function.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MAX3_F32</i></c> operation on GCN/RDNA hardware. It is
+/// worth further noting that this function is intentionally distinct from the HLSL <c><i>frac</i></c> intrinsic
+/// function.
+///
+/// @param [in] x The value to compute the fractional part from.
+///
+/// @returns
+/// The fractional part of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32 ffxFract(FfxFloat32 x)
+{
+ return fract(x);
+}
+
+/// Compute the factional part of a decimal value.
+///
+/// This function calculates <c><i>x - floor(x)</i></c>. Where <c><i>floor</i></c> is the intrinsic HLSL function.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MAX3_F32</i></c> operation on GCN/RDNA hardware. It is
+/// worth further noting that this function is intentionally distinct from the HLSL <c><i>frac</i></c> intrinsic
+/// function.
+///
+/// @param [in] x The value to compute the fractional part from.
+///
+/// @returns
+/// The fractional part of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x2 ffxFract(FfxFloat32x2 x)
+{
+ return fract(x);
+}
+
+/// Compute the factional part of a decimal value.
+///
+/// This function calculates <c><i>x - floor(x)</i></c>. Where <c><i>floor</i></c> is the intrinsic HLSL function.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MAX3_F32</i></c> operation on GCN/RDNA hardware. It is
+/// worth further noting that this function is intentionally distinct from the HLSL <c><i>frac</i></c> intrinsic
+/// function.
+///
+/// @param [in] x The value to compute the fractional part from.
+///
+/// @returns
+/// The fractional part of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x3 ffxFract(FfxFloat32x3 x)
+{
+ return fract(x);
+}
+
+/// Compute the factional part of a decimal value.
+///
+/// This function calculates <c><i>x - floor(x)</i></c>. Where <c><i>floor</i></c> is the intrinsic HLSL function.
+///
+/// NOTE: This function should compile down to a single <c><i>V_MAX3_F32</i></c> operation on GCN/RDNA hardware. It is
+/// worth further noting that this function is intentionally distinct from the HLSL <c><i>frac</i></c> intrinsic
+/// function.
+///
+/// @param [in] x The value to compute the fractional part from.
+///
+/// @returns
+/// The fractional part of <c><i>x</i></c>.
+///
+/// @ingroup GLSLCore
+FfxFloat32x4 ffxFract(FfxFloat32x4 x)
+{
+ return fract(x);
+}
+
+/// Rounds to the nearest integer. In case the fractional part is 0.5, it will round to the nearest even integer.
+///
+/// @param [in] x The value to be rounded.
+///
+/// @returns
+/// The nearest integer from <c><i>x</i></c>. The nearest even integer from <c><i>x</i></c> if equidistant from 2 integer.
+///
+/// @ingroup GLSLCore
+FfxFloat32 ffxRound(FfxFloat32 x)
+{
+ return roundEven(x);
+}
+
+/// Rounds to the nearest integer. In case the fractional part is 0.5, it will round to the nearest even integer.
+///
+/// @param [in] x The value to be rounded.
+///
+/// @returns
+/// The nearest integer from <c><i>x</i></c>. The nearest even integer from <c><i>x</i></c> if equidistant from 2 integer.
+///
+/// @ingroup GLSLCore
+FfxFloat32x2 ffxRound(FfxFloat32x2 x)
+{
+ return roundEven(x);
+}
+
+/// Rounds to the nearest integer. In case the fractional part is 0.5, it will round to the nearest even integer.
+///
+/// @param [in] x The value to be rounded.
+///
+/// @returns
+/// The nearest integer from <c><i>x</i></c>. The nearest even integer from <c><i>x</i></c> if equidistant from 2 integer.
+///
+/// @ingroup GLSLCore
+FfxFloat32x3 ffxRound(FfxFloat32x3 x)
+{
+ return roundEven(x);
+}
+
+/// Rounds to the nearest integer. In case the fractional part is 0.5, it will round to the nearest even integer.
+///
+/// @param [in] x The value to be rounded.
+///
+/// @returns
+/// The nearest integer from <c><i>x</i></c>. The nearest even integer from <c><i>x</i></c> if equidistant from 2 integer.
+///
+/// @ingroup GLSLCore
+FfxFloat32x4 ffxRound(FfxFloat32x4 x)
+{
+ return roundEven(x);
+}
+
+FfxUInt32 ffxAShrSU1(FfxUInt32 a, FfxUInt32 b)
+{
+ return FfxUInt32(FfxInt32(a) >> FfxInt32(b));
+}
+
+FfxUInt32 ffxPackF32(FfxFloat32x2 v){
+ return packHalf2x16(v);
+}
+
+FfxFloat32x2 ffxUnpackF32(FfxUInt32 u){
+ return unpackHalf2x16(u);
+}
+
+FfxUInt32x2 ffxPackF32x2(FfxFloat32x4 v){
+ return FfxUInt32x2(ffxPackF32(v.xy), ffxPackF32(v.zw));
+}
+
+FfxFloat32x4 ffxUnpackF32x2(FfxUInt32x2 a){
+ return FfxFloat32x4(ffxUnpackF32(a.x), ffxUnpackF32(a.y));
+}
+
+/// @brief Inverts the value while avoiding division by zero. If the value is zero, zero is returned.
+/// @param v Value to invert.
+/// @return If v = 0 returns 0. If v != 0 returns 1/v.
+FfxFloat32 ffxInvertSafe(FfxFloat32 v){
+ FfxFloat32 s = sign(v);
+ FfxFloat32 s2 = s*s;
+ return s2/(v + s2 - 1.0);
+}
+
+/// @brief Inverts the value while avoiding division by zero. If the value is zero, zero is returned.
+/// @param v Value to invert.
+/// @return If v = 0 returns 0. If v != 0 returns 1/v.
+FfxFloat32x2 ffxInvertSafe(FfxFloat32x2 v){
+ FfxFloat32x2 s = sign(v);
+ FfxFloat32x2 s2 = s*s;
+ return s2/(v + s2 - FfxFloat32x2(1.0, 1.0));
+}
+
+/// @brief Inverts the value while avoiding division by zero. If the value is zero, zero is returned.
+/// @param v Value to invert.
+/// @return If v = 0 returns 0. If v != 0 returns 1/v.
+FfxFloat32x3 ffxInvertSafe(FfxFloat32x3 v){
+ FfxFloat32x3 s = sign(v);
+ FfxFloat32x3 s2 = s*s;
+ return s2/(v + s2 - FfxFloat32x3(1.0, 1.0, 1.0));
+}
+
+/// @brief Inverts the value while avoiding division by zero. If the value is zero, zero is returned.
+/// @param v Value to invert.
+/// @return If v = 0 returns 0. If v != 0 returns 1/v.
+FfxFloat32x4 ffxInvertSafe(FfxFloat32x4 v){
+ FfxFloat32x4 s = sign(v);
+ FfxFloat32x4 s2 = s*s;
+ return s2/(v + s2 - FfxFloat32x4(1.0, 1.0, 1.0, 1.0));
+}
+#if FFX_HALF
+#define FFX_UINT32_TO_FLOAT16X2(x) unpackFloat2x16(FfxUInt32(x))
+
+FfxUInt32 ffxPackF16(FfxFloat16x2 v){
+ return packHalf2x16(v);
+}
+
+FfxFloat16x2 ffxUnpackF16(FfxUInt32 u){
+ return FfxFloat16x2(unpackHalf2x16(u));
+}
+
+FfxFloat16x4 ffxUint32x2ToFloat16x4(FfxUInt32x2 x)
+{
+ return FfxFloat16x4(unpackFloat2x16(x.x), unpackFloat2x16(x.y));
+}
+#define FFX_UINT32X2_TO_FLOAT16X4(x) ffxUint32x2ToFloat16x4(FfxUInt32x2(x))
+#define FFX_UINT32_TO_UINT16X2(x) unpackUint2x16(FfxUInt32(x))
+#define FFX_UINT32X2_TO_UINT16X4(x) unpackUint4x16(pack64(FfxUInt32x2(x)))
+//------------------------------------------------------------------------------------------------------------------------------
+#define FFX_FLOAT16X2_TO_UINT32(x) packFloat2x16(FfxFloat16x2(x))
+FfxUInt32x2 ffxFloat16x4ToUint32x2(FfxFloat16x4 x)
+{
+ return FfxUInt32x2(packFloat2x16(x.xy), packFloat2x16(x.zw));
+}
+#define FFX_FLOAT16X4_TO_UINT32X2(x) ffxFloat16x4ToUint32x2(FfxFloat16x4(x))
+#define FFX_UINT16X2_TO_UINT32(x) packUint2x16(FfxUInt16x2(x))
+#define FFX_UINT16X4_TO_UINT32X2(x) unpack32(packUint4x16(FfxUInt16x4(x)))
+//==============================================================================================================================
+#define FFX_TO_UINT16(x) halfBitsToUint16(FfxFloat16(x))
+#define FFX_TO_UINT16X2(x) halfBitsToUint16(FfxFloat16x2(x))
+#define FFX_TO_UINT16X3(x) halfBitsToUint16(FfxFloat16x3(x))
+#define FFX_TO_UINT16X4(x) halfBitsToUint16(FfxFloat16x4(x))
+//------------------------------------------------------------------------------------------------------------------------------
+#define FFX_TO_FLOAT16(x) uint16BitsToHalf(FfxUInt16(x))
+#define FFX_TO_FLOAT16X2(x) uint16BitsToHalf(FfxUInt16x2(x))
+#define FFX_TO_FLOAT16X3(x) uint16BitsToHalf(FfxUInt16x3(x))
+#define FFX_TO_FLOAT16X4(x) uint16BitsToHalf(FfxUInt16x4(x))
+//==============================================================================================================================
+FfxFloat16 ffxBroadcastFloat16(FfxFloat16 a)
+{
+ return FfxFloat16(a);
+}
+FfxFloat16x2 ffxBroadcastFloat16x2(FfxFloat16 a)
+{
+ return FfxFloat16x2(a, a);
+}
+FfxFloat16x3 ffxBroadcastFloat16x3(FfxFloat16 a)
+{
+ return FfxFloat16x3(a, a, a);
+}
+FfxFloat16x4 ffxBroadcastFloat16x4(FfxFloat16 a)
+{
+ return FfxFloat16x4(a, a, a, a);
+}
+#define FFX_BROADCAST_FLOAT16(a) FfxFloat16(a)
+#define FFX_BROADCAST_FLOAT16X2(a) FfxFloat16x2(FfxFloat16(a))
+#define FFX_BROADCAST_FLOAT16X3(a) FfxFloat16x3(FfxFloat16(a))
+#define FFX_BROADCAST_FLOAT16X4(a) FfxFloat16x4(FfxFloat16(a))
+//------------------------------------------------------------------------------------------------------------------------------
+FfxInt16 ffxBroadcastInt16(FfxInt16 a)
+{
+ return FfxInt16(a);
+}
+FfxInt16x2 ffxBroadcastInt16x2(FfxInt16 a)
+{
+ return FfxInt16x2(a, a);
+}
+FfxInt16x3 ffxBroadcastInt16x3(FfxInt16 a)
+{
+ return FfxInt16x3(a, a, a);
+}
+FfxInt16x4 ffxBroadcastInt16x4(FfxInt16 a)
+{
+ return FfxInt16x4(a, a, a, a);
+}
+#define FFX_BROADCAST_INT16(a) FfxInt16(a)
+#define FFX_BROADCAST_INT16X2(a) FfxInt16x2(FfxInt16(a))
+#define FFX_BROADCAST_INT16X3(a) FfxInt16x3(FfxInt16(a))
+#define FFX_BROADCAST_INT16X4(a) FfxInt16x4(FfxInt16(a))
+//------------------------------------------------------------------------------------------------------------------------------
+FfxUInt16 ffxBroadcastUInt16(FfxUInt16 a)
+{
+ return FfxUInt16(a);
+}
+FfxUInt16x2 ffxBroadcastUInt16x2(FfxUInt16 a)
+{
+ return FfxUInt16x2(a, a);
+}
+FfxUInt16x3 ffxBroadcastUInt16x3(FfxUInt16 a)
+{
+ return FfxUInt16x3(a, a, a);
+}
+FfxUInt16x4 ffxBroadcastUInt16x4(FfxUInt16 a)
+{
+ return FfxUInt16x4(a, a, a, a);
+}
+#define FFX_BROADCAST_UINT16(a) FfxUInt16(a)
+#define FFX_BROADCAST_UINT16X2(a) FfxUInt16x2(FfxUInt16(a))
+#define FFX_BROADCAST_UINT16X3(a) FfxUInt16x3(FfxUInt16(a))
+#define FFX_BROADCAST_UINT16X4(a) FfxUInt16x4(FfxUInt16(a))
+//==============================================================================================================================
+FfxUInt16 ffxAbsHalf(FfxUInt16 a)
+{
+ return FfxUInt16(abs(FfxInt16(a)));
+}
+FfxUInt16x2 ffxAbsHalf(FfxUInt16x2 a)
+{
+ return FfxUInt16x2(abs(FfxInt16x2(a)));
+}
+FfxUInt16x3 ffxAbsHalf(FfxUInt16x3 a)
+{
+ return FfxUInt16x3(abs(FfxInt16x3(a)));
+}
+FfxUInt16x4 ffxAbsHalf(FfxUInt16x4 a)
+{
+ return FfxUInt16x4(abs(FfxInt16x4(a)));
+}
+//------------------------------------------------------------------------------------------------------------------------------
+FfxFloat16 ffxClampHalf(FfxFloat16 x, FfxFloat16 n, FfxFloat16 m)
+{
+ return clamp(x, n, m);
+}
+FfxFloat16x2 ffxClampHalf(FfxFloat16x2 x, FfxFloat16x2 n, FfxFloat16x2 m)
+{
+ return clamp(x, n, m);
+}
+FfxFloat16x3 ffxClampHalf(FfxFloat16x3 x, FfxFloat16x3 n, FfxFloat16x3 m)
+{
+ return clamp(x, n, m);
+}
+FfxFloat16x4 ffxClampHalf(FfxFloat16x4 x, FfxFloat16x4 n, FfxFloat16x4 m)
+{
+ return clamp(x, n, m);
+}
+//------------------------------------------------------------------------------------------------------------------------------
+FfxFloat16 ffxFract(FfxFloat16 x)
+{
+ return fract(x);
+}
+FfxFloat16x2 ffxFract(FfxFloat16x2 x)
+{
+ return fract(x);
+}
+FfxFloat16x3 ffxFract(FfxFloat16x3 x)
+{
+ return fract(x);
+}
+FfxFloat16x4 ffxFract(FfxFloat16x4 x)
+{
+ return fract(x);
+}
+//------------------------------------------------------------------------------------------------------------------------------
+FfxFloat16 ffxLerp(FfxFloat16 x, FfxFloat16 y, FfxFloat16 a)
+{
+ return mix(x, y, a);
+}
+FfxFloat16x2 ffxLerp(FfxFloat16x2 x, FfxFloat16x2 y, FfxFloat16 a)
+{
+ return mix(x, y, a);
+}
+FfxFloat16x2 ffxLerp(FfxFloat16x2 x, FfxFloat16x2 y, FfxFloat16x2 a)
+{
+ return mix(x, y, a);
+}
+FfxFloat16x3 ffxLerp(FfxFloat16x3 x, FfxFloat16x3 y, FfxFloat16x3 a)
+{
+ return mix(x, y, a);
+}
+FfxFloat16x3 ffxLerp(FfxFloat16x3 x, FfxFloat16x3 y, FfxFloat16 a)
+{
+ return mix(x, y, a);
+}
+FfxFloat16x4 ffxLerp(FfxFloat16x4 x, FfxFloat16x4 y, FfxFloat16 a)
+{
+ return mix(x, y, a);
+}
+FfxFloat16x4 ffxLerp(FfxFloat16x4 x, FfxFloat16x4 y, FfxFloat16x4 a)
+{
+ return mix(x, y, a);
+}
+//------------------------------------------------------------------------------------------------------------------------------
+// No packed version of ffxMax3.
+FfxFloat16 ffxMax3Half(FfxFloat16 x, FfxFloat16 y, FfxFloat16 z)
+{
+ return max(x, max(y, z));
+}
+FfxFloat16x2 ffxMax3Half(FfxFloat16x2 x, FfxFloat16x2 y, FfxFloat16x2 z)
+{
+ return max(x, max(y, z));
+}
+FfxFloat16x3 ffxMax3Half(FfxFloat16x3 x, FfxFloat16x3 y, FfxFloat16x3 z)
+{
+ return max(x, max(y, z));
+}
+FfxFloat16x4 ffxMax3Half(FfxFloat16x4 x, FfxFloat16x4 y, FfxFloat16x4 z)
+{
+ return max(x, max(y, z));
+}
+//------------------------------------------------------------------------------------------------------------------------------
+// No packed version of ffxMin3.
+FfxFloat16 ffxMin3Half(FfxFloat16 x, FfxFloat16 y, FfxFloat16 z)
+{
+ return min(x, min(y, z));
+}
+FfxFloat16x2 ffxMin3Half(FfxFloat16x2 x, FfxFloat16x2 y, FfxFloat16x2 z)
+{
+ return min(x, min(y, z));
+}
+FfxFloat16x3 ffxMin3Half(FfxFloat16x3 x, FfxFloat16x3 y, FfxFloat16x3 z)
+{
+ return min(x, min(y, z));
+}
+FfxFloat16x4 ffxMin3Half(FfxFloat16x4 x, FfxFloat16x4 y, FfxFloat16x4 z)
+{
+ return min(x, min(y, z));
+}
+//------------------------------------------------------------------------------------------------------------------------------
+FfxFloat16 ffxMed3Half(FfxFloat16 x, FfxFloat16 y, FfxFloat16 z)
+{
+ return max(min(x, y), min(max(x, y), z));
+}
+FfxFloat16x2 ffxMed3Half(FfxFloat16x2 x, FfxFloat16x2 y, FfxFloat16x2 z)
+{
+ return max(min(x, y), min(max(x, y), z));
+}
+FfxFloat16x3 ffxMed3Half(FfxFloat16x3 x, FfxFloat16x3 y, FfxFloat16x3 z)
+{
+ return max(min(x, y), min(max(x, y), z));
+}
+FfxFloat16x4 ffxMed3Half(FfxFloat16x4 x, FfxFloat16x4 y, FfxFloat16x4 z)
+{
+ return max(min(x, y), min(max(x, y), z));
+}
+//------------------------------------------------------------------------------------------------------------------------------
+FfxFloat16 ffxReciprocalHalf(FfxFloat16 x)
+{
+ return FFX_BROADCAST_FLOAT16(1.0) / x;
+}
+FfxFloat16x2 ffxReciprocalHalf(FfxFloat16x2 x)
+{
+ return FFX_BROADCAST_FLOAT16X2(1.0) / x;
+}
+FfxFloat16x3 ffxReciprocalHalf(FfxFloat16x3 x)
+{
+ return FFX_BROADCAST_FLOAT16X3(1.0) / x;
+}
+FfxFloat16x4 ffxReciprocalHalf(FfxFloat16x4 x)
+{
+ return FFX_BROADCAST_FLOAT16X4(1.0) / x;
+}
+//------------------------------------------------------------------------------------------------------------------------------
+FfxFloat16 ffxReciprocalSquareRootHalf(FfxFloat16 x)
+{
+ return FFX_BROADCAST_FLOAT16(1.0) / sqrt(x);
+}
+FfxFloat16x2 ffxReciprocalSquareRootHalf(FfxFloat16x2 x)
+{
+ return FFX_BROADCAST_FLOAT16X2(1.0) / sqrt(x);
+}
+FfxFloat16x3 ffxReciprocalSquareRootHalf(FfxFloat16x3 x)
+{
+ return FFX_BROADCAST_FLOAT16X3(1.0) / sqrt(x);
+}
+FfxFloat16x4 ffxReciprocalSquareRootHalf(FfxFloat16x4 x)
+{
+ return FFX_BROADCAST_FLOAT16X4(1.0) / sqrt(x);
+}
+//------------------------------------------------------------------------------------------------------------------------------
+FfxFloat16 ffxSaturate(FfxFloat16 x)
+{
+ return clamp(x, FFX_BROADCAST_FLOAT16(0.0), FFX_BROADCAST_FLOAT16(1.0));
+}
+FfxFloat16x2 ffxSaturate(FfxFloat16x2 x)
+{
+ return clamp(x, FFX_BROADCAST_FLOAT16X2(0.0), FFX_BROADCAST_FLOAT16X2(1.0));
+}
+FfxFloat16x3 ffxSaturate(FfxFloat16x3 x)
+{
+ return clamp(x, FFX_BROADCAST_FLOAT16X3(0.0), FFX_BROADCAST_FLOAT16X3(1.0));
+}
+FfxFloat16x4 ffxSaturate(FfxFloat16x4 x)
+{
+ return clamp(x, FFX_BROADCAST_FLOAT16X4(0.0), FFX_BROADCAST_FLOAT16X4(1.0));
+}
+//------------------------------------------------------------------------------------------------------------------------------
+FfxUInt16 ffxBitShiftRightHalf(FfxUInt16 a, FfxUInt16 b)
+{
+ return FfxUInt16(FfxInt16(a) >> FfxInt16(b));
+}
+FfxUInt16x2 ffxBitShiftRightHalf(FfxUInt16x2 a, FfxUInt16x2 b)
+{
+ return FfxUInt16x2(FfxInt16x2(a) >> FfxInt16x2(b));
+}
+FfxUInt16x3 ffxBitShiftRightHalf(FfxUInt16x3 a, FfxUInt16x3 b)
+{
+ return FfxUInt16x3(FfxInt16x3(a) >> FfxInt16x3(b));
+}
+FfxUInt16x4 ffxBitShiftRightHalf(FfxUInt16x4 a, FfxUInt16x4 b)
+{
+ return FfxUInt16x4(FfxInt16x4(a) >> FfxInt16x4(b));
+}
+#endif // FFX_HALF
+
+#if defined(FFX_WAVE)
+// Where 'x' must be a compile time literal.
+FfxFloat32 ffxWaveXorF1(FfxFloat32 v, FfxUInt32 x)
+{
+ return subgroupShuffleXor(v, x);
+}
+FfxFloat32x2 ffxWaveXorF2(FfxFloat32x2 v, FfxUInt32 x)
+{
+ return subgroupShuffleXor(v, x);
+}
+FfxFloat32x3 ffxWaveXorF3(FfxFloat32x3 v, FfxUInt32 x)
+{
+ return subgroupShuffleXor(v, x);
+}
+FfxFloat32x4 ffxWaveXorF4(FfxFloat32x4 v, FfxUInt32 x)
+{
+ return subgroupShuffleXor(v, x);
+}
+FfxUInt32 ffxWaveXorU1(FfxUInt32 v, FfxUInt32 x)
+{
+ return subgroupShuffleXor(v, x);
+}
+FfxUInt32x2 ffxWaveXorU2(FfxUInt32x2 v, FfxUInt32 x)
+{
+ return subgroupShuffleXor(v, x);
+}
+FfxUInt32x3 ffxWaveXorU3(FfxUInt32x3 v, FfxUInt32 x)
+{
+ return subgroupShuffleXor(v, x);
+}
+FfxUInt32x4 ffxWaveXorU4(FfxUInt32x4 v, FfxUInt32 x)
+{
+ return subgroupShuffleXor(v, x);
+}
+FfxBoolean ffxWaveIsFirstLane()
+{
+ return subgroupElect();
+}
+FfxUInt32 ffxWaveLaneIndex()
+{
+ return gl_SubgroupInvocationID;
+}
+FfxBoolean ffxWaveReadAtLaneIndexB1(FfxBoolean v, FfxUInt32 x )
+{
+ return subgroupShuffle(v, x);
+}
+FfxUInt32 ffxWavePrefixCountBits(FfxBoolean v)
+{
+ return subgroupBallotExclusiveBitCount(subgroupBallot(v));
+}
+FfxUInt32 ffxWaveActiveCountBits(FfxBoolean v)
+{
+ return subgroupBallotBitCount(subgroupBallot(v));
+}
+FfxUInt32 ffxWaveReadLaneFirstU1(FfxUInt32 v)
+{
+ return subgroupBroadcastFirst(v);
+}
+FfxUInt32x2 ffxWaveReadLaneFirstU2(FfxUInt32x2 v)
+{
+ return subgroupBroadcastFirst(v);
+}
+FfxBoolean ffxWaveReadLaneFirstB1(FfxBoolean v)
+{
+ return subgroupBroadcastFirst(v);
+}
+FfxUInt32 ffxWaveOr(FfxUInt32 a)
+{
+ return subgroupOr(a);
+}
+FfxUInt32 ffxWaveMin(FfxUInt32 a)
+{
+ return subgroupMin(a);
+}
+FfxFloat32 ffxWaveMin(FfxFloat32 a)
+{
+ return subgroupMin(a);
+}
+FfxUInt32 ffxWaveMax(FfxUInt32 a)
+{
+ return subgroupMax(a);
+}
+FfxFloat32 ffxWaveMax(FfxFloat32 a)
+{
+ return subgroupMax(a);
+}
+FfxUInt32 ffxWaveSum(FfxUInt32 a)
+{
+ return subgroupAdd(a);
+}
+FfxFloat32 ffxWaveSum(FfxFloat32 a)
+{
+ return subgroupAdd(a);
+}
+FfxUInt32 ffxWaveLaneCount()
+{
+ return gl_SubgroupSize;
+}
+#if defined(FFX_WAVE_ALL_TRUE)
+FfxBoolean ffxWaveAllTrue(FfxBoolean v)
+{
+ return subgroupAll(v);
+}
+#endif
+FfxFloat32 ffxQuadReadX(FfxFloat32 v)
+{
+ return subgroupQuadSwapHorizontal(v);
+}
+FfxFloat32x2 ffxQuadReadX(FfxFloat32x2 v)
+{
+ return subgroupQuadSwapHorizontal(v);
+}
+FfxFloat32 ffxQuadReadY(FfxFloat32 v)
+{
+ return subgroupQuadSwapVertical(v);
+}
+FfxFloat32x2 ffxQuadReadY(FfxFloat32x2 v)
+{
+ return subgroupQuadSwapVertical(v);
+}
+
+//------------------------------------------------------------------------------------------------------------------------------
+#if FFX_HALF
+FfxFloat16x2 ffxWaveXorFloat16x2(FfxFloat16x2 v, FfxUInt32 x)
+{
+ return FFX_UINT32_TO_FLOAT16X2(subgroupShuffleXor(FFX_FLOAT16X2_TO_UINT32(v), x));
+}
+FfxFloat16x4 ffxWaveXorFloat16x4(FfxFloat16x4 v, FfxUInt32 x)
+{
+ return FFX_UINT32X2_TO_FLOAT16X4(subgroupShuffleXor(FFX_FLOAT16X4_TO_UINT32X2(v), x));
+}
+FfxUInt16x2 ffxWaveXorUint16x2(FfxUInt16x2 v, FfxUInt32 x)
+{
+ return FFX_UINT32_TO_UINT16X2(subgroupShuffleXor(FFX_UINT16X2_TO_UINT32(v), x));
+}
+FfxUInt16x4 ffxWaveXorUint16x4(FfxUInt16x4 v, FfxUInt32 x)
+{
+ return FFX_UINT32X2_TO_UINT16X4(subgroupShuffleXor(FFX_UINT16X4_TO_UINT32X2(v), x));
+}
+#endif // FFX_HALF
+#endif // #if defined(FFX_WAVE)
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_gpu_common.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_gpu_common.h
new file mode 100644
index 0000000000..da03b07aaf
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_gpu_common.h
@@ -0,0 +1,2736 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+/// A define for a true value in a boolean expression.
+///
+/// @ingroup GPUCore
+#define FFX_TRUE (true)
+
+/// A define for a false value in a boolean expression.
+///
+/// @ingroup GPUCore
+#define FFX_FALSE (false)
+
+/// A define value for positive infinity.
+///
+/// @ingroup GPUCore
+#define FFX_POSITIVE_INFINITY_FLOAT ffxAsFloat(0x7f800000u)
+
+/// A define value for negative infinity.
+///
+/// @ingroup GPUCore
+#define FFX_NEGATIVE_INFINITY_FLOAT ffxAsFloat(0xff800000u)
+
+/// A define value for PI.
+///
+/// @ingroup GPUCore
+#define FFX_PI (3.14159)
+
+FFX_STATIC const FfxFloat32 FFX_FP16_MIN = 6.10e-05f;
+FFX_STATIC const FfxFloat32 FFX_FP16_MAX = 65504.0f;
+FFX_STATIC const FfxFloat32 FFX_TONEMAP_EPSILON = 1.0f / FFX_FP16_MAX;
+
+#define FFX_HAS_FLAG(v, f) ((v & f) == f)
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxMin(FfxFloat32 x, FfxFloat32 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxMin(FfxFloat32x2 x, FfxFloat32x2 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxMin(FfxFloat32x3 x, FfxFloat32x3 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxMin(FfxFloat32x4 x, FfxFloat32x4 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt32 ffxMin(FfxInt32 x, FfxInt32 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt32x2 ffxMin(FfxInt32x2 x, FfxInt32x2 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt32x3 ffxMin(FfxInt32x3 x, FfxInt32x3 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt32x4 ffxMin(FfxInt32x4 x, FfxInt32x4 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxMin(FfxUInt32 x, FfxUInt32 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxMin(FfxUInt32x2 x, FfxUInt32x2 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt32x3 ffxMin(FfxUInt32x3 x, FfxUInt32x3 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt32x4 ffxMin(FfxUInt32x4 x, FfxUInt32x4 y)
+{
+ return min(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxMax(FfxFloat32 x, FfxFloat32 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxMax(FfxFloat32x2 x, FfxFloat32x2 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxMax(FfxFloat32x3 x, FfxFloat32x3 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxMax(FfxFloat32x4 x, FfxFloat32x4 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt32 ffxMax(FfxInt32 x, FfxInt32 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt32x2 ffxMax(FfxInt32x2 x, FfxInt32x2 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt32x3 ffxMax(FfxInt32x3 x, FfxInt32x3 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt32x4 ffxMax(FfxInt32x4 x, FfxInt32x4 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxMax(FfxUInt32 x, FfxUInt32 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxMax(FfxUInt32x2 x, FfxUInt32x2 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt32x3 ffxMax(FfxUInt32x3 x, FfxUInt32x3 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt32x4 ffxMax(FfxUInt32x4 x, FfxUInt32x4 y)
+{
+ return max(x, y);
+}
+
+/// Compute the value of the first parameter raised to the power of the second.
+///
+/// @param [in] x The value to raise to the power y.
+/// @param [in] y The power to which to raise x.
+///
+/// @returns
+/// The value of the first parameter raised to the power of the second.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxPow(FfxFloat32 x, FfxFloat32 y)
+{
+ return pow(x, y);
+}
+
+/// Compute the value of the first parameter raised to the power of the second.
+///
+/// @param [in] x The value to raise to the power y.
+/// @param [in] y The power to which to raise x.
+///
+/// @returns
+/// The value of the first parameter raised to the power of the second.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxPow(FfxFloat32x2 x, FfxFloat32x2 y)
+{
+ return pow(x, y);
+}
+
+/// Compute the value of the first parameter raised to the power of the second.
+///
+/// @param [in] x The value to raise to the power y.
+/// @param [in] y The power to which to raise x.
+///
+/// @returns
+/// The value of the first parameter raised to the power of the second.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxPow(FfxFloat32x3 x, FfxFloat32x3 y)
+{
+ return pow(x, y);
+}
+
+/// Compute the value of the first parameter raised to the power of the second.
+///
+/// @param [in] x The value to raise to the power y.
+/// @param [in] y The power to which to raise x.
+///
+/// @returns
+/// The value of the first parameter raised to the power of the second.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxPow(FfxFloat32x4 x, FfxFloat32x4 y)
+{
+ return pow(x, y);
+}
+
+/// Compute the square root of a value.
+///
+/// @param [in] x The first value to compute the min of.
+///
+/// @returns
+/// The the square root of <c><i>x</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxSqrt(FfxFloat32 x)
+{
+ return sqrt(x);
+}
+
+/// Compute the square root of a value.
+///
+/// @param [in] x The first value to compute the min of.
+///
+/// @returns
+/// The the square root of <c><i>x</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxSqrt(FfxFloat32x2 x)
+{
+ return sqrt(x);
+}
+
+/// Compute the square root of a value.
+///
+/// @param [in] x The first value to compute the min of.
+///
+/// @returns
+/// The the square root of <c><i>x</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxSqrt(FfxFloat32x3 x)
+{
+ return sqrt(x);
+}
+
+/// Compute the square root of a value.
+///
+/// @param [in] x The first value to compute the min of.
+///
+/// @returns
+/// The the square root of <c><i>x</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxSqrt(FfxFloat32x4 x)
+{
+ return sqrt(x);
+}
+
+/// Copy the sign bit from 's' to positive 'd'.
+///
+/// @param [in] d The value to copy the sign bit into.
+/// @param [in] s The value to copy the sign bit from.
+///
+/// @returns
+/// The value of <c><i>d</i></c> with the sign bit from <c><i>s</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxCopySignBit(FfxFloat32 d, FfxFloat32 s)
+{
+ return ffxAsFloat(ffxAsUInt32(d) | (ffxAsUInt32(s) & FfxUInt32(0x80000000u)));
+}
+
+/// Copy the sign bit from 's' to positive 'd'.
+///
+/// @param [in] d The value to copy the sign bit into.
+/// @param [in] s The value to copy the sign bit from.
+///
+/// @returns
+/// The value of <c><i>d</i></c> with the sign bit from <c><i>s</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxCopySignBit(FfxFloat32x2 d, FfxFloat32x2 s)
+{
+ return ffxAsFloat(ffxAsUInt32(d) | (ffxAsUInt32(s) & ffxBroadcast2(0x80000000u)));
+}
+
+/// Copy the sign bit from 's' to positive 'd'.
+///
+/// @param [in] d The value to copy the sign bit into.
+/// @param [in] s The value to copy the sign bit from.
+///
+/// @returns
+/// The value of <c><i>d</i></c> with the sign bit from <c><i>s</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxCopySignBit(FfxFloat32x3 d, FfxFloat32x3 s)
+{
+ return ffxAsFloat(ffxAsUInt32(d) | (ffxAsUInt32(s) & ffxBroadcast3(0x80000000u)));
+}
+
+/// Copy the sign bit from 's' to positive 'd'.
+///
+/// @param [in] d The value to copy the sign bit into.
+/// @param [in] s The value to copy the sign bit from.
+///
+/// @returns
+/// The value of <c><i>d</i></c> with the sign bit from <c><i>s</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxCopySignBit(FfxFloat32x4 d, FfxFloat32x4 s)
+{
+ return ffxAsFloat(ffxAsUInt32(d) | (ffxAsUInt32(s) & ffxBroadcast4(0x80000000u)));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 0
+/// m >= 0 := 0
+/// m < 0 := 1
+///
+/// Uses the following useful floating point logic,
+/// saturate(+a*(-INF)==-INF) := 0
+/// saturate( 0*(-INF)== NaN) := 0
+/// saturate(-a*(-INF)==+INF) := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against 0.
+///
+/// @returns
+/// 1.0 when the value is negative, or 0.0 when the value is 0 or position.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxIsSigned(FfxFloat32 m)
+{
+ return ffxSaturate(m * FfxFloat32(FFX_NEGATIVE_INFINITY_FLOAT));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 0
+/// m >= 0 := 0
+/// m < 0 := 1
+///
+/// Uses the following useful floating point logic,
+/// saturate(+a*(-INF)==-INF) := 0
+/// saturate( 0*(-INF)== NaN) := 0
+/// saturate(-a*(-INF)==+INF) := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against 0.
+///
+/// @returns
+/// 1.0 when the value is negative, or 0.0 when the value is 0 or position.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxIsSigned(FfxFloat32x2 m)
+{
+ return ffxSaturate(m * ffxBroadcast2(FFX_NEGATIVE_INFINITY_FLOAT));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 0
+/// m >= 0 := 0
+/// m < 0 := 1
+///
+/// Uses the following useful floating point logic,
+/// saturate(+a*(-INF)==-INF) := 0
+/// saturate( 0*(-INF)== NaN) := 0
+/// saturate(-a*(-INF)==+INF) := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against 0.
+///
+/// @returns
+/// 1.0 when the value is negative, or 0.0 when the value is 0 or position.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxIsSigned(FfxFloat32x3 m)
+{
+ return ffxSaturate(m * ffxBroadcast3(FFX_NEGATIVE_INFINITY_FLOAT));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 0
+/// m >= 0 := 0
+/// m < 0 := 1
+///
+/// Uses the following useful floating point logic,
+/// saturate(+a*(-INF)==-INF) := 0
+/// saturate( 0*(-INF)== NaN) := 0
+/// saturate(-a*(-INF)==+INF) := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against for have the sign set.
+///
+/// @returns
+/// 1.0 when the value is negative, or 0.0 when the value is 0 or positive.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxIsSigned(FfxFloat32x4 m)
+{
+ return ffxSaturate(m * ffxBroadcast4(FFX_NEGATIVE_INFINITY_FLOAT));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 1
+/// m > 0 := 0
+/// m <= 0 := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against zero.
+///
+/// @returns
+/// 1.0 when the value is position, or 0.0 when the value is 0 or negative.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxIsGreaterThanZero(FfxFloat32 m)
+{
+ return ffxSaturate(m * FfxFloat32(FFX_POSITIVE_INFINITY_FLOAT));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 1
+/// m > 0 := 0
+/// m <= 0 := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against zero.
+///
+/// @returns
+/// 1.0 when the value is position, or 0.0 when the value is 0 or negative.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxIsGreaterThanZero(FfxFloat32x2 m)
+{
+ return ffxSaturate(m * ffxBroadcast2(FFX_POSITIVE_INFINITY_FLOAT));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 1
+/// m > 0 := 0
+/// m <= 0 := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against zero.
+///
+/// @returns
+/// 1.0 when the value is position, or 0.0 when the value is 0 or negative.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxIsGreaterThanZero(FfxFloat32x3 m)
+{
+ return ffxSaturate(m * ffxBroadcast3(FFX_POSITIVE_INFINITY_FLOAT));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 1
+/// m > 0 := 0
+/// m <= 0 := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against zero.
+///
+/// @returns
+/// 1.0 when the value is position, or 0.0 when the value is 0 or negative.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxIsGreaterThanZero(FfxFloat32x4 m)
+{
+ return ffxSaturate(m * ffxBroadcast4(FFX_POSITIVE_INFINITY_FLOAT));
+}
+
+/// Convert a 32bit floating point value to sortable integer.
+///
+/// - If sign bit=0, flip the sign bit (positives).
+/// - If sign bit=1, flip all bits (negatives).
+///
+/// The function has the side effects that:
+/// - Larger integers are more positive values.
+/// - Float zero is mapped to center of integers (so clear to integer zero is a nice default for atomic max usage).
+///
+/// @param [in] value The floating point value to make sortable.
+///
+/// @returns
+/// The sortable integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxFloatToSortableInteger(FfxUInt32 value)
+{
+ return value ^ ((ffxAShrSU1(value, FfxUInt32(31))) | FfxUInt32(0x80000000));
+}
+
+/// Convert a sortable integer to a 32bit floating point value.
+///
+/// The function has the side effects that:
+/// - If sign bit=1, flip the sign bit (positives).
+/// - If sign bit=0, flip all bits (negatives).
+///
+/// @param [in] value The floating point value to make sortable.
+///
+/// @returns
+/// The sortable integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxSortableIntegerToFloat(FfxUInt32 value)
+{
+ return value ^ ((~ffxAShrSU1(value, FfxUInt32(31))) | FfxUInt32(0x80000000));
+}
+
+/// Calculate a low-quality approximation for the square root of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the square root for.
+///
+/// @returns
+/// An approximation of the square root, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxApproximateSqrt(FfxFloat32 value)
+{
+ return ffxAsFloat((ffxAsUInt32(value) >> FfxUInt32(1)) + FfxUInt32(0x1fbc4639));
+}
+
+/// Calculate a low-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxApproximateReciprocal(FfxFloat32 value)
+{
+ return ffxAsFloat(FfxUInt32(0x7ef07ebb) - ffxAsUInt32(value));
+}
+
+/// Calculate a medium-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to medium quality.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxApproximateReciprocalMedium(FfxFloat32 value)
+{
+ FfxFloat32 b = ffxAsFloat(FfxUInt32(0x7ef19fff) - ffxAsUInt32(value));
+ return b * (-b * value + FfxFloat32(2.0));
+}
+
+/// Calculate a low-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the reciprocal square root for.
+///
+/// @returns
+/// An approximation of the reciprocal square root, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxApproximateReciprocalSquareRoot(FfxFloat32 value)
+{
+ return ffxAsFloat(FfxUInt32(0x5f347d74) - (ffxAsUInt32(value) >> FfxUInt32(1)));
+}
+
+/// Calculate a low-quality approximation for the square root of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the square root for.
+///
+/// @returns
+/// An approximation of the square root, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxApproximateSqrt(FfxFloat32x2 value)
+{
+ return ffxAsFloat((ffxAsUInt32(value) >> ffxBroadcast2(1u)) + ffxBroadcast2(0x1fbc4639u));
+}
+
+/// Calculate a low-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxApproximateReciprocal(FfxFloat32x2 value)
+{
+ return ffxAsFloat(ffxBroadcast2(0x7ef07ebbu) - ffxAsUInt32(value));
+}
+
+/// Calculate a medium-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to medium quality.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxApproximateReciprocalMedium(FfxFloat32x2 value)
+{
+ FfxFloat32x2 b = ffxAsFloat(ffxBroadcast2(0x7ef19fffu) - ffxAsUInt32(value));
+ return b * (-b * value + ffxBroadcast2(2.0f));
+}
+
+/// Calculate a low-quality approximation for the square root of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the square root for.
+///
+/// @returns
+/// An approximation of the square root, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxApproximateReciprocalSquareRoot(FfxFloat32x2 value)
+{
+ return ffxAsFloat(ffxBroadcast2(0x5f347d74u) - (ffxAsUInt32(value) >> ffxBroadcast2(1u)));
+}
+
+/// Calculate a low-quality approximation for the square root of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the square root for.
+///
+/// @returns
+/// An approximation of the square root, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxApproximateSqrt(FfxFloat32x3 value)
+{
+ return ffxAsFloat((ffxAsUInt32(value) >> ffxBroadcast3(1u)) + ffxBroadcast3(0x1fbc4639u));
+}
+
+/// Calculate a low-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxApproximateReciprocal(FfxFloat32x3 value)
+{
+ return ffxAsFloat(ffxBroadcast3(0x7ef07ebbu) - ffxAsUInt32(value));
+}
+
+/// Calculate a medium-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to medium quality.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxApproximateReciprocalMedium(FfxFloat32x3 value)
+{
+ FfxFloat32x3 b = ffxAsFloat(ffxBroadcast3(0x7ef19fffu) - ffxAsUInt32(value));
+ return b * (-b * value + ffxBroadcast3(2.0f));
+}
+
+/// Calculate a low-quality approximation for the square root of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the square root for.
+///
+/// @returns
+/// An approximation of the square root, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxApproximateReciprocalSquareRoot(FfxFloat32x3 value)
+{
+ return ffxAsFloat(ffxBroadcast3(0x5f347d74u) - (ffxAsUInt32(value) >> ffxBroadcast3(1u)));
+}
+
+/// Calculate a low-quality approximation for the square root of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the square root for.
+///
+/// @returns
+/// An approximation of the square root, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxApproximateSqrt(FfxFloat32x4 value)
+{
+ return ffxAsFloat((ffxAsUInt32(value) >> ffxBroadcast4(1u)) + ffxBroadcast4(0x1fbc4639u));
+}
+
+/// Calculate a low-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxApproximateReciprocal(FfxFloat32x4 value)
+{
+ return ffxAsFloat(ffxBroadcast4(0x7ef07ebbu) - ffxAsUInt32(value));
+}
+
+/// Calculate a medium-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to medium quality.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxApproximateReciprocalMedium(FfxFloat32x4 value)
+{
+ FfxFloat32x4 b = ffxAsFloat(ffxBroadcast4(0x7ef19fffu) - ffxAsUInt32(value));
+ return b * (-b * value + ffxBroadcast4(2.0f));
+}
+
+/// Calculate a low-quality approximation for the square root of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] value The value to calculate an approximate to the square root for.
+///
+/// @returns
+/// An approximation of the square root, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxApproximateReciprocalSquareRoot(FfxFloat32x4 value)
+{
+ return ffxAsFloat(ffxBroadcast4(0x5f347d74u) - (ffxAsUInt32(value) >> ffxBroadcast4(1u)));
+}
+
+/// Calculate dot product of 'a' and 'b'.
+///
+/// @param [in] a First vector input.
+/// @param [in] b Second vector input.
+///
+/// @returns
+/// The value of <c><i>a</i></c> dot <c><i>b</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxDot2(FfxFloat32x2 a, FfxFloat32x2 b)
+{
+ return dot(a, b);
+}
+
+/// Calculate dot product of 'a' and 'b'.
+///
+/// @param [in] a First vector input.
+/// @param [in] b Second vector input.
+///
+/// @returns
+/// The value of <c><i>a</i></c> dot <c><i>b</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxDot3(FfxFloat32x3 a, FfxFloat32x3 b)
+{
+ return dot(a, b);
+}
+
+/// Calculate dot product of 'a' and 'b'.
+///
+/// @param [in] a First vector input.
+/// @param [in] b Second vector input.
+///
+/// @returns
+/// The value of <c><i>a</i></c> dot <c><i>b</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxDot4(FfxFloat32x4 a, FfxFloat32x4 b)
+{
+ return dot(a, b);
+}
+
+
+/// Compute an approximate conversion from PQ to Gamma2 space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between PQ and Gamma2.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into Gamma2.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxApproximatePQToGamma2Medium(FfxFloat32 a)
+{
+ return a * a * a * a;
+}
+
+/// Compute an approximate conversion from PQ to linear space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between PQ and linear.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into linear.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxApproximatePQToLinear(FfxFloat32 a)
+{
+ return a * a * a * a * a * a * a * a;
+}
+
+/// Compute an approximate conversion from gamma2 to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between gamma2 and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxApproximateGamma2ToPQ(FfxFloat32 a)
+{
+ return ffxAsFloat((ffxAsUInt32(a) >> FfxUInt32(2)) + FfxUInt32(0x2F9A4E46));
+}
+
+/// Compute a more accurate approximate conversion from gamma2 to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between gamma2 and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxApproximateGamma2ToPQMedium(FfxFloat32 a)
+{
+ FfxFloat32 b = ffxAsFloat((ffxAsUInt32(a) >> FfxUInt32(2)) + FfxUInt32(0x2F9A4E46));
+ FfxFloat32 b4 = b * b * b * b;
+ return b - b * (b4 - a) / (FfxFloat32(4.0) * b4);
+}
+
+/// Compute a high accuracy approximate conversion from gamma2 to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between gamma2 and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxApproximateGamma2ToPQHigh(FfxFloat32 a)
+{
+ return ffxSqrt(ffxSqrt(a));
+}
+
+/// Compute an approximate conversion from linear to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between linear and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxApproximateLinearToPQ(FfxFloat32 a)
+{
+ return ffxAsFloat((ffxAsUInt32(a) >> FfxUInt32(3)) + FfxUInt32(0x378D8723));
+}
+
+/// Compute a more accurate approximate conversion from linear to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between linear and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxApproximateLinearToPQMedium(FfxFloat32 a)
+{
+ FfxFloat32 b = ffxAsFloat((ffxAsUInt32(a) >> FfxUInt32(3)) + FfxUInt32(0x378D8723));
+ FfxFloat32 b8 = b * b * b * b * b * b * b * b;
+ return b - b * (b8 - a) / (FfxFloat32(8.0) * b8);
+}
+
+/// Compute a very accurate approximate conversion from linear to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between linear and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxApproximateLinearToPQHigh(FfxFloat32 a)
+{
+ return ffxSqrt(ffxSqrt(ffxSqrt(a)));
+}
+
+/// Compute an approximate conversion from PQ to Gamma2 space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between PQ and Gamma2.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into Gamma2.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxApproximatePQToGamma2Medium(FfxFloat32x2 a)
+{
+ return a * a * a * a;
+}
+
+/// Compute an approximate conversion from PQ to linear space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between PQ and linear.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into linear.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxApproximatePQToLinear(FfxFloat32x2 a)
+{
+ return a * a * a * a * a * a * a * a;
+}
+
+/// Compute an approximate conversion from gamma2 to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between gamma2 and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxApproximateGamma2ToPQ(FfxFloat32x2 a)
+{
+ return ffxAsFloat((ffxAsUInt32(a) >> ffxBroadcast2(2u)) + ffxBroadcast2(0x2F9A4E46u));
+}
+
+/// Compute a more accurate approximate conversion from gamma2 to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between gamma2 and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxApproximateGamma2ToPQMedium(FfxFloat32x2 a)
+{
+ FfxFloat32x2 b = ffxAsFloat((ffxAsUInt32(a) >> ffxBroadcast2(2u)) + ffxBroadcast2(0x2F9A4E46u));
+ FfxFloat32x2 b4 = b * b * b * b;
+ return b - b * (b4 - a) / (FfxFloat32(4.0) * b4);
+}
+
+/// Compute a high accuracy approximate conversion from gamma2 to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between gamma2 and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxApproximateGamma2ToPQHigh(FfxFloat32x2 a)
+{
+ return ffxSqrt(ffxSqrt(a));
+}
+
+/// Compute an approximate conversion from linear to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between linear and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxApproximateLinearToPQ(FfxFloat32x2 a)
+{
+ return ffxAsFloat((ffxAsUInt32(a) >> ffxBroadcast2(3u)) + ffxBroadcast2(0x378D8723u));
+}
+
+/// Compute a more accurate approximate conversion from linear to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between linear and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxApproximateLinearToPQMedium(FfxFloat32x2 a)
+{
+ FfxFloat32x2 b = ffxAsFloat((ffxAsUInt32(a) >> ffxBroadcast2(3u)) + ffxBroadcast2(0x378D8723u));
+ FfxFloat32x2 b8 = b * b * b * b * b * b * b * b;
+ return b - b * (b8 - a) / (FfxFloat32(8.0) * b8);
+}
+
+/// Compute a very accurate approximate conversion from linear to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between linear and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxApproximateLinearToPQHigh(FfxFloat32x2 a)
+{
+ return ffxSqrt(ffxSqrt(ffxSqrt(a)));
+}
+
+/// Compute an approximate conversion from PQ to Gamma2 space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between PQ and Gamma2.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into Gamma2.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxApproximatePQToGamma2Medium(FfxFloat32x3 a)
+{
+ return a * a * a * a;
+}
+
+/// Compute an approximate conversion from PQ to linear space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between PQ and linear.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into linear.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxApproximatePQToLinear(FfxFloat32x3 a)
+{
+ return a * a * a * a * a * a * a * a;
+}
+
+/// Compute an approximate conversion from gamma2 to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between gamma2 and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxApproximateGamma2ToPQ(FfxFloat32x3 a)
+{
+ return ffxAsFloat((ffxAsUInt32(a) >> ffxBroadcast3(2u)) + ffxBroadcast3(0x2F9A4E46u));
+}
+
+/// Compute a more accurate approximate conversion from gamma2 to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between gamma2 and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxApproximateGamma2ToPQMedium(FfxFloat32x3 a)
+{
+ FfxFloat32x3 b = ffxAsFloat((ffxAsUInt32(a) >> ffxBroadcast3(2u)) + ffxBroadcast3(0x2F9A4E46u));
+ FfxFloat32x3 b4 = b * b * b * b;
+ return b - b * (b4 - a) / (FfxFloat32(4.0) * b4);
+}
+
+/// Compute a high accuracy approximate conversion from gamma2 to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between gamma2 and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxApproximateGamma2ToPQHigh(FfxFloat32x3 a)
+{
+ return ffxSqrt(ffxSqrt(a));
+}
+
+/// Compute an approximate conversion from linear to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between linear and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxApproximateLinearToPQ(FfxFloat32x3 a)
+{
+ return ffxAsFloat((ffxAsUInt32(a) >> ffxBroadcast3(3u)) + ffxBroadcast3(0x378D8723u));
+}
+
+/// Compute a more accurate approximate conversion from linear to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between linear and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxApproximateLinearToPQMedium(FfxFloat32x3 a)
+{
+ FfxFloat32x3 b = ffxAsFloat((ffxAsUInt32(a) >> ffxBroadcast3(3u)) + ffxBroadcast3(0x378D8723u));
+ FfxFloat32x3 b8 = b * b * b * b * b * b * b * b;
+ return b - b * (b8 - a) / (FfxFloat32(8.0) * b8);
+}
+
+/// Compute a very accurate approximate conversion from linear to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between linear and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxApproximateLinearToPQHigh(FfxFloat32x3 a)
+{
+ return ffxSqrt(ffxSqrt(ffxSqrt(a)));
+}
+
+/// Compute an approximate conversion from PQ to Gamma2 space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between PQ and Gamma2.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into Gamma2.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxApproximatePQToGamma2Medium(FfxFloat32x4 a)
+{
+ return a * a * a * a;
+}
+
+/// Compute an approximate conversion from PQ to linear space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between PQ and linear.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into linear.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxApproximatePQToLinear(FfxFloat32x4 a)
+{
+ return a * a * a * a * a * a * a * a;
+}
+
+/// Compute an approximate conversion from gamma2 to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between gamma2 and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxApproximateGamma2ToPQ(FfxFloat32x4 a)
+{
+ return ffxAsFloat((ffxAsUInt32(a) >> ffxBroadcast4(2u)) + ffxBroadcast4(0x2F9A4E46u));
+}
+
+/// Compute a more accurate approximate conversion from gamma2 to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between gamma2 and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxApproximateGamma2ToPQMedium(FfxFloat32x4 a)
+{
+ FfxFloat32x4 b = ffxAsFloat((ffxAsUInt32(a) >> ffxBroadcast4(2u)) + ffxBroadcast4(0x2F9A4E46u));
+ FfxFloat32x4 b4 = b * b * b * b * b * b * b * b;
+ return b - b * (b4 - a) / (FfxFloat32(4.0) * b4);
+}
+
+/// Compute a high accuracy approximate conversion from gamma2 to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between gamma2 and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxApproximateGamma2ToPQHigh(FfxFloat32x4 a)
+{
+ return ffxSqrt(ffxSqrt(a));
+}
+
+/// Compute an approximate conversion from linear to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between linear and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxApproximateLinearToPQ(FfxFloat32x4 a)
+{
+ return ffxAsFloat((ffxAsUInt32(a) >> ffxBroadcast4(3u)) + ffxBroadcast4(0x378D8723u));
+}
+
+/// Compute a more accurate approximate conversion from linear to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between linear and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxApproximateLinearToPQMedium(FfxFloat32x4 a)
+{
+ FfxFloat32x4 b = ffxAsFloat((ffxAsUInt32(a) >> ffxBroadcast4(3u)) + ffxBroadcast4(0x378D8723u));
+ FfxFloat32x4 b8 = b * b * b * b * b * b * b * b;
+ return b - b * (b8 - a) / (FfxFloat32(8.0) * b8);
+}
+
+/// Compute a very accurate approximate conversion from linear to PQ space.
+///
+/// PQ is very close to x^(1/8). The functions below Use the fast FfxFloat32 approximation method to do
+/// PQ conversions to and from Gamma2 (4th power and fast 4th root), and PQ to and from Linear
+/// (8th power and fast 8th root). The maximum error is approximately 0.2%.
+///
+/// @param a The value to convert between linear and PQ.
+///
+/// @returns
+/// The value <c><i>a</i></c> converted into PQ.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxApproximateLinearToPQHigh(FfxFloat32x4 a)
+{
+ return ffxSqrt(ffxSqrt(ffxSqrt(a)));
+}
+
+// An approximation of sine.
+//
+// Valid input range is {-1 to 1} representing {0 to 2 pi}, and the output range
+// is {-1/4 to 1/4} representing {-1 to 1}.
+//
+// @param [in] value The value to calculate approximate sine for.
+//
+// @returns
+// The approximate sine of <c><i>value</i></c>.
+FfxFloat32 ffxParabolicSin(FfxFloat32 value)
+{
+ return value * abs(value) - value;
+}
+
+// An approximation of sine.
+//
+// Valid input range is {-1 to 1} representing {0 to 2 pi}, and the output range
+// is {-1/4 to 1/4} representing {-1 to 1}.
+//
+// @param [in] value The value to calculate approximate sine for.
+//
+// @returns
+// The approximate sine of <c><i>value</i></c>.
+FfxFloat32x2 ffxParabolicSin(FfxFloat32x2 x)
+{
+ return x * abs(x) - x;
+}
+
+// An approximation of cosine.
+//
+// Valid input range is {-1 to 1} representing {0 to 2 pi}, and the output range
+// is {-1/4 to 1/4} representing {-1 to 1}.
+//
+// @param [in] value The value to calculate approximate cosine for.
+//
+// @returns
+// The approximate cosine of <c><i>value</i></c>.
+FfxFloat32 ffxParabolicCos(FfxFloat32 x)
+{
+ x = ffxFract(x * FfxFloat32(0.5) + FfxFloat32(0.75));
+ x = x * FfxFloat32(2.0) - FfxFloat32(1.0);
+ return ffxParabolicSin(x);
+}
+
+// An approximation of cosine.
+//
+// Valid input range is {-1 to 1} representing {0 to 2 pi}, and the output range
+// is {-1/4 to 1/4} representing {-1 to 1}.
+//
+// @param [in] value The value to calculate approximate cosine for.
+//
+// @returns
+// The approximate cosine of <c><i>value</i></c>.
+FfxFloat32x2 ffxParabolicCos(FfxFloat32x2 x)
+{
+ x = ffxFract(x * ffxBroadcast2(0.5f) + ffxBroadcast2(0.75f));
+ x = x * ffxBroadcast2(2.0f) - ffxBroadcast2(1.0f);
+ return ffxParabolicSin(x);
+}
+
+// An approximation of both sine and cosine.
+//
+// Valid input range is {-1 to 1} representing {0 to 2 pi}, and the output range
+// is {-1/4 to 1/4} representing {-1 to 1}.
+//
+// @param [in] value The value to calculate approximate cosine for.
+//
+// @returns
+// A <c><i>FfxFloat32x2</i></c> containing approximations of both sine and cosine of <c><i>value</i></c>.
+FfxFloat32x2 ffxParabolicSinCos(FfxFloat32 x)
+{
+ FfxFloat32 y = ffxFract(x * FfxFloat32(0.5) + FfxFloat32(0.75));
+ y = y * FfxFloat32(2.0) - FfxFloat32(1.0);
+ return ffxParabolicSin(FfxFloat32x2(x, y));
+}
+
+/// Conditional free logic AND operation using values.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+///
+/// @returns
+/// Result of the AND operation.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxZeroOneAnd(FfxUInt32 x, FfxUInt32 y)
+{
+ return min(x, y);
+}
+
+/// Conditional free logic AND operation using two values.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+///
+/// @returns
+/// Result of the AND operation.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxZeroOneAnd(FfxUInt32x2 x, FfxUInt32x2 y)
+{
+ return min(x, y);
+}
+
+/// Conditional free logic AND operation using two values.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+///
+/// @returns
+/// Result of the AND operation.
+///
+/// @ingroup GPUCore
+FfxUInt32x3 ffxZeroOneAnd(FfxUInt32x3 x, FfxUInt32x3 y)
+{
+ return min(x, y);
+}
+
+/// Conditional free logic AND operation using two values.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+///
+/// @returns
+/// Result of the AND operation.
+///
+/// @ingroup GPUCore
+FfxUInt32x4 ffxZeroOneAnd(FfxUInt32x4 x, FfxUInt32x4 y)
+{
+ return min(x, y);
+}
+
+/// Conditional free logic NOT operation using two values.
+///
+/// @param [in] x The first value to be fed into the NOT operator.
+///
+/// @returns
+/// Result of the NOT operation.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxZeroOneAnd(FfxUInt32 x)
+{
+ return x ^ FfxUInt32(1);
+}
+
+/// Conditional free logic NOT operation using two values.
+///
+/// @param [in] x The first value to be fed into the NOT operator.
+///
+/// @returns
+/// Result of the NOT operation.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxZeroOneAnd(FfxUInt32x2 x)
+{
+ return x ^ ffxBroadcast2(1u);
+}
+
+/// Conditional free logic NOT operation using two values.
+///
+/// @param [in] x The first value to be fed into the NOT operator.
+///
+/// @returns
+/// Result of the NOT operation.
+///
+/// @ingroup GPUCore
+FfxUInt32x3 ffxZeroOneAnd(FfxUInt32x3 x)
+{
+ return x ^ ffxBroadcast3(1u);
+}
+
+/// Conditional free logic NOT operation using two values.
+///
+/// @param [in] x The first value to be fed into the NOT operator.
+///
+/// @returns
+/// Result of the NOT operation.
+///
+/// @ingroup GPUCore
+FfxUInt32x4 ffxZeroOneAnd(FfxUInt32x4 x)
+{
+ return x ^ ffxBroadcast4(1u);
+}
+
+/// Conditional free logic OR operation using two values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxZeroOneOr(FfxUInt32 x, FfxUInt32 y)
+{
+ return max(x, y);
+}
+
+/// Conditional free logic OR operation using two values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxZeroOneOr(FfxUInt32x2 x, FfxUInt32x2 y)
+{
+ return max(x, y);
+}
+
+/// Conditional free logic OR operation using two values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxUInt32x3 ffxZeroOneOr(FfxUInt32x3 x, FfxUInt32x3 y)
+{
+ return max(x, y);
+}
+
+/// Conditional free logic OR operation using two values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxUInt32x4 ffxZeroOneOr(FfxUInt32x4 x, FfxUInt32x4 y)
+{
+ return max(x, y);
+}
+
+/// Conditional free logic signed NOT operation using two half-precision FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the AND OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxZeroOneAndToU1(FfxFloat32 x)
+{
+ return FfxUInt32(FfxFloat32(1.0) - x);
+}
+
+/// Conditional free logic signed NOT operation using two half-precision FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the AND OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxZeroOneAndToU2(FfxFloat32x2 x)
+{
+ return FfxUInt32x2(ffxBroadcast2(1.0) - x);
+}
+
+/// Conditional free logic signed NOT operation using two half-precision FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the AND OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxUInt32x3 ffxZeroOneAndToU3(FfxFloat32x3 x)
+{
+ return FfxUInt32x3(ffxBroadcast3(1.0) - x);
+}
+
+/// Conditional free logic signed NOT operation using two half-precision FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the AND OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxUInt32x4 ffxZeroOneAndToU4(FfxFloat32x4 x)
+{
+ return FfxUInt32x4(ffxBroadcast4(1.0) - x);
+}
+
+/// Conditional free logic AND operation using two values followed by a NOT operation
+/// using the resulting value and a third value.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+/// @param [in] z The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxZeroOneAndOr(FfxFloat32 x, FfxFloat32 y, FfxFloat32 z)
+{
+ return ffxSaturate(x * y + z);
+}
+
+/// Conditional free logic AND operation using two values followed by a NOT operation
+/// using the resulting value and a third value.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+/// @param [in] z The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxZeroOneAndOr(FfxFloat32x2 x, FfxFloat32x2 y, FfxFloat32x2 z)
+{
+ return ffxSaturate(x * y + z);
+}
+
+/// Conditional free logic AND operation using two values followed by a NOT operation
+/// using the resulting value and a third value.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+/// @param [in] z The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxZeroOneAndOr(FfxFloat32x3 x, FfxFloat32x3 y, FfxFloat32x3 z)
+{
+ return ffxSaturate(x * y + z);
+}
+
+/// Conditional free logic AND operation using two values followed by a NOT operation
+/// using the resulting value and a third value.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+/// @param [in] z The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxZeroOneAndOr(FfxFloat32x4 x, FfxFloat32x4 y, FfxFloat32x4 z)
+{
+ return ffxSaturate(x * y + z);
+}
+
+/// Given a value, returns 1.0 if greater than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the greater than zero comparison.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxZeroOneIsGreaterThanZero(FfxFloat32 x)
+{
+ return ffxSaturate(x * FfxFloat32(FFX_POSITIVE_INFINITY_FLOAT));
+}
+
+/// Given a value, returns 1.0 if greater than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the greater than zero comparison.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxZeroOneIsGreaterThanZero(FfxFloat32x2 x)
+{
+ return ffxSaturate(x * ffxBroadcast2(FFX_POSITIVE_INFINITY_FLOAT));
+}
+
+/// Given a value, returns 1.0 if greater than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the greater than zero comparison.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxZeroOneIsGreaterThanZero(FfxFloat32x3 x)
+{
+ return ffxSaturate(x * ffxBroadcast3(FFX_POSITIVE_INFINITY_FLOAT));
+}
+
+/// Given a value, returns 1.0 if greater than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the greater than zero comparison.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxZeroOneIsGreaterThanZero(FfxFloat32x4 x)
+{
+ return ffxSaturate(x * ffxBroadcast4(FFX_POSITIVE_INFINITY_FLOAT));
+}
+
+/// Conditional free logic signed NOT operation using two FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the AND OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxZeroOneAnd(FfxFloat32 x)
+{
+ return FfxFloat32(1.0) - x;
+}
+
+/// Conditional free logic signed NOT operation using two FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the AND OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxZeroOneAnd(FfxFloat32x2 x)
+{
+ return ffxBroadcast2(1.0) - x;
+}
+
+/// Conditional free logic signed NOT operation using two FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the AND OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxZeroOneAnd(FfxFloat32x3 x)
+{
+ return ffxBroadcast3(1.0) - x;
+}
+
+/// Conditional free logic signed NOT operation using two FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the AND OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxZeroOneAnd(FfxFloat32x4 x)
+{
+ return ffxBroadcast4(1.0) - x;
+}
+
+/// Conditional free logic OR operation using two FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxZeroOneOr(FfxFloat32 x, FfxFloat32 y)
+{
+ return max(x, y);
+}
+
+/// Conditional free logic OR operation using two FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxZeroOneOr(FfxFloat32x2 x, FfxFloat32x2 y)
+{
+ return max(x, y);
+}
+
+/// Conditional free logic OR operation using two FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxZeroOneOr(FfxFloat32x3 x, FfxFloat32x3 y)
+{
+ return max(x, y);
+}
+
+/// Conditional free logic OR operation using two FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxZeroOneOr(FfxFloat32x4 x, FfxFloat32x4 y)
+{
+ return max(x, y);
+}
+
+/// Choose between two FfxFloat32 values if the first paramter is greater than zero.
+///
+/// @param [in] x The value to compare against zero.
+/// @param [in] y The value to return if the comparision is greater than zero.
+/// @param [in] z The value to return if the comparision is less than or equal to zero.
+///
+/// @returns
+/// The selected value.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxZeroOneSelect(FfxFloat32 x, FfxFloat32 y, FfxFloat32 z)
+{
+ FfxFloat32 r = (-x) * z + z;
+ return x * y + r;
+}
+
+/// Choose between two FfxFloat32 values if the first paramter is greater than zero.
+///
+/// @param [in] x The value to compare against zero.
+/// @param [in] y The value to return if the comparision is greater than zero.
+/// @param [in] z The value to return if the comparision is less than or equal to zero.
+///
+/// @returns
+/// The selected value.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxZeroOneSelect(FfxFloat32x2 x, FfxFloat32x2 y, FfxFloat32x2 z)
+{
+ FfxFloat32x2 r = (-x) * z + z;
+ return x * y + r;
+}
+
+/// Choose between two FfxFloat32 values if the first paramter is greater than zero.
+///
+/// @param [in] x The value to compare against zero.
+/// @param [in] y The value to return if the comparision is greater than zero.
+/// @param [in] z The value to return if the comparision is less than or equal to zero.
+///
+/// @returns
+/// The selected value.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxZeroOneSelect(FfxFloat32x3 x, FfxFloat32x3 y, FfxFloat32x3 z)
+{
+ FfxFloat32x3 r = (-x) * z + z;
+ return x * y + r;
+}
+
+/// Choose between two FfxFloat32 values if the first paramter is greater than zero.
+///
+/// @param [in] x The value to compare against zero.
+/// @param [in] y The value to return if the comparision is greater than zero.
+/// @param [in] z The value to return if the comparision is less than or equal to zero.
+///
+/// @returns
+/// The selected value.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxZeroOneSelect(FfxFloat32x4 x, FfxFloat32x4 y, FfxFloat32x4 z)
+{
+ FfxFloat32x4 r = (-x) * z + z;
+ return x * y + r;
+}
+
+/// Given a value, returns 1.0 if less than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the sign value.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxZeroOneIsSigned(FfxFloat32 x)
+{
+ return ffxSaturate(x * FfxFloat32(FFX_NEGATIVE_INFINITY_FLOAT));
+}
+
+/// Given a value, returns 1.0 if less than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the sign value.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxZeroOneIsSigned(FfxFloat32x2 x)
+{
+ return ffxSaturate(x * ffxBroadcast2(FFX_NEGATIVE_INFINITY_FLOAT));
+}
+
+/// Given a value, returns 1.0 if less than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the sign value.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxZeroOneIsSigned(FfxFloat32x3 x)
+{
+ return ffxSaturate(x * ffxBroadcast3(FFX_NEGATIVE_INFINITY_FLOAT));
+}
+
+/// Given a value, returns 1.0 if less than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the sign value.
+///
+/// @ingroup GPUCore
+FfxFloat32x4 ffxZeroOneIsSigned(FfxFloat32x4 x)
+{
+ return ffxSaturate(x * ffxBroadcast4(FFX_NEGATIVE_INFINITY_FLOAT));
+}
+
+/// Compute a Rec.709 color space.
+///
+/// Rec.709 is used for some HDTVs.
+///
+/// Both Rec.709 and sRGB have a linear segment which as spec'ed would intersect the curved segment 2 times.
+/// (a.) For 8-bit sRGB, steps {0 to 10.3} are in the linear region (4% of the encoding range).
+/// (b.) For 8-bit 709, steps {0 to 20.7} are in the linear region (8% of the encoding range).
+///
+/// @param [in] color The color to convert to Rec. 709.
+///
+/// @returns
+/// The <c><i>color</i></c> in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxRec709FromLinear(FfxFloat32 color)
+{
+ FfxFloat32x3 j = FfxFloat32x3(0.018 * 4.5, 4.5, 0.45);
+ FfxFloat32x2 k = FfxFloat32x2(1.099, -0.099);
+ return clamp(j.x, color * j.y, pow(color, j.z) * k.x + k.y);
+}
+
+/// Compute a Rec.709 color space.
+///
+/// Rec.709 is used for some HDTVs.
+///
+/// Both Rec.709 and sRGB have a linear segment which as spec'ed would intersect the curved segment 2 times.
+/// (a.) For 8-bit sRGB, steps {0 to 10.3} are in the linear region (4% of the encoding range).
+/// (b.) For 8-bit 709, steps {0 to 20.7} are in the linear region (8% of the encoding range).
+///
+/// @param [in] color The color to convert to Rec. 709.
+///
+/// @returns
+/// The <c><i>color</i></c> in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxRec709FromLinear(FfxFloat32x2 color)
+{
+ FfxFloat32x3 j = FfxFloat32x3(0.018 * 4.5, 4.5, 0.45);
+ FfxFloat32x2 k = FfxFloat32x2(1.099, -0.099);
+ return clamp(j.xx, color * j.yy, pow(color, j.zz) * k.xx + k.yy);
+}
+
+/// Compute a Rec.709 color space.
+///
+/// Rec.709 is used for some HDTVs.
+///
+/// Both Rec.709 and sRGB have a linear segment which as spec'ed would intersect the curved segment 2 times.
+/// (a.) For 8-bit sRGB, steps {0 to 10.3} are in the linear region (4% of the encoding range).
+/// (b.) For 8-bit 709, steps {0 to 20.7} are in the linear region (8% of the encoding range).
+///
+/// @param [in] color The color to convert to Rec. 709.
+///
+/// @returns
+/// The <c><i>color</i></c> in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxRec709FromLinear(FfxFloat32x3 color)
+{
+ FfxFloat32x3 j = FfxFloat32x3(0.018 * 4.5, 4.5, 0.45);
+ FfxFloat32x2 k = FfxFloat32x2(1.099, -0.099);
+ return clamp(j.xxx, color * j.yyy, pow(color, j.zzz) * k.xxx + k.yyy);
+}
+
+/// Compute a linear value from a REC.709 value.
+///
+/// @param [in] color The value to convert to linear from REC.709.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxLinearFromRec709(FfxFloat32 color)
+{
+ FfxFloat32x3 j = FfxFloat32x3(0.081 / 4.5, 1.0 / 4.5, 1.0 / 0.45);
+ FfxFloat32x2 k = FfxFloat32x2(1.0 / 1.099, 0.099 / 1.099);
+ return ffxZeroOneSelect(ffxZeroOneIsSigned(color - j.x), color * j.y, pow(color * k.x + k.y, j.z));
+}
+
+/// Compute a linear value from a REC.709 value.
+///
+/// @param [in] color The value to convert to linear from REC.709.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxLinearFromRec709(FfxFloat32x2 color)
+{
+ FfxFloat32x3 j = FfxFloat32x3(0.081 / 4.5, 1.0 / 4.5, 1.0 / 0.45);
+ FfxFloat32x2 k = FfxFloat32x2(1.0 / 1.099, 0.099 / 1.099);
+ return ffxZeroOneSelect(ffxZeroOneIsSigned(color - j.xx), color * j.yy, pow(color * k.xx + k.yy, j.zz));
+}
+
+/// Compute a linear value from a REC.709 value.
+///
+/// @param [in] color The value to convert to linear from REC.709.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxLinearFromRec709(FfxFloat32x3 color)
+{
+ FfxFloat32x3 j = FfxFloat32x3(0.081 / 4.5, 1.0 / 4.5, 1.0 / 0.45);
+ FfxFloat32x2 k = FfxFloat32x2(1.0 / 1.099, 0.099 / 1.099);
+ return ffxZeroOneSelect(ffxZeroOneIsSigned(color - j.xxx), color * j.yyy, pow(color * k.xxx + k.yyy, j.zzz));
+}
+
+/// Compute a gamma value from a linear value.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// Note: 'rcpX' is '1/x', where the 'x' is what would be used in <c><i>ffxLinearFromGamma</i></c>.
+///
+/// @param [in] value The value to convert to gamma space from linear.
+/// @param [in] power The reciprocal of power value used for the gamma curve.
+///
+/// @returns
+/// A value in gamma space.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxGammaFromLinear(FfxFloat32 value, FfxFloat32 power)
+{
+ return pow(value, FfxFloat32(power));
+}
+
+/// Compute a gamma value from a linear value.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// Note: 'rcpX' is '1/x', where the 'x' is what would be used in <c><i>ffxLinearFromGamma</i></c>.
+///
+/// @param [in] value The value to convert to gamma space from linear.
+/// @param [in] power The reciprocal of power value used for the gamma curve.
+///
+/// @returns
+/// A value in gamma space.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxGammaFromLinear(FfxFloat32x2 value, FfxFloat32 power)
+{
+ return pow(value, ffxBroadcast2(power));
+}
+
+/// Compute a gamma value from a linear value.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// Note: 'rcpX' is '1/x', where the 'x' is what would be used in <c><i>ffxLinearFromGamma</i></c>.
+///
+/// @param [in] value The value to convert to gamma space from linear.
+/// @param [in] power The reciprocal of power value used for the gamma curve.
+///
+/// @returns
+/// A value in gamma space.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxGammaFromLinear(FfxFloat32x3 value, FfxFloat32 power)
+{
+ return pow(value, ffxBroadcast3(power));
+}
+
+/// Compute a linear value from a value in a gamma space.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// @param [in] color The value to convert to linear in gamma space.
+/// @param [in] power The power value used for the gamma curve.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxLinearFromGamma(FfxFloat32 color, FfxFloat32 power)
+{
+ return pow(color, FfxFloat32(power));
+}
+
+/// Compute a linear value from a value in a gamma space.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// @param [in] color The value to convert to linear in gamma space.
+/// @param [in] power The power value used for the gamma curve.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxLinearFromGamma(FfxFloat32x2 color, FfxFloat32 power)
+{
+ return pow(color, ffxBroadcast2(power));
+}
+
+/// Compute a linear value from a value in a gamma space.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// @param [in] color The value to convert to linear in gamma space.
+/// @param [in] power The power value used for the gamma curve.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxLinearFromGamma(FfxFloat32x3 color, FfxFloat32 power)
+{
+ return pow(color, ffxBroadcast3(power));
+}
+
+/// Compute a PQ value from a linear value.
+///
+/// @param [in] value The value to convert to PQ from linear.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxPQFromLinear(FfxFloat32 value)
+{
+ FfxFloat32 p = pow(value, FfxFloat32(0.159302));
+ return pow((FfxFloat32(0.835938) + FfxFloat32(18.8516) * p) / (FfxFloat32(1.0) + FfxFloat32(18.6875) * p), FfxFloat32(78.8438));
+}
+
+/// Compute a PQ value from a linear value.
+///
+/// @param [in] value The value to convert to PQ from linear.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxPQFromLinear(FfxFloat32x2 value)
+{
+ FfxFloat32x2 p = pow(value, ffxBroadcast2(0.159302));
+ return pow((ffxBroadcast2(0.835938) + ffxBroadcast2(18.8516) * p) / (ffxBroadcast2(1.0) + ffxBroadcast2(18.6875) * p), ffxBroadcast2(78.8438));
+}
+
+/// Compute a PQ value from a linear value.
+///
+/// @param [in] value The value to convert to PQ from linear.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxPQFromLinear(FfxFloat32x3 value)
+{
+ FfxFloat32x3 p = pow(value, ffxBroadcast3(0.159302));
+ return pow((ffxBroadcast3(0.835938) + ffxBroadcast3(18.8516) * p) / (ffxBroadcast3(1.0) + ffxBroadcast3(18.6875) * p), ffxBroadcast3(78.8438));
+}
+
+/// Compute a linear value from a value in a PQ space.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// @param [in] value The value to convert to linear in PQ space.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxLinearFromPQ(FfxFloat32 value)
+{
+ FfxFloat32 p = pow(value, FfxFloat32(0.0126833));
+ return pow(ffxSaturate(p - FfxFloat32(0.835938)) / (FfxFloat32(18.8516) - FfxFloat32(18.6875) * p), FfxFloat32(6.27739));
+}
+
+/// Compute a linear value from a value in a PQ space.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// @param [in] value The value to convert to linear in PQ space.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxLinearFromPQ(FfxFloat32x2 value)
+{
+ FfxFloat32x2 p = pow(value, ffxBroadcast2(0.0126833));
+ return pow(ffxSaturate(p - ffxBroadcast2(0.835938)) / (ffxBroadcast2(18.8516) - ffxBroadcast2(18.6875) * p), ffxBroadcast2(6.27739));
+}
+
+/// Compute a linear value from a value in a PQ space.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// @param [in] value The value to convert to linear in PQ space.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxLinearFromPQ(FfxFloat32x3 value)
+{
+ FfxFloat32x3 p = pow(value, ffxBroadcast3(0.0126833));
+ return pow(ffxSaturate(p - ffxBroadcast3(0.835938)) / (ffxBroadcast3(18.8516) - ffxBroadcast3(18.6875) * p), ffxBroadcast3(6.27739));
+}
+
+/// Compute an SRGB value from a linear value.
+///
+/// @param [in] value The value to convert to SRGB from linear.
+///
+/// @returns
+/// A value in SRGB space.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxSrgbFromLinear(FfxFloat32 value)
+{
+ FfxFloat32x3 j = FfxFloat32x3(0.0031308 * 12.92, 12.92, 1.0 / 2.4);
+ FfxFloat32x2 k = FfxFloat32x2(1.055, -0.055);
+ return clamp(j.x, value * j.y, pow(value, j.z) * k.x + k.y);
+}
+
+/// Compute an SRGB value from a linear value.
+///
+/// @param [in] value The value to convert to SRGB from linear.
+///
+/// @returns
+/// A value in SRGB space.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxSrgbFromLinear(FfxFloat32x2 value)
+{
+ FfxFloat32x3 j = FfxFloat32x3(0.0031308 * 12.92, 12.92, 1.0 / 2.4);
+ FfxFloat32x2 k = FfxFloat32x2(1.055, -0.055);
+ return clamp(j.xx, value * j.yy, pow(value, j.zz) * k.xx + k.yy);
+}
+
+/// Compute an SRGB value from a linear value.
+///
+/// @param [in] value The value to convert to SRGB from linear.
+///
+/// @returns
+/// A value in SRGB space.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxSrgbFromLinear(FfxFloat32x3 value)
+{
+ FfxFloat32x3 j = FfxFloat32x3(0.0031308 * 12.92, 12.92, 1.0 / 2.4);
+ FfxFloat32x2 k = FfxFloat32x2(1.055, -0.055);
+ return clamp(j.xxx, value * j.yyy, pow(value, j.zzz) * k.xxx + k.yyy);
+}
+
+/// Compute a linear value from a value in a SRGB space.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// @param [in] value The value to convert to linear in SRGB space.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32 ffxLinearFromSrgb(FfxFloat32 value)
+{
+ FfxFloat32x3 j = FfxFloat32x3(0.04045, 1.0 / 12.92, 2.4);
+ FfxFloat32x2 k = FfxFloat32x2(1.0 / 1.055, 0.055 / 1.055);
+ return ffxZeroOneSelect(ffxZeroOneIsSigned(value - j.x), value * j.y, pow(value * k.x + k.y, j.z));
+}
+
+/// Compute a linear value from a value in a SRGB space.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// @param [in] value The value to convert to linear in SRGB space.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32x2 ffxLinearFromSrgb(FfxFloat32x2 value)
+{
+ FfxFloat32x3 j = FfxFloat32x3(0.04045, 1.0 / 12.92, 2.4);
+ FfxFloat32x2 k = FfxFloat32x2(1.0 / 1.055, 0.055 / 1.055);
+ return ffxZeroOneSelect(ffxZeroOneIsSigned(value - j.xx), value * j.yy, pow(value * k.xx + k.yy, j.zz));
+}
+
+/// Compute a linear value from a value in a SRGB space.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// @param [in] value The value to convert to linear in SRGB space.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat32x3 ffxLinearFromSrgb(FfxFloat32x3 value)
+{
+ FfxFloat32x3 j = FfxFloat32x3(0.04045, 1.0 / 12.92, 2.4);
+ FfxFloat32x2 k = FfxFloat32x2(1.0 / 1.055, 0.055 / 1.055);
+ return ffxZeroOneSelect(ffxZeroOneIsSigned(value - j.xxx), value * j.yyy, pow(value * k.xxx + k.yyy, j.zzz));
+}
+
+/// A remapping of 64x1 to 8x8 imposing rotated 2x2 pixel quads in quad linear.
+///
+/// Remap illustration:
+///
+/// 543210
+/// ~~~~~~
+/// ..xxx.
+/// yy...y
+///
+/// @param [in] a The input 1D coordinates to remap.
+///
+/// @returns
+/// The remapped 2D coordinates.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxRemapForQuad(FfxUInt32 a)
+{
+ return FfxUInt32x2(ffxBitfieldExtract(a, 1u, 3u), ffxBitfieldInsertMask(ffxBitfieldExtract(a, 3u, 3u), a, 1u));
+}
+
+/// A helper function performing a remap 64x1 to 8x8 remapping which is necessary for 2D wave reductions.
+///
+/// The 64-wide lane indices to 8x8 remapping is performed as follows:
+///
+/// 00 01 08 09 10 11 18 19
+/// 02 03 0a 0b 12 13 1a 1b
+/// 04 05 0c 0d 14 15 1c 1d
+/// 06 07 0e 0f 16 17 1e 1f
+/// 20 21 28 29 30 31 38 39
+/// 22 23 2a 2b 32 33 3a 3b
+/// 24 25 2c 2d 34 35 3c 3d
+/// 26 27 2e 2f 36 37 3e 3f
+///
+/// @param [in] a The input 1D coordinate to remap.
+///
+/// @returns
+/// The remapped 2D coordinates.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxRemapForWaveReduction(FfxUInt32 a)
+{
+ return FfxUInt32x2(ffxBitfieldInsertMask(ffxBitfieldExtract(a, 2u, 3u), a, 1u), ffxBitfieldInsertMask(ffxBitfieldExtract(a, 3u, 3u), ffxBitfieldExtract(a, 1u, 2u), 2u));
+}
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_gpu_common_half.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_gpu_common_half.h
new file mode 100644
index 0000000000..6b071d590f
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_gpu_common_half.h
@@ -0,0 +1,2981 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#if FFX_HALF
+#if FFX_HLSL_SM >= 62
+/// A define value for 16bit positive infinity.
+///
+/// @ingroup GPUCore
+#define FFX_POSITIVE_INFINITY_HALF FFX_TO_FLOAT16((uint16_t)0x7c00u)
+
+/// A define value for 16bit negative infinity.
+///
+/// @ingroup GPUCore
+#define FFX_NEGATIVE_INFINITY_HALF FFX_TO_FLOAT16((uint16_t)0xfc00u)
+#else
+/// A define value for 16bit positive infinity.
+///
+/// @ingroup GPUCore
+#define FFX_POSITIVE_INFINITY_HALF FFX_TO_FLOAT16(0x7c00u)
+
+/// A define value for 16bit negative infinity.
+///
+/// @ingroup GPUCore
+#define FFX_NEGATIVE_INFINITY_HALF FFX_TO_FLOAT16(0xfc00u)
+#endif // #if FFX_HLSL_SM>=62
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxMin(FfxFloat16 x, FfxFloat16 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxMin(FfxFloat16x2 x, FfxFloat16x2 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxMin(FfxFloat16x3 x, FfxFloat16x3 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxMin(FfxFloat16x4 x, FfxFloat16x4 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt16 ffxMin(FfxInt16 x, FfxInt16 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt16x2 ffxMin(FfxInt16x2 x, FfxInt16x2 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt16x3 ffxMin(FfxInt16x3 x, FfxInt16x3 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt16x4 ffxMin(FfxInt16x4 x, FfxInt16x4 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt16 ffxMin(FfxUInt16 x, FfxUInt16 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt16x2 ffxMin(FfxUInt16x2 x, FfxUInt16x2 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt16x3 ffxMin(FfxUInt16x3 x, FfxUInt16x3 y)
+{
+ return min(x, y);
+}
+
+/// Compute the min of two values.
+///
+/// @param [in] x The first value to compute the min of.
+/// @param [in] y The second value to compute the min of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt16x4 ffxMin(FfxUInt16x4 x, FfxUInt16x4 y)
+{
+ return min(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxMax(FfxFloat16 x, FfxFloat16 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxMax(FfxFloat16x2 x, FfxFloat16x2 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxMax(FfxFloat16x3 x, FfxFloat16x3 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxMax(FfxFloat16x4 x, FfxFloat16x4 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt16 ffxMax(FfxInt16 x, FfxInt16 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt16x2 ffxMax(FfxInt16x2 x, FfxInt16x2 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt16x3 ffxMax(FfxInt16x3 x, FfxInt16x3 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxInt16x4 ffxMax(FfxInt16x4 x, FfxInt16x4 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt16 ffxMax(FfxUInt16 x, FfxUInt16 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt16x2 ffxMax(FfxUInt16x2 x, FfxUInt16x2 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt16x3 ffxMax(FfxUInt16x3 x, FfxUInt16x3 y)
+{
+ return max(x, y);
+}
+
+/// Compute the max of two values.
+///
+/// @param [in] x The first value to compute the max of.
+/// @param [in] y The second value to compute the max of.
+///
+/// @returns
+/// The the lowest of two values.
+///
+/// @ingroup GPUCore
+FfxUInt16x4 ffxMax(FfxUInt16x4 x, FfxUInt16x4 y)
+{
+ return max(x, y);
+}
+
+/// Compute the value of the first parameter raised to the power of the second.
+///
+/// @param [in] x The value to raise to the power y.
+/// @param [in] y The power to which to raise x.
+///
+/// @returns
+/// The value of the first parameter raised to the power of the second.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxPow(FfxFloat16 x, FfxFloat16 y)
+{
+ return pow(x, y);
+}
+
+/// Compute the value of the first parameter raised to the power of the second.
+///
+/// @param [in] x The value to raise to the power y.
+/// @param [in] y The power to which to raise x.
+///
+/// @returns
+/// The value of the first parameter raised to the power of the second.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxPow(FfxFloat16x2 x, FfxFloat16x2 y)
+{
+ return pow(x, y);
+}
+
+/// Compute the value of the first parameter raised to the power of the second.
+///
+/// @param [in] x The value to raise to the power y.
+/// @param [in] y The power to which to raise x.
+///
+/// @returns
+/// The value of the first parameter raised to the power of the second.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxPow(FfxFloat16x3 x, FfxFloat16x3 y)
+{
+ return pow(x, y);
+}
+
+/// Compute the value of the first parameter raised to the power of the second.
+///
+/// @param [in] x The value to raise to the power y.
+/// @param [in] y The power to which to raise x.
+///
+/// @returns
+/// The value of the first parameter raised to the power of the second.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxPow(FfxFloat16x4 x, FfxFloat16x4 y)
+{
+ return pow(x, y);
+}
+
+/// Compute the square root of a value.
+///
+/// @param [in] x The first value to compute the min of.
+///
+/// @returns
+/// The the square root of <c><i>x</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxSqrt(FfxFloat16 x)
+{
+ return sqrt(x);
+}
+
+/// Compute the square root of a value.
+///
+/// @param [in] x The first value to compute the min of.
+///
+/// @returns
+/// The the square root of <c><i>x</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxSqrt(FfxFloat16x2 x)
+{
+ return sqrt(x);
+}
+
+/// Compute the square root of a value.
+///
+/// @param [in] x The first value to compute the min of.
+///
+/// @returns
+/// The the square root of <c><i>x</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxSqrt(FfxFloat16x3 x)
+{
+ return sqrt(x);
+}
+
+/// Compute the square root of a value.
+///
+/// @param [in] x The first value to compute the min of.
+///
+/// @returns
+/// The the square root of <c><i>x</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxSqrt(FfxFloat16x4 x)
+{
+ return sqrt(x);
+}
+
+/// Copy the sign bit from 's' to positive 'd'.
+///
+/// @param [in] d The value to copy the sign bit into.
+/// @param [in] s The value to copy the sign bit from.
+///
+/// @returns
+/// The value of <c><i>d</i></c> with the sign bit from <c><i>s</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxCopySignBitHalf(FfxFloat16 d, FfxFloat16 s)
+{
+ return FFX_TO_FLOAT16(FFX_TO_UINT16(d) | (FFX_TO_UINT16(s) & FFX_BROADCAST_UINT16(0x8000u)));
+}
+
+/// Copy the sign bit from 's' to positive 'd'.
+///
+/// @param [in] d The value to copy the sign bit into.
+/// @param [in] s The value to copy the sign bit from.
+///
+/// @returns
+/// The value of <c><i>d</i></c> with the sign bit from <c><i>s</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxCopySignBitHalf(FfxFloat16x2 d, FfxFloat16x2 s)
+{
+ return FFX_TO_FLOAT16X2(FFX_TO_UINT16X2(d) | (FFX_TO_UINT16X2(s) & FFX_BROADCAST_UINT16X2(0x8000u)));
+}
+
+/// Copy the sign bit from 's' to positive 'd'.
+///
+/// @param [in] d The value to copy the sign bit into.
+/// @param [in] s The value to copy the sign bit from.
+///
+/// @returns
+/// The value of <c><i>d</i></c> with the sign bit from <c><i>s</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxCopySignBitHalf(FfxFloat16x3 d, FfxFloat16x3 s)
+{
+ return FFX_TO_FLOAT16X3(FFX_TO_UINT16X3(d) | (FFX_TO_UINT16X3(s) & FFX_BROADCAST_UINT16X3(0x8000u)));
+}
+
+/// Copy the sign bit from 's' to positive 'd'.
+///
+/// @param [in] d The value to copy the sign bit into.
+/// @param [in] s The value to copy the sign bit from.
+///
+/// @returns
+/// The value of <c><i>d</i></c> with the sign bit from <c><i>s</i></c>.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxCopySignBitHalf(FfxFloat16x4 d, FfxFloat16x4 s)
+{
+ return FFX_TO_FLOAT16X4(FFX_TO_UINT16X4(d) | (FFX_TO_UINT16X4(s) & FFX_BROADCAST_UINT16X4(0x8000u)));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 0
+/// m >= 0 := 0
+/// m < 0 := 1
+///
+/// Uses the following useful floating point logic,
+/// saturate(+a*(-INF)==-INF) := 0
+/// saturate( 0*(-INF)== NaN) := 0
+/// saturate(-a*(-INF)==+INF) := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against 0.
+///
+/// @returns
+/// 1.0 when the value is negative, or 0.0 when the value is 0 or position.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxIsSignedHalf(FfxFloat16 m)
+{
+ return ffxSaturate(m * FFX_BROADCAST_FLOAT16(FFX_NEGATIVE_INFINITY_HALF));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 0
+/// m >= 0 := 0
+/// m < 0 := 1
+///
+/// Uses the following useful floating point logic,
+/// saturate(+a*(-INF)==-INF) := 0
+/// saturate( 0*(-INF)== NaN) := 0
+/// saturate(-a*(-INF)==+INF) := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against 0.
+///
+/// @returns
+/// 1.0 when the value is negative, or 0.0 when the value is 0 or position.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxIsSignedHalf(FfxFloat16x2 m)
+{
+ return ffxSaturate(m * FFX_BROADCAST_FLOAT16X2(FFX_NEGATIVE_INFINITY_HALF));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 0
+/// m >= 0 := 0
+/// m < 0 := 1
+///
+/// Uses the following useful floating point logic,
+/// saturate(+a*(-INF)==-INF) := 0
+/// saturate( 0*(-INF)== NaN) := 0
+/// saturate(-a*(-INF)==+INF) := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against 0.
+///
+/// @returns
+/// 1.0 when the value is negative, or 0.0 when the value is 0 or position.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxIsSignedHalf(FfxFloat16x3 m)
+{
+ return ffxSaturate(m * FFX_BROADCAST_FLOAT16X3(FFX_NEGATIVE_INFINITY_HALF));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 0
+/// m >= 0 := 0
+/// m < 0 := 1
+///
+/// Uses the following useful floating point logic,
+/// saturate(+a*(-INF)==-INF) := 0
+/// saturate( 0*(-INF)== NaN) := 0
+/// saturate(-a*(-INF)==+INF) := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against 0.
+///
+/// @returns
+/// 1.0 when the value is negative, or 0.0 when the value is 0 or position.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxIsSignedHalf(FfxFloat16x4 m)
+{
+ return ffxSaturate(m * FFX_BROADCAST_FLOAT16X4(FFX_NEGATIVE_INFINITY_HALF));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 1
+/// m > 0 := 0
+/// m <= 0 := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against zero.
+///
+/// @returns
+/// 1.0 when the value is position, or 0.0 when the value is 0 or negative.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxIsGreaterThanZeroHalf(FfxFloat16 m)
+{
+ return ffxSaturate(m * FFX_BROADCAST_FLOAT16(FFX_POSITIVE_INFINITY_HALF));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 1
+/// m > 0 := 0
+/// m <= 0 := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against zero.
+///
+/// @returns
+/// 1.0 when the value is position, or 0.0 when the value is 0 or negative.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxIsGreaterThanZeroHalf(FfxFloat16x2 m)
+{
+ return ffxSaturate(m * FFX_BROADCAST_FLOAT16X2(FFX_POSITIVE_INFINITY_HALF));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 1
+/// m > 0 := 0
+/// m <= 0 := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against zero.
+///
+/// @returns
+/// 1.0 when the value is position, or 0.0 when the value is 0 or negative.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxIsGreaterThanZeroHalf(FfxFloat16x3 m)
+{
+ return ffxSaturate(m * FFX_BROADCAST_FLOAT16X3(FFX_POSITIVE_INFINITY_HALF));
+}
+
+/// A single operation to return the following:
+/// m = NaN := 1
+/// m > 0 := 0
+/// m <= 0 := 1
+///
+/// This function is useful when creating masks for branch-free logic.
+///
+/// @param [in] m The value to test against zero.
+///
+/// @returns
+/// 1.0 when the value is position, or 0.0 when the value is 0 or negative.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxIsGreaterThanZeroHalf(FfxFloat16x4 m)
+{
+ return ffxSaturate(m * FFX_BROADCAST_FLOAT16X4(FFX_POSITIVE_INFINITY_HALF));
+}
+
+/// Convert a 16bit floating point value to sortable integer.
+///
+/// - If sign bit=0, flip the sign bit (positives).
+/// - If sign bit=1, flip all bits (negatives).
+///
+/// The function has the side effects that:
+/// - Larger integers are more positive values.
+/// - Float zero is mapped to center of integers (so clear to integer zero is a nice default for atomic max usage).
+///
+/// @param [in] x The floating point value to make sortable.
+///
+/// @returns
+/// The sortable integer value.
+///
+/// @ingroup GPUCore
+FfxUInt16 ffxFloatToSortableIntegerHalf(FfxUInt16 x)
+{
+ return x ^ ((ffxBitShiftRightHalf(x, FFX_BROADCAST_UINT16(15))) | FFX_BROADCAST_UINT16(0x8000));
+}
+
+/// Convert a sortable integer to a 16bit floating point value.
+///
+/// The function has the side effects that:
+/// - If sign bit=1, flip the sign bit (positives).
+/// - If sign bit=0, flip all bits (negatives).
+///
+/// @param [in] x The sortable integer value to make floating point.
+///
+/// @returns
+/// The floating point value.
+///
+/// @ingroup GPUCore
+FfxUInt16 ffxSortableIntegerToFloatHalf(FfxUInt16 x)
+{
+ return x ^ ((~ffxBitShiftRightHalf(x, FFX_BROADCAST_UINT16(15))) | FFX_BROADCAST_UINT16(0x8000));
+}
+
+/// Convert a pair of 16bit floating point values to a pair of sortable integers.
+///
+/// - If sign bit=0, flip the sign bit (positives).
+/// - If sign bit=1, flip all bits (negatives).
+///
+/// The function has the side effects that:
+/// - Larger integers are more positive values.
+/// - Float zero is mapped to center of integers (so clear to integer zero is a nice default for atomic max usage).
+///
+/// @param [in] x The floating point values to make sortable.
+///
+/// @returns
+/// The sortable integer values.
+///
+/// @ingroup GPUCore
+FfxUInt16x2 ffxFloatToSortableIntegerHalf(FfxUInt16x2 x)
+{
+ return x ^ ((ffxBitShiftRightHalf(x, FFX_BROADCAST_UINT16X2(15))) | FFX_BROADCAST_UINT16X2(0x8000));
+}
+
+/// Convert a pair of sortable integers to a pair of 16bit floating point values.
+///
+/// The function has the side effects that:
+/// - If sign bit=1, flip the sign bit (positives).
+/// - If sign bit=0, flip all bits (negatives).
+///
+/// @param [in] x The sortable integer values to make floating point.
+///
+/// @returns
+/// The floating point values.
+///
+/// @ingroup GPUCore
+FfxUInt16x2 ffxSortableIntegerToFloatHalf(FfxUInt16x2 x)
+{
+ return x ^ ((~ffxBitShiftRightHalf(x, FFX_BROADCAST_UINT16X2(15))) | FFX_BROADCAST_UINT16X2(0x8000));
+}
+
+/// Packs the bytes from the X and Y components of a FfxUInt32x2 into a single 32-bit integer.
+///
+/// The resulting integer will contain bytes in the following order, from most to least significant:
+/// [Zero] Y0 [Zero] X0
+///
+/// @param [in] i The integer pair to pack.
+///
+/// @returns
+/// The packed integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxPackBytesZeroY0ZeroX0(FfxUInt32x2 i)
+{
+ return ((i.x) & 0xffu) | ((i.y << 16) & 0xff0000u);
+}
+
+/// Packs the bytes from the X and Y components of a FfxUInt32x2 into a single 32-bit integer.
+///
+/// The resulting integer will contain bytes in the following order, from most to least significant:
+/// [Zero] Y1 [Zero] X1
+///
+/// @param [in] i The integer pair to pack.
+///
+/// @returns
+/// The packed integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxPackBytesZeroY1ZeroX1(FfxUInt32x2 i)
+{
+ return ((i.x >> 8) & 0xffu) | ((i.y << 8) & 0xff0000u);
+}
+
+/// Packs the bytes from the X and Y components of a FfxUInt32x2 into a single 32-bit integer.
+///
+/// The resulting integer will contain bytes in the following order, from most to least significant:
+/// [Zero] Y2 [Zero] X2
+///
+/// @param [in] i The integer pair to pack.
+///
+/// @returns
+/// The packed integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxPackBytesZeroY2ZeroX2(FfxUInt32x2 i)
+{
+ return ((i.x >> 16) & 0xffu) | ((i.y) & 0xff0000u);
+}
+
+/// Packs the bytes from the X and Y components of a FfxUInt32x2 into a single 32-bit integer.
+///
+/// The resulting integer will contain bytes in the following order, from most to least significant:
+/// [Zero] Y3 [Zero] X3
+///
+/// @param [in] i The integer pair to pack.
+///
+/// @returns
+/// The packed integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxPackBytesZeroY3ZeroX3(FfxUInt32x2 i)
+{
+ return ((i.x >> 24) & 0xffu) | ((i.y >> 8) & 0xff0000u);
+}
+
+/// Packs the bytes from the X and Y components of a FfxUInt32x2 into a single 32-bit integer.
+///
+/// The resulting integer will contain bytes in the following order, from most to least significant:
+/// Y3 Y2 Y1 X0
+///
+/// @param [in] i The integer pair to pack.
+///
+/// @returns
+/// The packed integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxPackBytesY3Y2Y1X0(FfxUInt32x2 i)
+{
+ return ((i.x) & 0x000000ffu) | (i.y & 0xffffff00u);
+}
+
+/// Packs the bytes from the X and Y components of a FfxUInt32x2 into a single 32-bit integer.
+///
+/// The resulting integer will contain bytes in the following order, from most to least significant:
+/// Y3 Y2 Y1 X2
+///
+/// @param [in] i The integer pair to pack.
+///
+/// @returns
+/// The packed integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxPackBytesY3Y2Y1X2(FfxUInt32x2 i)
+{
+ return ((i.x >> 16) & 0x000000ffu) | (i.y & 0xffffff00u);
+}
+
+/// Packs the bytes from the X and Y components of a FfxUInt32x2 into a single 32-bit integer.
+///
+/// The resulting integer will contain bytes in the following order, from most to least significant:
+/// Y3 Y2 X0 Y0
+///
+/// @param [in] i The integer pair to pack.
+///
+/// @returns
+/// The packed integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxPackBytesY3Y2X0Y0(FfxUInt32x2 i)
+{
+ return ((i.x << 8) & 0x0000ff00u) | (i.y & 0xffff00ffu);
+}
+
+/// Packs the bytes from the X and Y components of a FfxUInt32x2 into a single 32-bit integer.
+///
+/// The resulting integer will contain bytes in the following order, from most to least significant:
+/// Y3 Y2 X2 Y0
+///
+/// @param [in] i The integer pair to pack.
+///
+/// @returns
+/// The packed integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxPackBytesY3Y2X2Y0(FfxUInt32x2 i)
+{
+ return ((i.x >> 8) & 0x0000ff00u) | (i.y & 0xffff00ffu);
+}
+
+/// Packs the bytes from the X and Y components of a FfxUInt32x2 into a single 32-bit integer.
+///
+/// The resulting integer will contain bytes in the following order, from most to least significant:
+/// Y3 X0 Y1 Y0
+///
+/// @param [in] i The integer pair to pack.
+///
+/// @returns
+/// The packed integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxPackBytesY3X0Y1Y0(FfxUInt32x2 i)
+{
+ return ((i.x << 16) & 0x00ff0000u) | (i.y & 0xff00ffffu);
+}
+
+/// Packs the bytes from the X and Y components of a FfxUInt32x2 into a single 32-bit integer.
+///
+/// The resulting integer will contain bytes in the following order, from most to least significant:
+/// Y3 X2 Y1 Y0
+///
+/// @param [in] i The integer pair to pack.
+///
+/// @returns
+/// The packed integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxPackBytesY3X2Y1Y0(FfxUInt32x2 i)
+{
+ return ((i.x) & 0x00ff0000u) | (i.y & 0xff00ffffu);
+}
+
+/// Packs the bytes from the X and Y components of a FfxUInt32x2 into a single 32-bit integer.
+///
+/// The resulting integer will contain bytes in the following order, from most to least significant:
+/// X0 Y2 Y1 Y0
+///
+/// @param [in] i The integer pair to pack.
+///
+/// @returns
+/// The packed integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxPackBytesX0Y2Y1Y0(FfxUInt32x2 i)
+{
+ return ((i.x << 24) & 0xff000000u) | (i.y & 0x00ffffffu);
+}
+
+/// Packs the bytes from the X and Y components of a FfxUInt32x2 into a single 32-bit integer.
+///
+/// The resulting integer will contain bytes in the following order, from most to least significant:
+/// X2 Y2 Y1 Y0
+///
+/// @param [in] i The integer pair to pack.
+///
+/// @returns
+/// The packed integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxPackBytesX2Y2Y1Y0(FfxUInt32x2 i)
+{
+ return ((i.x << 8) & 0xff000000u) | (i.y & 0x00ffffffu);
+}
+
+/// Packs the bytes from the X and Y components of a FfxUInt32x2 into a single 32-bit integer.
+///
+/// The resulting integer will contain bytes in the following order, from most to least significant:
+/// Y2 X2 Y0 X0
+///
+/// @param [in] i The integer pair to pack.
+///
+/// @returns
+/// The packed integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxPackBytesY2X2Y0X0(FfxUInt32x2 i)
+{
+ return ((i.x) & 0x00ff00ffu) | ((i.y << 8) & 0xff00ff00u);
+}
+
+/// Packs the bytes from the X and Y components of a FfxUInt32x2 into a single 32-bit integer.
+///
+/// The resulting integer will contain bytes in the following order, from most to least significant:
+/// Y2 Y0 X2 X0
+///
+/// @param [in] i The integer pair to pack.
+///
+/// @returns
+/// The packed integer value.
+///
+/// @ingroup GPUCore
+FfxUInt32 ffxPackBytesY2Y0X2X0(FfxUInt32x2 i)
+{
+ return (((i.x) & 0xffu) | ((i.x >> 8) & 0xff00u) | ((i.y << 16) & 0xff0000u) | ((i.y << 8) & 0xff000000u));
+}
+
+/// Takes two Float16x2 values x and y, normalizes them and builds a single Uint16x2 value in the format {{x0,y0},{x1,y1}}.
+///
+/// @param [in] x The first float16x2 value to pack.
+/// @param [in] y The second float16x2 value to pack.
+///
+/// @returns
+/// The packed FfxUInt32x2 value.
+///
+/// @ingroup GPUCore
+FfxUInt16x2 ffxPackX0Y0X1Y1UnsignedToUint16x2(FfxFloat16x2 x, FfxFloat16x2 y)
+{
+ x *= FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0);
+ y *= FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0);
+ return FFX_UINT32_TO_UINT16X2(ffxPackBytesY2X2Y0X0(FfxUInt32x2(FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(x)), FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(y)))));
+}
+
+/// Given a FfxUInt32x2 value d, Float16x2 value i and a resulting FfxUInt32x2 value r, this function packs d.x[0:7] into r.x[0:7],
+/// d.y[0:7] into r.y[0:7], i.x[8:15] into r.x[8:15], r.y[8:15] and i.y[0:15] into r.x[16:31], r.y[16:31] using 3 ops.
+///
+/// r=ffxPermuteUByte0Float16x2ToUint2(d,i)
+/// Where 'k0' is an SGPR with {1.0/32768.0} packed into the lower 16-bits
+/// Where 'k1' is an SGPR with 0x????
+/// Where 'k2' is an SGPR with 0x????
+/// V_PK_FMA_F16 i,i,k0.x,0
+/// V_PERM_B32 r.x,i,i,k1
+/// V_PERM_B32 r.y,i,i,k2
+///
+/// @param [in] d The FfxUInt32x2 value to be packed.
+/// @param [in] i The FfxFloat16x2 value to be packed.
+///
+/// @returns
+/// The packed FfxUInt32x2 value.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxPermuteUByte0Float16x2ToUint2(FfxUInt32x2 d, FfxFloat16x2 i)
+{
+ FfxUInt32 b = FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(i * FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0)));
+ return FfxUInt32x2(ffxPackBytesY3Y2Y1X0(FfxUInt32x2(d.x, b)), ffxPackBytesY3Y2Y1X2(FfxUInt32x2(d.y, b)));
+}
+
+/// Given a FfxUInt32x2 value d, Float16x2 value i and a resulting FfxUInt32x2 value r, this function packs d.x[0:7] into r.x[8:15],
+/// d.y[0:7] into r.y[8:15], i.x[0:7] into r.x[0:7], r.y[0:7] and i.y[0:15] into r.x[16:31], r.y[16:31] using 3 ops.
+///
+/// r=ffxPermuteUByte1Float16x2ToUint2(d,i)
+/// Where 'k0' is an SGPR with {1.0/32768.0} packed into the lower 16-bits
+/// Where 'k1' is an SGPR with 0x????
+/// Where 'k2' is an SGPR with 0x????
+/// V_PK_FMA_F16 i,i,k0.x,0
+/// V_PERM_B32 r.x,i,i,k1
+/// V_PERM_B32 r.y,i,i,k2
+///
+/// @param [in] d The FfxUInt32x2 value to be packed.
+/// @param [in] i The FfxFloat16x2 value to be packed.
+///
+/// @returns
+/// The packed FfxUInt32x2 value.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxPermuteUByte1Float16x2ToUint2(FfxUInt32x2 d, FfxFloat16x2 i)
+{
+ FfxUInt32 b = FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(i * FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0)));
+ return FfxUInt32x2(ffxPackBytesY3Y2X0Y0(FfxUInt32x2(d.x, b)), ffxPackBytesY3Y2X2Y0(FfxUInt32x2(d.y, b)));
+}
+
+/// Given a FfxUInt32x2 value d, Float16x2 value i and a resulting FfxUInt32x2 value r, this function packs d.x[0:7] into r.x[16:23],
+/// d.y[0:7] into r.y[16:23], i.x[0:15] into r.x[0:15], r.y[0:15] and i.y[8:15] into r.x[24:31], r.y[24:31] using 3 ops.
+///
+/// r=ffxPermuteUByte2Float16x2ToUint2(d,i)
+/// Where 'k0' is an SGPR with {1.0/32768.0} packed into the lower 16-bits
+/// Where 'k1' is an SGPR with 0x????
+/// Where 'k2' is an SGPR with 0x????
+/// V_PK_FMA_F16 i,i,k0.x,0
+/// V_PERM_B32 r.x,i,i,k1
+/// V_PERM_B32 r.y,i,i,k2
+///
+/// @param [in] d The FfxUInt32x2 value to be packed.
+/// @param [in] i The FfxFloat16x2 value to be packed.
+///
+/// @returns
+/// The packed FfxUInt32x2 value.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxPermuteUByte2Float16x2ToUint2(FfxUInt32x2 d, FfxFloat16x2 i)
+{
+ FfxUInt32 b = FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(i * FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0)));
+ return FfxUInt32x2(ffxPackBytesY3X0Y1Y0(FfxUInt32x2(d.x, b)), ffxPackBytesY3X2Y1Y0(FfxUInt32x2(d.y, b)));
+}
+
+/// Given a FfxUInt32x2 value d, Float16x2 value i and a resulting FfxUInt32x2 value r, this function packs d.x[0:7] into r.x[24:31],
+/// d.y[0:7] into r.y[24:31], i.x[0:15] into r.x[0:15], r.y[0:15] and i.y[0:7] into r.x[16:23], r.y[16:23] using 3 ops.
+///
+/// r=ffxPermuteUByte3Float16x2ToUint2(d,i)
+/// Where 'k0' is an SGPR with {1.0/32768.0} packed into the lower 16-bits
+/// Where 'k1' is an SGPR with 0x????
+/// Where 'k2' is an SGPR with 0x????
+/// V_PK_FMA_F16 i,i,k0.x,0
+/// V_PERM_B32 r.x,i,i,k1
+/// V_PERM_B32 r.y,i,i,k2
+///
+/// @param [in] d The FfxUInt32x2 value to be packed.
+/// @param [in] i The FfxFloat16x2 value to be packed.
+///
+/// @returns
+/// The packed FfxUInt32x2 value.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxPermuteUByte3Float16x2ToUint2(FfxUInt32x2 d, FfxFloat16x2 i)
+{
+ FfxUInt32 b = FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(i * FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0)));
+ return FfxUInt32x2(ffxPackBytesX0Y2Y1Y0(FfxUInt32x2(d.x, b)), ffxPackBytesX2Y2Y1Y0(FfxUInt32x2(d.y, b)));
+}
+
+/// Given a FfxUInt32x2 value i and a resulting Float16x2 value r, this function packs i.x[0:7] into r.x[0:7] and i.y[0:7] into r.y[0:7] using 2 ops.
+///
+/// @param [in] i The FfxUInt32x2 value to be unpacked.
+///
+/// @returns
+/// The unpacked FfxFloat16x2.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxPermuteUByte0Uint2ToFloat16x2(FfxUInt32x2 i)
+{
+ return FFX_TO_FLOAT16X2(FFX_UINT32_TO_UINT16X2(ffxPackBytesZeroY0ZeroX0(i))) * FFX_BROADCAST_FLOAT16X2(32768.0);
+}
+
+/// Given a FfxUInt32x2 value i and a resulting Float16x2 value r, this function packs i.x[8:15] into r.x[0:7] and i.y[8:15] into r.y[0:7] using 2 ops.
+///
+/// @param [in] i The FfxUInt32x2 value to be unpacked.
+///
+/// @returns
+/// The unpacked FfxFloat16x2.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxPermuteUByte1Uint2ToFloat16x2(FfxUInt32x2 i)
+{
+ return FFX_TO_FLOAT16X2(FFX_UINT32_TO_UINT16X2(ffxPackBytesZeroY1ZeroX1(i))) * FFX_BROADCAST_FLOAT16X2(32768.0);
+}
+
+/// Given a FfxUInt32x2 value i and a resulting Float16x2 value r, this function packs i.x[16:23] into r.x[0:7] and i.y[16:23] into r.y[0:7] using 2 ops.
+///
+/// @param [in] i The FfxUInt32x2 value to be unpacked.
+///
+/// @returns
+/// The unpacked FfxFloat16x2.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxPermuteUByte2Uint2ToFloat16x2(FfxUInt32x2 i)
+{
+ return FFX_TO_FLOAT16X2(FFX_UINT32_TO_UINT16X2(ffxPackBytesZeroY2ZeroX2(i))) * FFX_BROADCAST_FLOAT16X2(32768.0);
+}
+
+/// Given a FfxUInt32x2 value i and a resulting Float16x2 value r, this function packs i.x[24:31] into r.x[0:7] and i.y[24:31] into r.y[0:7] using 2 ops.
+///
+/// @param [in] i The FfxUInt32x2 value to be unpacked.
+///
+/// @returns
+/// The unpacked FfxFloat16x2.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxPermuteUByte3Uint2ToFloat16x2(FfxUInt32x2 i)
+{
+ return FFX_TO_FLOAT16X2(FFX_UINT32_TO_UINT16X2(ffxPackBytesZeroY3ZeroX3(i))) * FFX_BROADCAST_FLOAT16X2(32768.0);
+}
+
+/// Takes two Float16x2 values x and y, normalizes them and builds a single Uint16x2 value in the format {{x0,y0},{x1,y1}}.
+///
+/// @param [in] x The first float16x2 value to pack.
+/// @param [in] y The second float16x2 value to pack.
+///
+/// @returns
+/// The packed FfxUInt32x2 value.
+///
+/// @ingroup GPUCore
+FfxUInt16x2 ffxPackX0Y0X1Y1SignedToUint16x2(FfxFloat16x2 x, FfxFloat16x2 y)
+{
+ x = x * FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0) + FFX_BROADCAST_FLOAT16X2(0.25 / 32768.0);
+ y = y * FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0) + FFX_BROADCAST_FLOAT16X2(0.25 / 32768.0);
+ return FFX_UINT32_TO_UINT16X2(ffxPackBytesY2X2Y0X0(FfxUInt32x2(FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(x)), FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(y)))));
+}
+
+/// Given a FfxUInt32x2 value d, Float16x2 value i and a resulting FfxUInt32x2 value r, this function packs d.x[0:7] into r.x[0:7],
+/// d.y[0:7] into r.y[0:7], i.x[8:15] into r.x[8:15], r.y[8:15] and i.y[0:15] into r.x[16:31], r.y[16:31] using 3 ops.
+///
+/// Handles signed byte values.
+///
+/// @param [in] d The FfxUInt32x2 value to be packed.
+/// @param [in] i The FfxFloat16x2 value to be packed.
+///
+/// @returns
+/// The packed FfxUInt32x2 value.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxPermuteSByte0Float16x2ToUint2(FfxUInt32x2 d, FfxFloat16x2 i)
+{
+ FfxUInt32 b = FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(i * FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0) + FFX_BROADCAST_FLOAT16X2(0.25 / 32768.0)));
+ return FfxUInt32x2(ffxPackBytesY3Y2Y1X0(FfxUInt32x2(d.x, b)), ffxPackBytesY3Y2Y1X2(FfxUInt32x2(d.y, b)));
+}
+
+/// Given a FfxUInt32x2 value d, Float16x2 value i and a resulting FfxUInt32x2 value r, this function packs d.x[0:7] into r.x[8:15],
+/// d.y[0:7] into r.y[8:15], i.x[0:7] into r.x[0:7], r.y[0:7] and i.y[0:15] into r.x[16:31], r.y[16:31] using 3 ops.
+///
+/// Handles signed byte values.
+///
+/// @param [in] d The FfxUInt32x2 value to be packed.
+/// @param [in] i The FfxFloat16x2 value to be packed.
+///
+/// @returns
+/// The packed FfxUInt32x2 value.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxPermuteSByte1Float16x2ToUint2(FfxUInt32x2 d, FfxFloat16x2 i)
+{
+ FfxUInt32 b = FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(i * FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0) + FFX_BROADCAST_FLOAT16X2(0.25 / 32768.0)));
+ return FfxUInt32x2(ffxPackBytesY3Y2X0Y0(FfxUInt32x2(d.x, b)), ffxPackBytesY3Y2X2Y0(FfxUInt32x2(d.y, b)));
+}
+
+/// Given a FfxUInt32x2 value d, Float16x2 value i and a resulting FfxUInt32x2 value r, this function packs d.x[0:7] into r.x[16:23],
+/// d.y[0:7] into r.y[16:23], i.x[0:15] into r.x[0:15], r.y[0:15] and i.y[8:15] into r.x[24:31], r.y[24:31] using 3 ops.
+///
+/// Handles signed byte values.
+///
+/// @param [in] d The FfxUInt32x2 value to be packed.
+/// @param [in] i The FfxFloat16x2 value to be packed.
+///
+/// @returns
+/// The packed FfxUInt32x2 value.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxPermuteSByte2Float16x2ToUint2(FfxUInt32x2 d, FfxFloat16x2 i)
+{
+ FfxUInt32 b = FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(i * FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0) + FFX_BROADCAST_FLOAT16X2(0.25 / 32768.0)));
+ return FfxUInt32x2(ffxPackBytesY3X0Y1Y0(FfxUInt32x2(d.x, b)), ffxPackBytesY3X2Y1Y0(FfxUInt32x2(d.y, b)));
+}
+
+/// Given a FfxUInt32x2 value d, Float16x2 value i and a resulting FfxUInt32x2 value r, this function packs d.x[0:7] into r.x[24:31],
+/// d.y[0:7] into r.y[24:31], i.x[0:15] into r.x[0:15], r.y[0:15] and i.y[0:7] into r.x[16:23], r.y[16:23] using 3 ops.
+///
+/// Handles signed byte values.
+///
+/// @param [in] d The FfxUInt32x2 value to be packed.
+/// @param [in] i The FfxFloat16x2 value to be packed.
+///
+/// @returns
+/// The packed FfxUInt32x2 value.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxPermuteSByte3Float16x2ToUint2(FfxUInt32x2 d, FfxFloat16x2 i)
+{
+ FfxUInt32 b = FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(i * FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0) + FFX_BROADCAST_FLOAT16X2(0.25 / 32768.0)));
+ return FfxUInt32x2(ffxPackBytesX0Y2Y1Y0(FfxUInt32x2(d.x, b)), ffxPackBytesX2Y2Y1Y0(FfxUInt32x2(d.y, b)));
+}
+
+/// Given a FfxUInt32x2 value d, Float16x2 value i and a resulting FfxUInt32x2 value r, this function packs d.x[0:7] into r.x[0:7],
+/// d.y[0:7] into r.y[0:7], i.x[8:15] into r.x[8:15], r.y[8:15] and i.y[0:15] into r.x[16:31], r.y[16:31] using 3 ops.
+///
+/// Zero-based flips the MSB bit of the byte (making 128 "exact zero" actually zero).
+/// This is useful if there is a desire for cleared values to decode as zero.
+///
+/// Handles signed byte values.
+///
+/// @param [in] d The FfxUInt32x2 value to be packed.
+/// @param [in] i The FfxFloat16x2 value to be packed.
+///
+/// @returns
+/// The packed FfxUInt32x2 value.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxPermuteZeroBasedSByte0Float16x2ToUint2(FfxUInt32x2 d, FfxFloat16x2 i)
+{
+ FfxUInt32 b = FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(i * FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0) + FFX_BROADCAST_FLOAT16X2(0.25 / 32768.0))) ^ 0x00800080u;
+ return FfxUInt32x2(ffxPackBytesY3Y2Y1X0(FfxUInt32x2(d.x, b)), ffxPackBytesY3Y2Y1X2(FfxUInt32x2(d.y, b)));
+}
+
+/// Given a FfxUInt32x2 value d, Float16x2 value i and a resulting FfxUInt32x2 value r, this function packs d.x[0:7] into r.x[8:15],
+/// d.y[0:7] into r.y[8:15], i.x[0:7] into r.x[0:7], r.y[0:7] and i.y[0:15] into r.x[16:31], r.y[16:31] using 3 ops.
+///
+/// Zero-based flips the MSB bit of the byte (making 128 "exact zero" actually zero).
+/// This is useful if there is a desire for cleared values to decode as zero.
+///
+/// Handles signed byte values.
+///
+/// @param [in] d The FfxUInt32x2 value to be packed.
+/// @param [in] i The FfxFloat16x2 value to be packed.
+///
+/// @returns
+/// The packed FfxUInt32x2 value.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxPermuteZeroBasedSByte1Float16x2ToUint2(FfxUInt32x2 d, FfxFloat16x2 i)
+{
+ FfxUInt32 b = FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(i * FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0) + FFX_BROADCAST_FLOAT16X2(0.25 / 32768.0))) ^ 0x00800080u;
+ return FfxUInt32x2(ffxPackBytesY3Y2X0Y0(FfxUInt32x2(d.x, b)), ffxPackBytesY3Y2X2Y0(FfxUInt32x2(d.y, b)));
+}
+
+/// Given a FfxUInt32x2 value d, Float16x2 value i and a resulting FfxUInt32x2 value r, this function packs d.x[0:7] into r.x[16:23],
+/// d.y[0:7] into r.y[16:23], i.x[0:15] into r.x[0:15], r.y[0:15] and i.y[8:15] into r.x[24:31], r.y[24:31] using 3 ops.
+///
+/// Zero-based flips the MSB bit of the byte (making 128 "exact zero" actually zero).
+/// This is useful if there is a desire for cleared values to decode as zero.
+///
+/// Handles signed byte values.
+///
+/// @param [in] d The FfxUInt32x2 value to be packed.
+/// @param [in] i The FfxFloat16x2 value to be packed.
+///
+/// @returns
+/// The packed FfxUInt32x2 value.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxPermuteZeroBasedSByte2Float16x2ToUint2(FfxUInt32x2 d, FfxFloat16x2 i)
+{
+ FfxUInt32 b = FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(i * FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0) + FFX_BROADCAST_FLOAT16X2(0.25 / 32768.0))) ^ 0x00800080u;
+ return FfxUInt32x2(ffxPackBytesY3X0Y1Y0(FfxUInt32x2(d.x, b)), ffxPackBytesY3X2Y1Y0(FfxUInt32x2(d.y, b)));
+}
+
+/// Given a FfxUInt32x2 value d, Float16x2 value i and a resulting FfxUInt32x2 value r, this function packs d.x[0:7] into r.x[24:31],
+/// d.y[0:7] into r.y[24:31], i.x[0:15] into r.x[0:15], r.y[0:15] and i.y[0:7] into r.x[16:23], r.y[16:23] using 3 ops.
+///
+/// Zero-based flips the MSB bit of the byte (making 128 "exact zero" actually zero).
+/// This is useful if there is a desire for cleared values to decode as zero.
+///
+/// Handles signed byte values.
+///
+/// @param [in] d The FfxUInt32x2 value to be packed.
+/// @param [in] i The FfxFloat16x2 value to be packed.
+///
+/// @returns
+/// The packed FfxUInt32x2 value.
+///
+/// @ingroup GPUCore
+FfxUInt32x2 ffxPermuteZeroBasedSByte3Float16x2ToUint2(FfxUInt32x2 d, FfxFloat16x2 i)
+{
+ FfxUInt32 b = FFX_UINT16X2_TO_UINT32(FFX_TO_UINT16X2(i * FFX_BROADCAST_FLOAT16X2(1.0 / 32768.0) + FFX_BROADCAST_FLOAT16X2(0.25 / 32768.0))) ^ 0x00800080u;
+ return FfxUInt32x2(ffxPackBytesX0Y2Y1Y0(FfxUInt32x2(d.x, b)), ffxPackBytesX2Y2Y1Y0(FfxUInt32x2(d.y, b)));
+}
+
+/// Given a FfxUInt32x2 value i and a resulting Float16x2 value r, this function packs i.x[0:7] into r.x[0:7] and i.y[0:7] into r.y[0:7] using 2 ops.
+///
+/// Handles signed byte values.
+///
+/// @param [in] i The FfxUInt32x2 value to be unpacked.
+///
+/// @returns
+/// The unpacked FfxFloat16x2.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxPermuteSByte0Uint2ToFloat16x2(FfxUInt32x2 i)
+{
+ return FFX_TO_FLOAT16X2(FFX_UINT32_TO_UINT16X2(ffxPackBytesZeroY0ZeroX0(i))) * FFX_BROADCAST_FLOAT16X2(32768.0) - FFX_BROADCAST_FLOAT16X2(0.25);
+}
+
+/// Given a FfxUInt32x2 value i and a resulting Float16x2 value r, this function packs i.x[8:15] into r.x[0:7] and i.y[8:15] into r.y[0:7] using 2 ops.
+///
+/// Handles signed byte values.
+///
+/// @param [in] i The FfxUInt32x2 value to be unpacked.
+///
+/// @returns
+/// The unpacked FfxFloat16x2.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxPermuteSByte1Uint2ToFloat16x2(FfxUInt32x2 i)
+{
+ return FFX_TO_FLOAT16X2(FFX_UINT32_TO_UINT16X2(ffxPackBytesZeroY1ZeroX1(i))) * FFX_BROADCAST_FLOAT16X2(32768.0) - FFX_BROADCAST_FLOAT16X2(0.25);
+}
+
+/// Given a FfxUInt32x2 value i and a resulting Float16x2 value r, this function packs i.x[16:23] into r.x[0:7] and i.y[16:23] into r.y[0:7] using 2 ops.
+///
+/// Handles signed byte values.
+///
+/// @param [in] i The FfxUInt32x2 value to be unpacked.
+///
+/// @returns
+/// The unpacked FfxFloat16x2.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxPermuteSByte2Uint2ToFloat16x2(FfxUInt32x2 i)
+{
+ return FFX_TO_FLOAT16X2(FFX_UINT32_TO_UINT16X2(ffxPackBytesZeroY2ZeroX2(i))) * FFX_BROADCAST_FLOAT16X2(32768.0) - FFX_BROADCAST_FLOAT16X2(0.25);
+}
+
+/// Given a FfxUInt32x2 value i and a resulting Float16x2 value r, this function packs i.x[24:31] into r.x[0:7] and i.y[24:31] into r.y[0:7] using 2 ops.
+///
+/// Handles signed byte values.
+///
+/// @param [in] i The FfxUInt32x2 value to be unpacked.
+///
+/// @returns
+/// The unpacked FfxFloat16x2.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxPermuteSByte3Uint2ToFloat16x2(FfxUInt32x2 i)
+{
+ return FFX_TO_FLOAT16X2(FFX_UINT32_TO_UINT16X2(ffxPackBytesZeroY3ZeroX3(i))) * FFX_BROADCAST_FLOAT16X2(32768.0) - FFX_BROADCAST_FLOAT16X2(0.25);
+}
+
+/// Given a FfxUInt32x2 value i and a resulting Float16x2 value r, this function packs i.x[0:7] into r.x[0:7] and i.y[0:7] into r.y[0:7] using 2 ops.
+///
+/// Handles signed byte values.
+///
+/// @param [in] i The FfxUInt32x2 value to be unpacked.
+///
+/// @returns
+/// The unpacked FfxFloat16x2.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxPermuteZeroBasedSByte0Uint2ToFloat16x2(FfxUInt32x2 i)
+{
+ return FFX_TO_FLOAT16X2(FFX_UINT32_TO_UINT16X2(ffxPackBytesZeroY0ZeroX0(i) ^ 0x00800080u)) * FFX_BROADCAST_FLOAT16X2(32768.0) - FFX_BROADCAST_FLOAT16X2(0.25);
+}
+
+/// Given a FfxUInt32x2 value i and a resulting Float16x2 value r, this function packs i.x[8:15] into r.x[0:7] and i.y[8:15] into r.y[0:7] using 2 ops.
+///
+/// Handles signed byte values.
+///
+/// @param [in] i The FfxUInt32x2 value to be unpacked.
+///
+/// @returns
+/// The unpacked FfxFloat16x2.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxPermuteZeroBasedSByte1Uint2ToFloat16x2(FfxUInt32x2 i)
+{
+ return FFX_TO_FLOAT16X2(FFX_UINT32_TO_UINT16X2(ffxPackBytesZeroY1ZeroX1(i) ^ 0x00800080u)) * FFX_BROADCAST_FLOAT16X2(32768.0) - FFX_BROADCAST_FLOAT16X2(0.25);
+}
+
+/// Given a FfxUInt32x2 value i and a resulting Float16x2 value r, this function packs i.x[16:23] into r.x[0:7] and i.y[16:23] into r.y[0:7] using 2 ops.
+///
+/// Handles signed byte values.
+///
+/// @param [in] i The FfxUInt32x2 value to be unpacked.
+///
+/// @returns
+/// The unpacked FfxFloat16x2.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxPermuteZeroBasedSByte2Uint2ToFloat16x2(FfxUInt32x2 i)
+{
+ return FFX_TO_FLOAT16X2(FFX_UINT32_TO_UINT16X2(ffxPackBytesZeroY2ZeroX2(i) ^ 0x00800080u)) * FFX_BROADCAST_FLOAT16X2(32768.0) - FFX_BROADCAST_FLOAT16X2(0.25);
+}
+
+/// Given a FfxUInt32x2 value i and a resulting Float16x2 value r, this function packs i.x[24:31] into r.x[0:7] and i.y[24:31] into r.y[0:7] using 2 ops.
+///
+/// Handles signed byte values.
+///
+/// @param [in] i The FfxUInt32x2 value to be unpacked.
+///
+/// @returns
+/// The unpacked FfxFloat16x2.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxPermuteZeroBasedSByte3Uint2ToFloat16x2(FfxUInt32x2 i)
+{
+ return FFX_TO_FLOAT16X2(FFX_UINT32_TO_UINT16X2(ffxPackBytesZeroY3ZeroX3(i) ^ 0x00800080u)) * FFX_BROADCAST_FLOAT16X2(32768.0) - FFX_BROADCAST_FLOAT16X2(0.25);
+}
+
+/// Calculate a half-precision low-quality approximation for the square root of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] a The value to calculate an approximate to the square root for.
+///
+/// @returns
+/// An approximation of the square root, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxApproximateSqrtHalf(FfxFloat16 a)
+{
+ return FFX_TO_FLOAT16((FFX_TO_UINT16(a) >> FFX_BROADCAST_UINT16(1)) + FFX_BROADCAST_UINT16(0x1de2));
+}
+
+/// Calculate a half-precision low-quality approximation for the square root of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] a The value to calculate an approximate to the square root for.
+///
+/// @returns
+/// An approximation of the square root, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxApproximateSqrtHalf(FfxFloat16x2 a)
+{
+ return FFX_TO_FLOAT16X2((FFX_TO_UINT16X2(a) >> FFX_BROADCAST_UINT16X2(1)) + FFX_BROADCAST_UINT16X2(0x1de2));
+}
+
+/// Calculate a half-precision low-quality approximation for the square root of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] a The value to calculate an approximate to the square root for.
+///
+/// @returns
+/// An approximation of the square root, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxApproximateSqrtHalf(FfxFloat16x3 a)
+{
+ return FFX_TO_FLOAT16X3((FFX_TO_UINT16X3(a) >> FFX_BROADCAST_UINT16X3(1)) + FFX_BROADCAST_UINT16X3(0x1de2));
+}
+
+/// Calculate a half-precision low-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] a The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxApproximateReciprocalHalf(FfxFloat16 a)
+{
+ return FFX_TO_FLOAT16(FFX_BROADCAST_UINT16(0x7784) - FFX_TO_UINT16(a));
+}
+
+/// Calculate a half-precision low-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] a The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxApproximateReciprocalHalf(FfxFloat16x2 a)
+{
+ return FFX_TO_FLOAT16X2(FFX_BROADCAST_UINT16X2(0x7784) - FFX_TO_UINT16X2(a));
+}
+
+/// Calculate a half-precision low-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] a The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxApproximateReciprocalHalf(FfxFloat16x3 a)
+{
+ return FFX_TO_FLOAT16X3(FFX_BROADCAST_UINT16X3(0x7784) - FFX_TO_UINT16X3(a));
+}
+
+/// Calculate a half-precision low-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] a The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxApproximateReciprocalHalf(FfxFloat16x4 a)
+{
+ return FFX_TO_FLOAT16X4(FFX_BROADCAST_UINT16X4(0x7784) - FFX_TO_UINT16X4(a));
+}
+
+/// Calculate a half-precision medium-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] a The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to medium quality.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxApproximateReciprocalMediumHalf(FfxFloat16 a)
+{
+ FfxFloat16 b = FFX_TO_FLOAT16(FFX_BROADCAST_UINT16(0x778d) - FFX_TO_UINT16(a));
+ return b * (-b * a + FFX_BROADCAST_FLOAT16(2.0));
+}
+
+/// Calculate a half-precision medium-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] a The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to medium quality.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxApproximateReciprocalMediumHalf(FfxFloat16x2 a)
+{
+ FfxFloat16x2 b = FFX_TO_FLOAT16X2(FFX_BROADCAST_UINT16X2(0x778d) - FFX_TO_UINT16X2(a));
+ return b * (-b * a + FFX_BROADCAST_FLOAT16X2(2.0));
+}
+
+/// Calculate a half-precision medium-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] a The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to medium quality.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxApproximateReciprocalMediumHalf(FfxFloat16x3 a)
+{
+ FfxFloat16x3 b = FFX_TO_FLOAT16X3(FFX_BROADCAST_UINT16X3(0x778d) - FFX_TO_UINT16X3(a));
+ return b * (-b * a + FFX_BROADCAST_FLOAT16X3(2.0));
+}
+
+/// Calculate a half-precision medium-quality approximation for the reciprocal of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] a The value to calculate an approximate to the reciprocal for.
+///
+/// @returns
+/// An approximation of the reciprocal, estimated to medium quality.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxApproximateReciprocalMediumHalf(FfxFloat16x4 a)
+{
+ FfxFloat16x4 b = FFX_TO_FLOAT16X4(FFX_BROADCAST_UINT16X4(0x778d) - FFX_TO_UINT16X4(a));
+ return b * (-b * a + FFX_BROADCAST_FLOAT16X4(2.0));
+}
+
+/// Calculate a half-precision low-quality approximation for the reciprocal of the square root of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] a The value to calculate an approximate to the reciprocal of the square root for.
+///
+/// @returns
+/// An approximation of the reciprocal of the square root, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxApproximateReciprocalSquareRootHalf(FfxFloat16 a)
+{
+ return FFX_TO_FLOAT16(FFX_BROADCAST_UINT16(0x59a3) - (FFX_TO_UINT16(a) >> FFX_BROADCAST_UINT16(1)));
+}
+
+/// Calculate a half-precision low-quality approximation for the reciprocal of the square root of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] a The value to calculate an approximate to the reciprocal of the square root for.
+///
+/// @returns
+/// An approximation of the reciprocal of the square root, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxApproximateReciprocalSquareRootHalf(FfxFloat16x2 a)
+{
+ return FFX_TO_FLOAT16X2(FFX_BROADCAST_UINT16X2(0x59a3) - (FFX_TO_UINT16X2(a) >> FFX_BROADCAST_UINT16X2(1)));
+}
+
+/// Calculate a half-precision low-quality approximation for the reciprocal of the square root of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] a The value to calculate an approximate to the reciprocal of the square root for.
+///
+/// @returns
+/// An approximation of the reciprocal of the square root, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxApproximateReciprocalSquareRootHalf(FfxFloat16x3 a)
+{
+ return FFX_TO_FLOAT16X3(FFX_BROADCAST_UINT16X3(0x59a3) - (FFX_TO_UINT16X3(a) >> FFX_BROADCAST_UINT16X3(1)));
+}
+
+/// Calculate a half-precision low-quality approximation for the reciprocal of the square root of a value.
+///
+/// For additional information on the approximation family of functions, you can refer to Michal Drobot's excellent
+/// presentation materials:
+///
+/// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf
+/// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
+///
+/// @param [in] a The value to calculate an approximate to the reciprocal of the square root for.
+///
+/// @returns
+/// An approximation of the reciprocal of the square root, estimated to low quality.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxApproximateReciprocalSquareRootHalf(FfxFloat16x4 a)
+{
+ return FFX_TO_FLOAT16X4(FFX_BROADCAST_UINT16X4(0x59a3) - (FFX_TO_UINT16X4(a) >> FFX_BROADCAST_UINT16X4(1)));
+}
+
+/// An approximation of sine.
+///
+/// Valid input range is {-1 to 1} representing {0 to 2 pi}, and the output range
+/// is {-1/4 to 1/4} representing {-1 to 1}.
+///
+/// @param [in] x The value to calculate approximate sine for.
+///
+/// @returns
+/// The approximate sine of <c><i>value</i></c>.
+FfxFloat16 ffxParabolicSinHalf(FfxFloat16 x)
+{
+ return x * abs(x) - x;
+}
+
+/// An approximation of sine.
+///
+/// Valid input range is {-1 to 1} representing {0 to 2 pi}, and the output range
+/// is {-1/4 to 1/4} representing {-1 to 1}.
+///
+/// @param [in] x The value to calculate approximate sine for.
+///
+/// @returns
+/// The approximate sine of <c><i>value</i></c>.
+FfxFloat16x2 ffxParabolicSinHalf(FfxFloat16x2 x)
+{
+ return x * abs(x) - x;
+}
+
+/// An approximation of cosine.
+///
+/// Valid input range is {-1 to 1} representing {0 to 2 pi}, and the output range
+/// is {-1/4 to 1/4} representing {-1 to 1}.
+///
+/// @param [in] x The value to calculate approximate cosine for.
+///
+/// @returns
+/// The approximate cosine of <c><i>value</i></c>.
+FfxFloat16 ffxParabolicCosHalf(FfxFloat16 x)
+{
+ x = ffxFract(x * FFX_BROADCAST_FLOAT16(0.5) + FFX_BROADCAST_FLOAT16(0.75));
+ x = x * FFX_BROADCAST_FLOAT16(2.0) - FFX_BROADCAST_FLOAT16(1.0);
+ return ffxParabolicSinHalf(x);
+}
+
+/// An approximation of cosine.
+///
+/// Valid input range is {-1 to 1} representing {0 to 2 pi}, and the output range
+/// is {-1/4 to 1/4} representing {-1 to 1}.
+///
+/// @param [in] x The value to calculate approximate cosine for.
+///
+/// @returns
+/// The approximate cosine of <c><i>value</i></c>.
+FfxFloat16x2 ffxParabolicCosHalf(FfxFloat16x2 x)
+{
+ x = ffxFract(x * FFX_BROADCAST_FLOAT16X2(0.5) + FFX_BROADCAST_FLOAT16X2(0.75));
+ x = x * FFX_BROADCAST_FLOAT16X2(2.0) - FFX_BROADCAST_FLOAT16X2(1.0);
+ return ffxParabolicSinHalf(x);
+}
+
+/// An approximation of both sine and cosine.
+///
+/// Valid input range is {-1 to 1} representing {0 to 2 pi}, and the output range
+/// is {-1/4 to 1/4} representing {-1 to 1}.
+///
+/// @param [in] x The value to calculate approximate cosine for.
+///
+/// @returns
+/// A <c><i>FfxFloat32x2</i></c> containing approximations of both sine and cosine of <c><i>value</i></c>.
+FfxFloat16x2 ffxParabolicSinCosHalf(FfxFloat16 x)
+{
+ FfxFloat16 y = ffxFract(x * FFX_BROADCAST_FLOAT16(0.5) + FFX_BROADCAST_FLOAT16(0.75));
+ y = y * FFX_BROADCAST_FLOAT16(2.0) - FFX_BROADCAST_FLOAT16(1.0);
+ return ffxParabolicSinHalf(FfxFloat16x2(x, y));
+}
+
+/// Conditional free logic AND operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+///
+/// @returns
+/// Result of the AND operation.
+///
+/// @ingroup GPUCore
+FfxUInt16 ffxZeroOneAndHalf(FfxUInt16 x, FfxUInt16 y)
+{
+ return min(x, y);
+}
+
+/// Conditional free logic AND operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+///
+/// @returns
+/// Result of the AND operation.
+///
+/// @ingroup GPUCore
+FfxUInt16x2 ffxZeroOneAndHalf(FfxUInt16x2 x, FfxUInt16x2 y)
+{
+ return min(x, y);
+}
+
+/// Conditional free logic AND operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+///
+/// @returns
+/// Result of the AND operation.
+///
+/// @ingroup GPUCore
+FfxUInt16x3 ffxZeroOneAndHalf(FfxUInt16x3 x, FfxUInt16x3 y)
+{
+ return min(x, y);
+}
+
+/// Conditional free logic AND operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+///
+/// @returns
+/// Result of the AND operation.
+///
+/// @ingroup GPUCore
+FfxUInt16x4 ffxZeroOneAndHalf(FfxUInt16x4 x, FfxUInt16x4 y)
+{
+ return min(x, y);
+}
+
+/// Conditional free logic NOT operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the NOT operator.
+/// @param [in] y The second value to be fed into the NOT operator.
+///
+/// @returns
+/// Result of the NOT operation.
+///
+/// @ingroup GPUCore
+FfxUInt16 ffxZeroOneNotHalf(FfxUInt16 x)
+{
+ return x ^ FFX_BROADCAST_UINT16(1);
+}
+
+/// Conditional free logic NOT operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the NOT operator.
+/// @param [in] y The second value to be fed into the NOT operator.
+///
+/// @returns
+/// Result of the NOT operation.
+///
+/// @ingroup GPUCore
+FfxUInt16x2 ffxZeroOneNotHalf(FfxUInt16x2 x)
+{
+ return x ^ FFX_BROADCAST_UINT16X2(1);
+}
+
+/// Conditional free logic NOT operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the NOT operator.
+/// @param [in] y The second value to be fed into the NOT operator.
+///
+/// @returns
+/// Result of the NOT operation.
+///
+/// @ingroup GPUCore
+FfxUInt16x3 ffxZeroOneNotHalf(FfxUInt16x3 x)
+{
+ return x ^ FFX_BROADCAST_UINT16X3(1);
+}
+
+/// Conditional free logic NOT operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the NOT operator.
+/// @param [in] y The second value to be fed into the NOT operator.
+///
+/// @returns
+/// Result of the NOT operation.
+///
+/// @ingroup GPUCore
+FfxUInt16x4 ffxZeroOneNotHalf(FfxUInt16x4 x)
+{
+ return x ^ FFX_BROADCAST_UINT16X4(1);
+}
+
+/// Conditional free logic OR operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxUInt16 ffxZeroOneOrHalf(FfxUInt16 x, FfxUInt16 y)
+{
+ return max(x, y);
+}
+
+/// Conditional free logic OR operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxUInt16x2 ffxZeroOneOrHalf(FfxUInt16x2 x, FfxUInt16x2 y)
+{
+ return max(x, y);
+}
+
+/// Conditional free logic OR operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxUInt16x3 ffxZeroOneOrHalf(FfxUInt16x3 x, FfxUInt16x3 y)
+{
+ return max(x, y);
+}
+
+/// Conditional free logic OR operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxUInt16x4 ffxZeroOneOrHalf(FfxUInt16x4 x, FfxUInt16x4 y)
+{
+ return max(x, y);
+}
+
+/// Convert a half-precision FfxFloat32 value between 0.0f and 1.0f to a half-precision Uint.
+///
+/// @param [in] x The value to converted to a Uint.
+///
+/// @returns
+/// The converted Uint value.
+///
+/// @ingroup GPUCore
+FfxUInt16 ffxZeroOneFloat16ToUint16(FfxFloat16 x)
+{
+ return FFX_TO_UINT16(x * FFX_TO_FLOAT16(FFX_TO_UINT16(1)));
+}
+
+/// Convert a half-precision FfxFloat32 value between 0.0f and 1.0f to a half-precision Uint.
+///
+/// @param [in] x The value to converted to a Uint.
+///
+/// @returns
+/// The converted Uint value.
+///
+/// @ingroup GPUCore
+FfxUInt16x2 ffxZeroOneFloat16x2ToUint16x2(FfxFloat16x2 x)
+{
+ return FFX_TO_UINT16X2(x * FFX_TO_FLOAT16X2(FfxUInt16x2(1, 1)));
+}
+
+/// Convert a half-precision FfxFloat32 value between 0.0f and 1.0f to a half-precision Uint.
+///
+/// @param [in] x The value to converted to a Uint.
+///
+/// @returns
+/// The converted Uint value.
+///
+/// @ingroup GPUCore
+FfxUInt16x3 ffxZeroOneFloat16x3ToUint16x3(FfxFloat16x3 x)
+{
+ return FFX_TO_UINT16X3(x * FFX_TO_FLOAT16X3(FfxUInt16x3(1, 1, 1)));
+}
+
+/// Convert a half-precision FfxFloat32 value between 0.0f and 1.0f to a half-precision Uint.
+///
+/// @param [in] x The value to converted to a Uint.
+///
+/// @returns
+/// The converted Uint value.
+///
+/// @ingroup GPUCore
+FfxUInt16x4 ffxZeroOneFloat16x4ToUint16x4(FfxFloat16x4 x)
+{
+ return FFX_TO_UINT16X4(x * FFX_TO_FLOAT16X4(FfxUInt16x4(1, 1, 1, 1)));
+}
+
+/// Convert a half-precision FfxUInt32 value between 0 and 1 to a half-precision FfxFloat32.
+///
+/// @param [in] x The value to converted to a half-precision FfxFloat32.
+///
+/// @returns
+/// The converted half-precision FfxFloat32 value.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxZeroOneUint16ToFloat16(FfxUInt16 x)
+{
+ return FFX_TO_FLOAT16(x * FFX_TO_UINT16(FFX_TO_FLOAT16(1.0)));
+}
+
+/// Convert a half-precision FfxUInt32 value between 0 and 1 to a half-precision FfxFloat32.
+///
+/// @param [in] x The value to converted to a half-precision FfxFloat32.
+///
+/// @returns
+/// The converted half-precision FfxFloat32 value.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxZeroOneUint16x2ToFloat16x2(FfxUInt16x2 x)
+{
+ return FFX_TO_FLOAT16X2(x * FFX_TO_UINT16X2(FfxUInt16x2(FFX_TO_FLOAT16(1.0), FFX_TO_FLOAT16(1.0))));
+}
+
+/// Convert a half-precision FfxUInt32 value between 0 and 1 to a half-precision FfxFloat32.
+///
+/// @param [in] x The value to converted to a half-precision FfxFloat32.
+///
+/// @returns
+/// The converted half-precision FfxFloat32 value.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxZeroOneUint16x3ToFloat16x3(FfxUInt16x3 x)
+{
+ return FFX_TO_FLOAT16X3(x * FFX_TO_UINT16X3(FfxUInt16x3(FFX_TO_FLOAT16(1.0), FFX_TO_FLOAT16(1.0), FFX_TO_FLOAT16(1.0))));
+}
+
+/// Convert a half-precision FfxUInt32 value between 0 and 1 to a half-precision FfxFloat32.
+///
+/// @param [in] x The value to converted to a half-precision FfxFloat32.
+///
+/// @returns
+/// The converted half-precision FfxFloat32 value.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxZeroOneUint16x4ToFloat16x4(FfxUInt16x4 x)
+{
+ return FFX_TO_FLOAT16X4(x * FFX_TO_UINT16X4(FfxUInt16x4(FFX_TO_FLOAT16(1.0), FFX_TO_FLOAT16(1.0), FFX_TO_FLOAT16(1.0), FFX_TO_FLOAT16(1.0))));
+}
+
+/// Conditional free logic AND operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+///
+/// @returns
+/// Result of the AND operation.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxZeroOneAndHalf(FfxFloat16 x, FfxFloat16 y)
+{
+ return min(x, y);
+}
+
+/// Conditional free logic AND operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+///
+/// @returns
+/// Result of the AND operation.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxZeroOneAndHalf(FfxFloat16x2 x, FfxFloat16x2 y)
+{
+ return min(x, y);
+}
+
+/// Conditional free logic AND operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+///
+/// @returns
+/// Result of the AND operation.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxZeroOneAndHalf(FfxFloat16x3 x, FfxFloat16x3 y)
+{
+ return min(x, y);
+}
+
+/// Conditional free logic AND operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+///
+/// @returns
+/// Result of the AND operation.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxZeroOneAndHalf(FfxFloat16x4 x, FfxFloat16x4 y)
+{
+ return min(x, y);
+}
+
+/// Conditional free logic AND NOT operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the AND NOT operator.
+/// @param [in] y The second value to be fed into the AND NOT operator.
+///
+/// @returns
+/// Result of the AND NOT operation.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxSignedZeroOneAndOrHalf(FfxFloat16 x, FfxFloat16 y)
+{
+ return (-x) * y + FFX_BROADCAST_FLOAT16(1.0);
+}
+
+/// Conditional free logic AND NOT operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the AND NOT operator.
+/// @param [in] y The second value to be fed into the AND NOT operator.
+///
+/// @returns
+/// Result of the AND NOT operation.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxSignedZeroOneAndOrHalf(FfxFloat16x2 x, FfxFloat16x2 y)
+{
+ return (-x) * y + FFX_BROADCAST_FLOAT16X2(1.0);
+}
+
+/// Conditional free logic AND NOT operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the AND NOT operator.
+/// @param [in] y The second value to be fed into the AND NOT operator.
+///
+/// @returns
+/// Result of the AND NOT operation.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxSignedZeroOneAndOrHalf(FfxFloat16x3 x, FfxFloat16x3 y)
+{
+ return (-x) * y + FFX_BROADCAST_FLOAT16X3(1.0);
+}
+
+/// Conditional free logic AND NOT operation using two half-precision values.
+///
+/// @param [in] x The first value to be fed into the AND NOT operator.
+/// @param [in] y The second value to be fed into the AND NOT operator.
+///
+/// @returns
+/// Result of the AND NOT operation.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxSignedZeroOneAndOrHalf(FfxFloat16x4 x, FfxFloat16x4 y)
+{
+ return (-x) * y + FFX_BROADCAST_FLOAT16X4(1.0);
+}
+
+/// Conditional free logic AND operation using two half-precision values followed by
+/// a NOT operation using the resulting value and a third half-precision value.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+/// @param [in] z The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxZeroOneAndOrHalf(FfxFloat16 x, FfxFloat16 y, FfxFloat16 z)
+{
+ return ffxSaturate(x * y + z);
+}
+
+/// Conditional free logic AND operation using two half-precision values followed by
+/// a NOT operation using the resulting value and a third half-precision value.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+/// @param [in] z The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxZeroOneAndOrHalf(FfxFloat16x2 x, FfxFloat16x2 y, FfxFloat16x2 z)
+{
+ return ffxSaturate(x * y + z);
+}
+
+/// Conditional free logic AND operation using two half-precision values followed by
+/// a NOT operation using the resulting value and a third half-precision value.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+/// @param [in] z The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxZeroOneAndOrHalf(FfxFloat16x3 x, FfxFloat16x3 y, FfxFloat16x3 z)
+{
+ return ffxSaturate(x * y + z);
+}
+
+/// Conditional free logic AND operation using two half-precision values followed by
+/// a NOT operation using the resulting value and a third half-precision value.
+///
+/// @param [in] x The first value to be fed into the AND operator.
+/// @param [in] y The second value to be fed into the AND operator.
+/// @param [in] z The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxZeroOneAndOrHalf(FfxFloat16x4 x, FfxFloat16x4 y, FfxFloat16x4 z)
+{
+ return ffxSaturate(x * y + z);
+}
+
+/// Given a half-precision value, returns 1.0 if greater than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the greater than zero comparison.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxZeroOneIsGreaterThanZeroHalf(FfxFloat16 x)
+{
+ return ffxSaturate(x * FFX_BROADCAST_FLOAT16(FFX_POSITIVE_INFINITY_HALF));
+}
+
+/// Given a half-precision value, returns 1.0 if greater than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the greater than zero comparison.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxZeroOneIsGreaterThanZeroHalf(FfxFloat16x2 x)
+{
+ return ffxSaturate(x * FFX_BROADCAST_FLOAT16X2(FFX_POSITIVE_INFINITY_HALF));
+}
+
+/// Given a half-precision value, returns 1.0 if greater than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the greater than zero comparison.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxZeroOneIsGreaterThanZeroHalf(FfxFloat16x3 x)
+{
+ return ffxSaturate(x * FFX_BROADCAST_FLOAT16X3(FFX_POSITIVE_INFINITY_HALF));
+}
+
+/// Given a half-precision value, returns 1.0 if greater than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the greater than zero comparison.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxZeroOneIsGreaterThanZeroHalf(FfxFloat16x4 x)
+{
+ return ffxSaturate(x * FFX_BROADCAST_FLOAT16X4(FFX_POSITIVE_INFINITY_HALF));
+}
+
+/// Conditional free logic signed NOT operation using two half-precision FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the AND OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxZeroOneNotHalf(FfxFloat16 x)
+{
+ return FFX_BROADCAST_FLOAT16(1.0) - x;
+}
+
+/// Conditional free logic signed NOT operation using two half-precision FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the AND OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxZeroOneNotHalf(FfxFloat16x2 x)
+{
+ return FFX_BROADCAST_FLOAT16X2(1.0) - x;
+}
+
+/// Conditional free logic signed NOT operation using two half-precision FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the AND OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxZeroOneNotHalf(FfxFloat16x3 x)
+{
+ return FFX_BROADCAST_FLOAT16X3(1.0) - x;
+}
+
+/// Conditional free logic signed NOT operation using two half-precision FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the AND OR operator.
+///
+/// @returns
+/// Result of the AND OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxZeroOneNotHalf(FfxFloat16x4 x)
+{
+ return FFX_BROADCAST_FLOAT16X4(1.0) - x;
+}
+
+/// Conditional free logic OR operation using two half-precision FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxZeroOneOrHalf(FfxFloat16 x, FfxFloat16 y)
+{
+ return max(x, y);
+}
+
+/// Conditional free logic OR operation using two half-precision FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxZeroOneOrHalf(FfxFloat16x2 x, FfxFloat16x2 y)
+{
+ return max(x, y);
+}
+
+/// Conditional free logic OR operation using two half-precision FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxZeroOneOrHalf(FfxFloat16x3 x, FfxFloat16x3 y)
+{
+ return max(x, y);
+}
+
+/// Conditional free logic OR operation using two half-precision FfxFloat32 values.
+///
+/// @param [in] x The first value to be fed into the OR operator.
+/// @param [in] y The second value to be fed into the OR operator.
+///
+/// @returns
+/// Result of the OR operation.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxZeroOneOrHalf(FfxFloat16x4 x, FfxFloat16x4 y)
+{
+ return max(x, y);
+}
+
+/// Choose between two half-precision FfxFloat32 values if the first paramter is greater than zero.
+///
+/// @param [in] x The value to compare against zero.
+/// @param [in] y The value to return if the comparision is greater than zero.
+/// @param [in] z The value to return if the comparision is less than or equal to zero.
+///
+/// @returns
+/// The selected value.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxZeroOneSelectHalf(FfxFloat16 x, FfxFloat16 y, FfxFloat16 z)
+{
+ FfxFloat16 r = (-x) * z + z;
+ return x * y + r;
+}
+
+/// Choose between two half-precision FfxFloat32 values if the first paramter is greater than zero.
+///
+/// @param [in] x The value to compare against zero.
+/// @param [in] y The value to return if the comparision is greater than zero.
+/// @param [in] z The value to return if the comparision is less than or equal to zero.
+///
+/// @returns
+/// The selected value.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxZeroOneSelectHalf(FfxFloat16x2 x, FfxFloat16x2 y, FfxFloat16x2 z)
+{
+ FfxFloat16x2 r = (-x) * z + z;
+ return x * y + r;
+}
+
+/// Choose between two half-precision FfxFloat32 values if the first paramter is greater than zero.
+///
+/// @param [in] x The value to compare against zero.
+/// @param [in] y The value to return if the comparision is greater than zero.
+/// @param [in] z The value to return if the comparision is less than or equal to zero.
+///
+/// @returns
+/// The selected value.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxZeroOneSelectHalf(FfxFloat16x3 x, FfxFloat16x3 y, FfxFloat16x3 z)
+{
+ FfxFloat16x3 r = (-x) * z + z;
+ return x * y + r;
+}
+
+/// Choose between two half-precision FfxFloat32 values if the first paramter is greater than zero.
+///
+/// @param [in] x The value to compare against zero.
+/// @param [in] y The value to return if the comparision is greater than zero.
+/// @param [in] z The value to return if the comparision is less than or equal to zero.
+///
+/// @returns
+/// The selected value.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxZeroOneSelectHalf(FfxFloat16x4 x, FfxFloat16x4 y, FfxFloat16x4 z)
+{
+ FfxFloat16x4 r = (-x) * z + z;
+ return x * y + r;
+}
+
+/// Given a half-precision value, returns 1.0 if less than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the sign value.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxZeroOneIsSignedHalf(FfxFloat16 x)
+{
+ return ffxSaturate(x * FFX_BROADCAST_FLOAT16(FFX_NEGATIVE_INFINITY_HALF));
+}
+
+/// Given a half-precision value, returns 1.0 if less than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the sign value.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxZeroOneIsSignedHalf(FfxFloat16x2 x)
+{
+ return ffxSaturate(x * FFX_BROADCAST_FLOAT16X2(FFX_NEGATIVE_INFINITY_HALF));
+}
+
+/// Given a half-precision value, returns 1.0 if less than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the sign value.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxZeroOneIsSignedHalf(FfxFloat16x3 x)
+{
+ return ffxSaturate(x * FFX_BROADCAST_FLOAT16X3(FFX_NEGATIVE_INFINITY_HALF));
+}
+
+/// Given a half-precision value, returns 1.0 if less than zero and 0.0 if not.
+///
+/// @param [in] x The value to be compared.
+///
+/// @returns
+/// Result of the sign value.
+///
+/// @ingroup GPUCore
+FfxFloat16x4 ffxZeroOneIsSignedHalf(FfxFloat16x4 x)
+{
+ return ffxSaturate(x * FFX_BROADCAST_FLOAT16X4(FFX_NEGATIVE_INFINITY_HALF));
+}
+
+/// Compute a Rec.709 color space.
+///
+/// Rec.709 is used for some HDTVs.
+///
+/// Both Rec.709 and sRGB have a linear segment which as spec'ed would intersect the curved segment 2 times.
+/// (a.) For 8-bit sRGB, steps {0 to 10.3} are in the linear region (4% of the encoding range).
+/// (b.) For 8-bit 709, steps {0 to 20.7} are in the linear region (8% of the encoding range).
+///
+/// @param [in] c The color to convert to Rec. 709.
+///
+/// @returns
+/// The <c><i>color</i></c> in Rec.709 space.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxRec709FromLinearHalf(FfxFloat16 c)
+{
+ FfxFloat16x3 j = FfxFloat16x3(0.018 * 4.5, 4.5, 0.45);
+ FfxFloat16x2 k = FfxFloat16x2(1.099, -0.099);
+ return clamp(j.x, c * j.y, pow(c, j.z) * k.x + k.y);
+}
+
+/// Compute a Rec.709 color space.
+///
+/// Rec.709 is used for some HDTVs.
+///
+/// Both Rec.709 and sRGB have a linear segment which as spec'ed would intersect the curved segment 2 times.
+/// (a.) For 8-bit sRGB, steps {0 to 10.3} are in the linear region (4% of the encoding range).
+/// (b.) For 8-bit 709, steps {0 to 20.7} are in the linear region (8% of the encoding range).
+///
+/// @param [in] c The color to convert to Rec. 709.
+///
+/// @returns
+/// The <c><i>color</i></c> in Rec.709 space.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxRec709FromLinearHalf(FfxFloat16x2 c)
+{
+ FfxFloat16x3 j = FfxFloat16x3(0.018 * 4.5, 4.5, 0.45);
+ FfxFloat16x2 k = FfxFloat16x2(1.099, -0.099);
+ return clamp(j.xx, c * j.yy, pow(c, j.zz) * k.xx + k.yy);
+}
+
+/// Compute a Rec.709 color space.
+///
+/// Rec.709 is used for some HDTVs.
+///
+/// Both Rec.709 and sRGB have a linear segment which as spec'ed would intersect the curved segment 2 times.
+/// (a.) For 8-bit sRGB, steps {0 to 10.3} are in the linear region (4% of the encoding range).
+/// (b.) For 8-bit 709, steps {0 to 20.7} are in the linear region (8% of the encoding range).
+///
+/// @param [in] c The color to convert to Rec. 709.
+///
+/// @returns
+/// The <c><i>color</i></c> in Rec.709 space.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxRec709FromLinearHalf(FfxFloat16x3 c)
+{
+ FfxFloat16x3 j = FfxFloat16x3(0.018 * 4.5, 4.5, 0.45);
+ FfxFloat16x2 k = FfxFloat16x2(1.099, -0.099);
+ return clamp(j.xxx, c * j.yyy, pow(c, j.zzz) * k.xxx + k.yyy);
+}
+
+/// Compute a gamma value from a linear value.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// Note: 'rcpX' is '1/x', where the 'x' is what would be used in <c><i>ffxLinearFromGammaHalf</i></c>.
+///
+/// @param [in] c The value to convert to gamma space from linear.
+/// @param [in] rcpX The reciprocal of power value used for the gamma curve.
+///
+/// @returns
+/// A value in gamma space.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxGammaFromLinearHalf(FfxFloat16 c, FfxFloat16 rcpX)
+{
+ return pow(c, FFX_BROADCAST_FLOAT16(rcpX));
+}
+
+/// Compute a gamma value from a linear value.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// Note: 'rcpX' is '1/x', where the 'x' is what would be used in <c><i>ffxLinearFromGammaHalf</i></c>.
+///
+/// @param [in] c The value to convert to gamma space from linear.
+/// @param [in] rcpX The reciprocal of power value used for the gamma curve.
+///
+/// @returns
+/// A value in gamma space.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxGammaFromLinearHalf(FfxFloat16x2 c, FfxFloat16 rcpX)
+{
+ return pow(c, FFX_BROADCAST_FLOAT16X2(rcpX));
+}
+
+/// Compute a gamma value from a linear value.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// Note: 'rcpX' is '1/x', where the 'x' is what would be used in <c><i>ffxLinearFromGammaHalf</i></c>.
+///
+/// @param [in] c The value to convert to gamma space from linear.
+/// @param [in] rcpX The reciprocal of power value used for the gamma curve.
+///
+/// @returns
+/// A value in gamma space.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxGammaFromLinearHalf(FfxFloat16x3 c, FfxFloat16 rcpX)
+{
+ return pow(c, FFX_BROADCAST_FLOAT16X3(rcpX));
+}
+
+/// Compute an SRGB value from a linear value.
+///
+/// @param [in] c The value to convert to SRGB from linear.
+///
+/// @returns
+/// A value in SRGB space.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxSrgbFromLinearHalf(FfxFloat16 c)
+{
+ FfxFloat16x3 j = FfxFloat16x3(0.0031308 * 12.92, 12.92, 1.0 / 2.4);
+ FfxFloat16x2 k = FfxFloat16x2(1.055, -0.055);
+ return clamp(j.x, c * j.y, pow(c, j.z) * k.x + k.y);
+}
+
+/// Compute an SRGB value from a linear value.
+///
+/// @param [in] c The value to convert to SRGB from linear.
+///
+/// @returns
+/// A value in SRGB space.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxSrgbFromLinearHalf(FfxFloat16x2 c)
+{
+ FfxFloat16x3 j = FfxFloat16x3(0.0031308 * 12.92, 12.92, 1.0 / 2.4);
+ FfxFloat16x2 k = FfxFloat16x2(1.055, -0.055);
+ return clamp(j.xx, c * j.yy, pow(c, j.zz) * k.xx + k.yy);
+}
+
+/// Compute an SRGB value from a linear value.
+///
+/// @param [in] c The value to convert to SRGB from linear.
+///
+/// @returns
+/// A value in SRGB space.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxSrgbFromLinearHalf(FfxFloat16x3 c)
+{
+ FfxFloat16x3 j = FfxFloat16x3(0.0031308 * 12.92, 12.92, 1.0 / 2.4);
+ FfxFloat16x2 k = FfxFloat16x2(1.055, -0.055);
+ return clamp(j.xxx, c * j.yyy, pow(c, j.zzz) * k.xxx + k.yyy);
+}
+
+/// Compute the square root of a value.
+///
+/// @param [in] c The value to compute the square root for.
+///
+/// @returns
+/// A square root of the input value.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxSquareRootHalf(FfxFloat16 c)
+{
+ return sqrt(c);
+}
+
+/// Compute the square root of a value.
+///
+/// @param [in] c The value to compute the square root for.
+///
+/// @returns
+/// A square root of the input value.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxSquareRootHalf(FfxFloat16x2 c)
+{
+ return sqrt(c);
+}
+
+/// Compute the square root of a value.
+///
+/// @param [in] c The value to compute the square root for.
+///
+/// @returns
+/// A square root of the input value.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxSquareRootHalf(FfxFloat16x3 c)
+{
+ return sqrt(c);
+}
+
+/// Compute the cube root of a value.
+///
+/// @param [in] c The value to compute the cube root for.
+///
+/// @returns
+/// A cube root of the input value.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxCubeRootHalf(FfxFloat16 c)
+{
+ return pow(c, FFX_BROADCAST_FLOAT16(1.0 / 3.0));
+}
+
+/// Compute the cube root of a value.
+///
+/// @param [in] c The value to compute the cube root for.
+///
+/// @returns
+/// A cube root of the input value.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxCubeRootHalf(FfxFloat16x2 c)
+{
+ return pow(c, FFX_BROADCAST_FLOAT16X2(1.0 / 3.0));
+}
+
+/// Compute the cube root of a value.
+///
+/// @param [in] c The value to compute the cube root for.
+///
+/// @returns
+/// A cube root of the input value.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxCubeRootHalf(FfxFloat16x3 c)
+{
+ return pow(c, FFX_BROADCAST_FLOAT16X3(1.0 / 3.0));
+}
+
+/// Compute a linear value from a REC.709 value.
+///
+/// @param [in] c The value to convert to linear from REC.709.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxLinearFromRec709Half(FfxFloat16 c)
+{
+ FfxFloat16x3 j = FfxFloat16x3(0.081 / 4.5, 1.0 / 4.5, 1.0 / 0.45);
+ FfxFloat16x2 k = FfxFloat16x2(1.0 / 1.099, 0.099 / 1.099);
+ return ffxZeroOneSelectHalf(ffxZeroOneIsSignedHalf(c - j.x), c * j.y, pow(c * k.x + k.y, j.z));
+}
+
+/// Compute a linear value from a REC.709 value.
+///
+/// @param [in] c The value to convert to linear from REC.709.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxLinearFromRec709Half(FfxFloat16x2 c)
+{
+ FfxFloat16x3 j = FfxFloat16x3(0.081 / 4.5, 1.0 / 4.5, 1.0 / 0.45);
+ FfxFloat16x2 k = FfxFloat16x2(1.0 / 1.099, 0.099 / 1.099);
+ return ffxZeroOneSelectHalf(ffxZeroOneIsSignedHalf(c - j.xx), c * j.yy, pow(c * k.xx + k.yy, j.zz));
+}
+
+/// Compute a linear value from a REC.709 value.
+///
+/// @param [in] c The value to convert to linear from REC.709.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxLinearFromRec709Half(FfxFloat16x3 c)
+{
+ FfxFloat16x3 j = FfxFloat16x3(0.081 / 4.5, 1.0 / 4.5, 1.0 / 0.45);
+ FfxFloat16x2 k = FfxFloat16x2(1.0 / 1.099, 0.099 / 1.099);
+ return ffxZeroOneSelectHalf(ffxZeroOneIsSignedHalf(c - j.xxx), c * j.yyy, pow(c * k.xxx + k.yyy, j.zzz));
+}
+
+/// Compute a linear value from a value in a gamma space.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// @param [in] c The value to convert to linear in gamma space.
+/// @param [in] x The power value used for the gamma curve.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxLinearFromGammaHalf(FfxFloat16 c, FfxFloat16 x)
+{
+ return pow(c, FFX_BROADCAST_FLOAT16(x));
+}
+
+/// Compute a linear value from a value in a gamma space.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// @param [in] c The value to convert to linear in gamma space.
+/// @param [in] x The power value used for the gamma curve.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxLinearFromGammaHalf(FfxFloat16x2 c, FfxFloat16 x)
+{
+ return pow(c, FFX_BROADCAST_FLOAT16X2(x));
+}
+
+/// Compute a linear value from a value in a gamma space.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// @param [in] c The value to convert to linear in gamma space.
+/// @param [in] x The power value used for the gamma curve.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxLinearFromGammaHalf(FfxFloat16x3 c, FfxFloat16 x)
+{
+ return pow(c, FFX_BROADCAST_FLOAT16X3(x));
+}
+
+/// Compute a linear value from a value in a SRGB space.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// @param [in] c The value to convert to linear in SRGB space.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat16 ffxLinearFromSrgbHalf(FfxFloat16 c)
+{
+ FfxFloat16x3 j = FfxFloat16x3(0.04045, 1.0 / 12.92, 2.4);
+ FfxFloat16x2 k = FfxFloat16x2(1.0 / 1.055, 0.055 / 1.055);
+ return ffxZeroOneSelectHalf(ffxZeroOneIsSignedHalf(c - j.x), c * j.y, pow(c * k.x + k.y, j.z));
+}
+
+/// Compute a linear value from a value in a SRGB space.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// @param [in] c The value to convert to linear in SRGB space.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat16x2 ffxLinearFromSrgbHalf(FfxFloat16x2 c)
+{
+ FfxFloat16x3 j = FfxFloat16x3(0.04045, 1.0 / 12.92, 2.4);
+ FfxFloat16x2 k = FfxFloat16x2(1.0 / 1.055, 0.055 / 1.055);
+ return ffxZeroOneSelectHalf(ffxZeroOneIsSignedHalf(c - j.xx), c * j.yy, pow(c * k.xx + k.yy, j.zz));
+}
+
+/// Compute a linear value from a value in a SRGB space.
+///
+/// Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native.
+///
+/// @param [in] c The value to convert to linear in SRGB space.
+///
+/// @returns
+/// A value in linear space.
+///
+/// @ingroup GPUCore
+FfxFloat16x3 ffxLinearFromSrgbHalf(FfxFloat16x3 c)
+{
+ FfxFloat16x3 j = FfxFloat16x3(0.04045, 1.0 / 12.92, 2.4);
+ FfxFloat16x2 k = FfxFloat16x2(1.0 / 1.055, 0.055 / 1.055);
+ return ffxZeroOneSelectHalf(ffxZeroOneIsSignedHalf(c - j.xxx), c * j.yyy, pow(c * k.xxx + k.yyy, j.zzz));
+}
+
+/// A remapping of 64x1 to 8x8 imposing rotated 2x2 pixel quads in quad linear.
+///
+/// Remap illustration:
+///
+/// 543210
+/// ~~~~~~
+/// ..xxx.
+/// yy...y
+///
+/// @param [in] a The input 1D coordinates to remap.
+///
+/// @returns
+/// The remapped 2D coordinates.
+///
+/// @ingroup GPUCore
+FfxUInt16x2 ffxRemapForQuadHalf(FfxUInt32 a)
+{
+ return FfxUInt16x2(ffxBitfieldExtract(a, 1u, 3u), ffxBitfieldInsertMask(ffxBitfieldExtract(a, 3u, 3u), a, 1u));
+}
+
+/// A helper function performing a remap 64x1 to 8x8 remapping which is necessary for 2D wave reductions.
+///
+/// The 64-wide lane indices to 8x8 remapping is performed as follows:
+///
+/// 00 01 08 09 10 11 18 19
+/// 02 03 0a 0b 12 13 1a 1b
+/// 04 05 0c 0d 14 15 1c 1d
+/// 06 07 0e 0f 16 17 1e 1f
+/// 20 21 28 29 30 31 38 39
+/// 22 23 2a 2b 32 33 3a 3b
+/// 24 25 2c 2d 34 35 3c 3d
+/// 26 27 2e 2f 36 37 3e 3f
+///
+/// @param [in] a The input 1D coordinate to remap.
+///
+/// @returns
+/// The remapped 2D coordinates.
+///
+/// @ingroup GPUCore
+FfxUInt16x2 ffxRemapForWaveReductionHalf(FfxUInt32 a)
+{
+ return FfxUInt16x2(ffxBitfieldInsertMask(ffxBitfieldExtract(a, 2u, 3u), a, 1u), ffxBitfieldInsertMask(ffxBitfieldExtract(a, 3u, 3u), ffxBitfieldExtract(a, 1u, 2u), 2u));
+}
+
+#endif // FFX_HALF
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_portability.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_portability.h
new file mode 100644
index 0000000000..12147b9a7b
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/ffx_core_portability.h
@@ -0,0 +1,46 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+void ffxOpAAddOneF3(FFX_PARAMETER_OUT FfxFloat32x3 d, FfxFloat32x3 a, FfxFloat32 b)
+{
+ d = a + ffxBroadcast3(b);
+}
+
+void ffxOpACpyF3(FFX_PARAMETER_OUT FfxFloat32x3 d, FfxFloat32x3 a)
+{
+ d = a;
+}
+
+void ffxOpAMulF3(FFX_PARAMETER_OUT FfxFloat32x3 d, FfxFloat32x3 a, FfxFloat32x3 b)
+{
+ d = a * b;
+}
+
+void ffxOpAMulOneF3(FFX_PARAMETER_OUT FfxFloat32x3 d, FfxFloat32x3 a, FfxFloat32 b)
+{
+ d = a * b;
+}
+
+void ffxOpARcpF3(FFX_PARAMETER_OUT FfxFloat32x3 d, FfxFloat32x3 a)
+{
+ d = ffxReciprocal(a);
+}
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr1/ffx_fsr1.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr1/ffx_fsr1.h
new file mode 100644
index 0000000000..82ebf21fed
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr1/ffx_fsr1.h
@@ -0,0 +1,1252 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+/// @defgroup FfxGPUFsr1 FidelityFX FSR1
+/// FidelityFX Super Resolution 1 GPU documentation
+///
+/// @ingroup FfxGPUEffects
+
+/// Setup required constant values for EASU (works on CPU or GPU).
+///
+/// @param [out] con0
+/// @param [out] con1
+/// @param [out] con2
+/// @param [out] con3
+/// @param [in] inputViewportInPixelsX The rendered image resolution being upscaled in X dimension.
+/// @param [in] inputViewportInPixelsY The rendered image resolution being upscaled in Y dimension.
+/// @param [in] inputSizeInPixelsX The resolution of the resource containing the input image (useful for dynamic resolution) in X dimension.
+/// @param [in] inputSizeInPixelsY The resolution of the resource containing the input image (useful for dynamic resolution) in Y dimension.
+/// @param [in] outputSizeInPixelsX The display resolution which the input image gets upscaled to in X dimension.
+/// @param [in] outputSizeInPixelsY The display resolution which the input image gets upscaled to in Y dimension.
+///
+/// @ingroup FfxGPUFsr1
+FFX_STATIC void ffxFsrPopulateEasuConstants(
+ FFX_PARAMETER_INOUT FfxUInt32x4 con0,
+ FFX_PARAMETER_INOUT FfxUInt32x4 con1,
+ FFX_PARAMETER_INOUT FfxUInt32x4 con2,
+ FFX_PARAMETER_INOUT FfxUInt32x4 con3,
+ FFX_PARAMETER_IN FfxFloat32 inputViewportInPixelsX,
+ FFX_PARAMETER_IN FfxFloat32 inputViewportInPixelsY,
+ FFX_PARAMETER_IN FfxFloat32 inputSizeInPixelsX,
+ FFX_PARAMETER_IN FfxFloat32 inputSizeInPixelsY,
+ FFX_PARAMETER_IN FfxFloat32 outputSizeInPixelsX,
+ FFX_PARAMETER_IN FfxFloat32 outputSizeInPixelsY)
+{
+ // Output integer position to a pixel position in viewport.
+ con0[0] = ffxAsUInt32(inputViewportInPixelsX * ffxReciprocal(outputSizeInPixelsX));
+ con0[1] = ffxAsUInt32(inputViewportInPixelsY * ffxReciprocal(outputSizeInPixelsY));
+ con0[2] = ffxAsUInt32(FfxFloat32(0.5) * inputViewportInPixelsX * ffxReciprocal(outputSizeInPixelsX) - FfxFloat32(0.5));
+ con0[3] = ffxAsUInt32(FfxFloat32(0.5) * inputViewportInPixelsY * ffxReciprocal(outputSizeInPixelsY) - FfxFloat32(0.5));
+
+ // Viewport pixel position to normalized image space.
+ // This is used to get upper-left of 'F' tap.
+ con1[0] = ffxAsUInt32(ffxReciprocal(inputSizeInPixelsX));
+ con1[1] = ffxAsUInt32(ffxReciprocal(inputSizeInPixelsY));
+
+ // Centers of gather4, first offset from upper-left of 'F'.
+ // +---+---+
+ // | | |
+ // +--(0)--+
+ // | b | c |
+ // +---F---+---+---+
+ // | e | f | g | h |
+ // +--(1)--+--(2)--+
+ // | i | j | k | l |
+ // +---+---+---+---+
+ // | n | o |
+ // +--(3)--+
+ // | | |
+ // +---+---+
+ con1[2] = ffxAsUInt32(FfxFloat32(1.0) * ffxReciprocal(inputSizeInPixelsX));
+ con1[3] = ffxAsUInt32(FfxFloat32(-1.0) * ffxReciprocal(inputSizeInPixelsY));
+
+ // These are from (0) instead of 'F'.
+ con2[0] = ffxAsUInt32(FfxFloat32(-1.0) * ffxReciprocal(inputSizeInPixelsX));
+ con2[1] = ffxAsUInt32(FfxFloat32(2.0) * ffxReciprocal(inputSizeInPixelsY));
+ con2[2] = ffxAsUInt32(FfxFloat32(1.0) * ffxReciprocal(inputSizeInPixelsX));
+ con2[3] = ffxAsUInt32(FfxFloat32(2.0) * ffxReciprocal(inputSizeInPixelsY));
+ con3[0] = ffxAsUInt32(FfxFloat32(0.0) * ffxReciprocal(inputSizeInPixelsX));
+ con3[1] = ffxAsUInt32(FfxFloat32(4.0) * ffxReciprocal(inputSizeInPixelsY));
+ con3[2] = con3[3] = 0;
+}
+
+/// Setup required constant values for EASU (works on CPU or GPU).
+///
+/// @param [out] con0
+/// @param [out] con1
+/// @param [out] con2
+/// @param [out] con3
+/// @param [in] inputViewportInPixelsX The resolution of the input in the X dimension.
+/// @param [in] inputViewportInPixelsY The resolution of the input in the Y dimension.
+/// @param [in] inputSizeInPixelsX The input size in pixels in the X dimension.
+/// @param [in] inputSizeInPixelsY The input size in pixels in the Y dimension.
+/// @param [in] outputSizeInPixelsX The output size in pixels in the X dimension.
+/// @param [in] outputSizeInPixelsY The output size in pixels in the Y dimension.
+/// @param [in] inputOffsetInPixelsX The input image offset in the X dimension into the resource containing it (useful for dynamic resolution).
+/// @param [in] inputOffsetInPixelsY The input image offset in the Y dimension into the resource containing it (useful for dynamic resolution).
+///
+/// @ingroup FfxGPUFsr1
+FFX_STATIC void ffxFsrPopulateEasuConstantsOffset(
+ FFX_PARAMETER_INOUT FfxUInt32x4 con0,
+ FFX_PARAMETER_INOUT FfxUInt32x4 con1,
+ FFX_PARAMETER_INOUT FfxUInt32x4 con2,
+ FFX_PARAMETER_INOUT FfxUInt32x4 con3,
+ FFX_PARAMETER_IN FfxFloat32 inputViewportInPixelsX,
+ FFX_PARAMETER_IN FfxFloat32 inputViewportInPixelsY,
+ FFX_PARAMETER_IN FfxFloat32 inputSizeInPixelsX,
+ FFX_PARAMETER_IN FfxFloat32 inputSizeInPixelsY,
+ FFX_PARAMETER_IN FfxFloat32 outputSizeInPixelsX,
+ FFX_PARAMETER_IN FfxFloat32 outputSizeInPixelsY,
+ FFX_PARAMETER_IN FfxFloat32 inputOffsetInPixelsX,
+ FFX_PARAMETER_IN FfxFloat32 inputOffsetInPixelsY)
+{
+ ffxFsrPopulateEasuConstants(
+ con0,
+ con1,
+ con2,
+ con3,
+ inputViewportInPixelsX,
+ inputViewportInPixelsY,
+ inputSizeInPixelsX,
+ inputSizeInPixelsY,
+ outputSizeInPixelsX,
+ outputSizeInPixelsY);
+
+ // override
+ con0[2] = ffxAsUInt32(FfxFloat32(0.5) * inputViewportInPixelsX * ffxReciprocal(outputSizeInPixelsX) - FfxFloat32(0.5) + inputOffsetInPixelsX);
+ con0[3] = ffxAsUInt32(FfxFloat32(0.5) * inputViewportInPixelsY * ffxReciprocal(outputSizeInPixelsY) - FfxFloat32(0.5) + inputOffsetInPixelsY);
+}
+
+#if defined(FFX_GPU) && defined(FFX_FSR_EASU_FLOAT)
+// Input callback prototypes, need to be implemented by calling shader
+FfxFloat32x4 FsrEasuRF(FfxFloat32x2 p);
+FfxFloat32x4 FsrEasuGF(FfxFloat32x2 p);
+FfxFloat32x4 FsrEasuBF(FfxFloat32x2 p);
+
+// Filtering for a given tap for the scalar.
+void fsrEasuTapFloat(
+ FFX_PARAMETER_INOUT FfxFloat32x3 accumulatedColor, // Accumulated color, with negative lobe.
+ FFX_PARAMETER_INOUT FfxFloat32 accumulatedWeight, // Accumulated weight.
+ FFX_PARAMETER_IN FfxFloat32x2 pixelOffset, // Pixel offset from resolve position to tap.
+ FFX_PARAMETER_IN FfxFloat32x2 gradientDirection, // Gradient direction.
+ FFX_PARAMETER_IN FfxFloat32x2 length, // Length.
+ FFX_PARAMETER_IN FfxFloat32 negativeLobeStrength, // Negative lobe strength.
+ FFX_PARAMETER_IN FfxFloat32 clippingPoint, // Clipping point.
+ FFX_PARAMETER_IN FfxFloat32x3 color) // Tap color.
+{
+ // Rotate offset by direction.
+ FfxFloat32x2 rotatedOffset;
+ rotatedOffset.x = (pixelOffset.x * (gradientDirection.x)) + (pixelOffset.y * gradientDirection.y);
+ rotatedOffset.y = (pixelOffset.x * (-gradientDirection.y)) + (pixelOffset.y * gradientDirection.x);
+
+ // Anisotropy.
+ rotatedOffset *= length;
+
+ // Compute distance^2.
+ FfxFloat32 distanceSquared = rotatedOffset.x * rotatedOffset.x + rotatedOffset.y * rotatedOffset.y;
+
+ // Limit to the window as at corner, 2 taps can easily be outside.
+ distanceSquared = ffxMin(distanceSquared, clippingPoint);
+
+ // Approximation of lancos2 without sin() or rcp(), or sqrt() to get x.
+ // (25/16 * (2/5 * x^2 - 1)^2 - (25/16 - 1)) * (1/4 * x^2 - 1)^2
+ // |_______________________________________| |_______________|
+ // base window
+ // The general form of the 'base' is,
+ // (a*(b*x^2-1)^2-(a-1))
+ // Where 'a=1/(2*b-b^2)' and 'b' moves around the negative lobe.
+ FfxFloat32 weightB = FfxFloat32(2.0 / 5.0) * distanceSquared + FfxFloat32(-1.0);
+ FfxFloat32 weightA = negativeLobeStrength * distanceSquared + FfxFloat32(-1.0);
+ weightB *= weightB;
+ weightA *= weightA;
+ weightB = FfxFloat32(25.0 / 16.0) * weightB + FfxFloat32(-(25.0 / 16.0 - 1.0));
+ FfxFloat32 weight = weightB * weightA;
+
+ // Do weighted average.
+ accumulatedColor += color * weight;
+ accumulatedWeight += weight;
+}
+
+// Accumulate direction and length.
+void fsrEasuSetFloat(
+ FFX_PARAMETER_INOUT FfxFloat32x2 direction,
+ FFX_PARAMETER_INOUT FfxFloat32 length,
+ FFX_PARAMETER_IN FfxFloat32x2 pp,
+ FFX_PARAMETER_IN FfxBoolean biS,
+ FFX_PARAMETER_IN FfxBoolean biT,
+ FFX_PARAMETER_IN FfxBoolean biU,
+ FFX_PARAMETER_IN FfxBoolean biV,
+ FFX_PARAMETER_IN FfxFloat32 lA,
+ FFX_PARAMETER_IN FfxFloat32 lB,
+ FFX_PARAMETER_IN FfxFloat32 lC,
+ FFX_PARAMETER_IN FfxFloat32 lD,
+ FFX_PARAMETER_IN FfxFloat32 lE)
+{
+ // Compute bilinear weight, branches factor out as predicates are compiler time immediates.
+ // s t
+ // u v
+ FfxFloat32 weight = FfxFloat32(0.0);
+ if (biS)
+ weight = (FfxFloat32(1.0) - pp.x) * (FfxFloat32(1.0) - pp.y);
+ if (biT)
+ weight = pp.x * (FfxFloat32(1.0) - pp.y);
+ if (biU)
+ weight = (FfxFloat32(1.0) - pp.x) * pp.y;
+ if (biV)
+ weight = pp.x * pp.y;
+
+ // Direction is the '+' diff.
+ // a
+ // b c d
+ // e
+ // Then takes magnitude from abs average of both sides of 'c'.
+ // Length converts gradient reversal to 0, smoothly to non-reversal at 1, shaped, then adding horz and vert terms.
+ FfxFloat32 dc = lD - lC;
+ FfxFloat32 cb = lC - lB;
+ FfxFloat32 lengthX = max(abs(dc), abs(cb));
+ lengthX = ffxApproximateReciprocal(lengthX);
+ FfxFloat32 directionX = lD - lB;
+ direction.x += directionX * weight;
+ lengthX = ffxSaturate(abs(directionX) * lengthX);
+ lengthX *= lengthX;
+ length += lengthX * weight;
+
+ // Repeat for the y axis.
+ FfxFloat32 ec = lE - lC;
+ FfxFloat32 ca = lC - lA;
+ FfxFloat32 lengthY = max(abs(ec), abs(ca));
+ lengthY = ffxApproximateReciprocal(lengthY);
+ FfxFloat32 directionY = lE - lA;
+ direction.y += directionY * weight;
+ lengthY = ffxSaturate(abs(directionY) * lengthY);
+ lengthY *= lengthY;
+ length += lengthY * weight;
+}
+
+/// Apply edge-aware spatial upsampling using 32bit floating point precision calculations.
+///
+/// @param [out] outPixel The computed color of a pixel.
+/// @param [in] integerPosition Integer pixel position within the output.
+/// @param [in] con0 The first constant value generated by <c><i>ffxFsrPopulateEasuConstants</i></c>.
+/// @param [in] con1 The second constant value generated by <c><i>ffxFsrPopulateEasuConstants</i></c>.
+/// @param [in] con2 The third constant value generated by <c><i>ffxFsrPopulateEasuConstants</i></c>.
+/// @param [in] con3 The fourth constant value generated by <c><i>ffxFsrPopulateEasuConstants</i></c>.
+///
+/// @ingroup FSR
+void ffxFsrEasuFloat(
+ FFX_PARAMETER_OUT FfxFloat32x3 pix,
+ FFX_PARAMETER_IN FfxUInt32x2 ip,
+ FFX_PARAMETER_IN FfxUInt32x4 con0,
+ FFX_PARAMETER_IN FfxUInt32x4 con1,
+ FFX_PARAMETER_IN FfxUInt32x4 con2,
+ FFX_PARAMETER_IN FfxUInt32x4 con3)
+{
+ // Get position of 'f'.
+ FfxFloat32x2 pp = FfxFloat32x2(ip) * ffxAsFloat(con0.xy) + ffxAsFloat(con0.zw);
+ FfxFloat32x2 fp = floor(pp);
+ pp -= fp;
+
+ // 12-tap kernel.
+ // b c
+ // e f g h
+ // i j k l
+ // n o
+ // Gather 4 ordering.
+ // a b
+ // r g
+ // For packed FP16, need either {rg} or {ab} so using the following setup for gather in all versions,
+ // a b <- unused (z)
+ // r g
+ // a b a b
+ // r g r g
+ // a b
+ // r g <- unused (z)
+ // Allowing dead-code removal to remove the 'z's.
+ FfxFloat32x2 p0 = fp * ffxAsFloat(con1.xy) + ffxAsFloat(con1.zw);
+
+ // These are from p0 to avoid pulling two constants on pre-Navi hardware.
+ FfxFloat32x2 p1 = p0 + ffxAsFloat(con2.xy);
+ FfxFloat32x2 p2 = p0 + ffxAsFloat(con2.zw);
+ FfxFloat32x2 p3 = p0 + ffxAsFloat(con3.xy);
+ FfxFloat32x4 bczzR = FsrEasuRF(p0);
+ FfxFloat32x4 bczzG = FsrEasuGF(p0);
+ FfxFloat32x4 bczzB = FsrEasuBF(p0);
+ FfxFloat32x4 ijfeR = FsrEasuRF(p1);
+ FfxFloat32x4 ijfeG = FsrEasuGF(p1);
+ FfxFloat32x4 ijfeB = FsrEasuBF(p1);
+ FfxFloat32x4 klhgR = FsrEasuRF(p2);
+ FfxFloat32x4 klhgG = FsrEasuGF(p2);
+ FfxFloat32x4 klhgB = FsrEasuBF(p2);
+ FfxFloat32x4 zzonR = FsrEasuRF(p3);
+ FfxFloat32x4 zzonG = FsrEasuGF(p3);
+ FfxFloat32x4 zzonB = FsrEasuBF(p3);
+
+ // Simplest multi-channel approximate luma possible (luma times 2, in 2 FMA/MAD).
+ FfxFloat32x4 bczzL = bczzB * ffxBroadcast4(0.5) + (bczzR * ffxBroadcast4(0.5) + bczzG);
+ FfxFloat32x4 ijfeL = ijfeB * ffxBroadcast4(0.5) + (ijfeR * ffxBroadcast4(0.5) + ijfeG);
+ FfxFloat32x4 klhgL = klhgB * ffxBroadcast4(0.5) + (klhgR * ffxBroadcast4(0.5) + klhgG);
+ FfxFloat32x4 zzonL = zzonB * ffxBroadcast4(0.5) + (zzonR * ffxBroadcast4(0.5) + zzonG);
+
+ // Rename.
+ FfxFloat32 bL = bczzL.x;
+ FfxFloat32 cL = bczzL.y;
+ FfxFloat32 iL = ijfeL.x;
+ FfxFloat32 jL = ijfeL.y;
+ FfxFloat32 fL = ijfeL.z;
+ FfxFloat32 eL = ijfeL.w;
+ FfxFloat32 kL = klhgL.x;
+ FfxFloat32 lL = klhgL.y;
+ FfxFloat32 hL = klhgL.z;
+ FfxFloat32 gL = klhgL.w;
+ FfxFloat32 oL = zzonL.z;
+ FfxFloat32 nL = zzonL.w;
+
+ // Accumulate for bilinear interpolation.
+ FfxFloat32x2 dir = ffxBroadcast2(0.0);
+ FfxFloat32 len = FfxFloat32(0.0);
+ fsrEasuSetFloat(dir, len, pp, FFX_TRUE, FFX_FALSE, FFX_FALSE, FFX_FALSE, bL, eL, fL, gL, jL);
+ fsrEasuSetFloat(dir, len, pp, FFX_FALSE, FFX_TRUE, FFX_FALSE, FFX_FALSE, cL, fL, gL, hL, kL);
+ fsrEasuSetFloat(dir, len, pp, FFX_FALSE, FFX_FALSE, FFX_TRUE, FFX_FALSE, fL, iL, jL, kL, nL);
+ fsrEasuSetFloat(dir, len, pp, FFX_FALSE, FFX_FALSE, FFX_FALSE, FFX_TRUE, gL, jL, kL, lL, oL);
+
+ // Normalize with approximation, and cleanup close to zero.
+ FfxFloat32x2 dir2 = dir * dir;
+ FfxFloat32 dirR = dir2.x + dir2.y;
+ FfxBoolean zro = dirR < FfxFloat32(1.0 / 32768.0);
+ dirR = ffxApproximateReciprocalSquareRoot(dirR);
+ dirR = zro ? FfxFloat32(1.0) : dirR;
+ dir.x = zro ? FfxFloat32(1.0) : dir.x;
+ dir *= ffxBroadcast2(dirR);
+
+ // Transform from {0 to 2} to {0 to 1} range, and shape with square.
+ len = len * FfxFloat32(0.5);
+ len *= len;
+
+ // Stretch kernel {1.0 vert|horz, to sqrt(2.0) on diagonal}.
+ FfxFloat32 stretch = (dir.x * dir.x + dir.y * dir.y) * ffxApproximateReciprocal(max(abs(dir.x), abs(dir.y)));
+
+ // Anisotropic length after rotation,
+ // x := 1.0 lerp to 'stretch' on edges
+ // y := 1.0 lerp to 2x on edges
+ FfxFloat32x2 len2 = FfxFloat32x2(FfxFloat32(1.0) + (stretch - FfxFloat32(1.0)) * len, FfxFloat32(1.0) + FfxFloat32(-0.5) * len);
+
+ // Based on the amount of 'edge',
+ // the window shifts from +/-{sqrt(2.0) to slightly beyond 2.0}.
+ FfxFloat32 lob = FfxFloat32(0.5) + FfxFloat32((1.0 / 4.0 - 0.04) - 0.5) * len;
+
+ // Set distance^2 clipping point to the end of the adjustable window.
+ FfxFloat32 clp = ffxApproximateReciprocal(lob);
+
+ // Accumulation mixed with min/max of 4 nearest.
+ // b c
+ // e f g h
+ // i j k l
+ // n o
+ FfxFloat32x3 min4 =
+ ffxMin(ffxMin3(FfxFloat32x3(ijfeR.z, ijfeG.z, ijfeB.z), FfxFloat32x3(klhgR.w, klhgG.w, klhgB.w), FfxFloat32x3(ijfeR.y, ijfeG.y, ijfeB.y)),
+ FfxFloat32x3(klhgR.x, klhgG.x, klhgB.x));
+ FfxFloat32x3 max4 =
+ max(ffxMax3(FfxFloat32x3(ijfeR.z, ijfeG.z, ijfeB.z), FfxFloat32x3(klhgR.w, klhgG.w, klhgB.w), FfxFloat32x3(ijfeR.y, ijfeG.y, ijfeB.y)), FfxFloat32x3(klhgR.x, klhgG.x, klhgB.x));
+
+ // Accumulation.
+ FfxFloat32x3 aC = ffxBroadcast3(0.0);
+ FfxFloat32 aW = FfxFloat32(0.0);
+ fsrEasuTapFloat(aC, aW, FfxFloat32x2(0.0, -1.0) - pp, dir, len2, lob, clp, FfxFloat32x3(bczzR.x, bczzG.x, bczzB.x)); // b
+ fsrEasuTapFloat(aC, aW, FfxFloat32x2(1.0, -1.0) - pp, dir, len2, lob, clp, FfxFloat32x3(bczzR.y, bczzG.y, bczzB.y)); // c
+ fsrEasuTapFloat(aC, aW, FfxFloat32x2(-1.0, 1.0) - pp, dir, len2, lob, clp, FfxFloat32x3(ijfeR.x, ijfeG.x, ijfeB.x)); // i
+ fsrEasuTapFloat(aC, aW, FfxFloat32x2(0.0, 1.0) - pp, dir, len2, lob, clp, FfxFloat32x3(ijfeR.y, ijfeG.y, ijfeB.y)); // j
+ fsrEasuTapFloat(aC, aW, FfxFloat32x2(0.0, 0.0) - pp, dir, len2, lob, clp, FfxFloat32x3(ijfeR.z, ijfeG.z, ijfeB.z)); // f
+ fsrEasuTapFloat(aC, aW, FfxFloat32x2(-1.0, 0.0) - pp, dir, len2, lob, clp, FfxFloat32x3(ijfeR.w, ijfeG.w, ijfeB.w)); // e
+ fsrEasuTapFloat(aC, aW, FfxFloat32x2(1.0, 1.0) - pp, dir, len2, lob, clp, FfxFloat32x3(klhgR.x, klhgG.x, klhgB.x)); // k
+ fsrEasuTapFloat(aC, aW, FfxFloat32x2(2.0, 1.0) - pp, dir, len2, lob, clp, FfxFloat32x3(klhgR.y, klhgG.y, klhgB.y)); // l
+ fsrEasuTapFloat(aC, aW, FfxFloat32x2(2.0, 0.0) - pp, dir, len2, lob, clp, FfxFloat32x3(klhgR.z, klhgG.z, klhgB.z)); // h
+ fsrEasuTapFloat(aC, aW, FfxFloat32x2(1.0, 0.0) - pp, dir, len2, lob, clp, FfxFloat32x3(klhgR.w, klhgG.w, klhgB.w)); // g
+ fsrEasuTapFloat(aC, aW, FfxFloat32x2(1.0, 2.0) - pp, dir, len2, lob, clp, FfxFloat32x3(zzonR.z, zzonG.z, zzonB.z)); // o
+ fsrEasuTapFloat(aC, aW, FfxFloat32x2(0.0, 2.0) - pp, dir, len2, lob, clp, FfxFloat32x3(zzonR.w, zzonG.w, zzonB.w)); // n
+
+ // Normalize and dering.
+ pix = ffxMin(max4, max(min4, aC * ffxBroadcast3(ffxReciprocal(aW))));
+}
+#endif // #if defined(FFX_GPU) && defined(FFX_FSR_EASU_FLOAT)
+
+#if defined(FFX_GPU) && FFX_HALF == 1 && defined(FFX_FSR_EASU_HALF)
+// Input callback prototypes, need to be implemented by calling shader
+FfxFloat16x4 FsrEasuRH(FfxFloat32x2 p);
+FfxFloat16x4 FsrEasuGH(FfxFloat32x2 p);
+FfxFloat16x4 FsrEasuBH(FfxFloat32x2 p);
+
+// This runs 2 taps in parallel.
+void FsrEasuTapH(
+ FFX_PARAMETER_INOUT FfxFloat16x2 aCR,
+ FFX_PARAMETER_INOUT FfxFloat16x2 aCG,
+ FFX_PARAMETER_INOUT FfxFloat16x2 aCB,
+ FFX_PARAMETER_INOUT FfxFloat16x2 aW,
+ FFX_PARAMETER_IN FfxFloat16x2 offX,
+ FFX_PARAMETER_IN FfxFloat16x2 offY,
+ FFX_PARAMETER_IN FfxFloat16x2 dir,
+ FFX_PARAMETER_IN FfxFloat16x2 len,
+ FFX_PARAMETER_IN FfxFloat16 lob,
+ FFX_PARAMETER_IN FfxFloat16 clp,
+ FFX_PARAMETER_IN FfxFloat16x2 cR,
+ FFX_PARAMETER_IN FfxFloat16x2 cG,
+ FFX_PARAMETER_IN FfxFloat16x2 cB)
+{
+ FfxFloat16x2 vX, vY;
+ vX = offX * dir.xx + offY * dir.yy;
+ vY = offX * (-dir.yy) + offY * dir.xx;
+ vX *= len.x;
+ vY *= len.y;
+ FfxFloat16x2 d2 = vX * vX + vY * vY;
+ d2 = min(d2, FFX_BROADCAST_FLOAT16X2(clp));
+ FfxFloat16x2 wB = FFX_BROADCAST_FLOAT16X2(2.0 / 5.0) * d2 + FFX_BROADCAST_FLOAT16X2(-1.0);
+ FfxFloat16x2 wA = FFX_BROADCAST_FLOAT16X2(lob) * d2 + FFX_BROADCAST_FLOAT16X2(-1.0);
+ wB *= wB;
+ wA *= wA;
+ wB = FFX_BROADCAST_FLOAT16X2(25.0 / 16.0) * wB + FFX_BROADCAST_FLOAT16X2(-(25.0 / 16.0 - 1.0));
+ FfxFloat16x2 w = wB * wA;
+ aCR += cR * w;
+ aCG += cG * w;
+ aCB += cB * w;
+ aW += w;
+}
+
+// This runs 2 taps in parallel.
+void FsrEasuSetH(
+ FFX_PARAMETER_INOUT FfxFloat16x2 dirPX,
+ FFX_PARAMETER_INOUT FfxFloat16x2 dirPY,
+ FFX_PARAMETER_INOUT FfxFloat16x2 lenP,
+ FFX_PARAMETER_IN FfxFloat16x2 pp,
+ FFX_PARAMETER_IN FfxBoolean biST,
+ FFX_PARAMETER_IN FfxBoolean biUV,
+ FFX_PARAMETER_IN FfxFloat16x2 lA,
+ FFX_PARAMETER_IN FfxFloat16x2 lB,
+ FFX_PARAMETER_IN FfxFloat16x2 lC,
+ FFX_PARAMETER_IN FfxFloat16x2 lD,
+ FFX_PARAMETER_IN FfxFloat16x2 lE)
+{
+ FfxFloat16x2 w = FFX_BROADCAST_FLOAT16X2(0.0);
+
+ if (biST)
+ w = (FfxFloat16x2(1.0, 0.0) + FfxFloat16x2(-pp.x, pp.x)) * FFX_BROADCAST_FLOAT16X2(FFX_BROADCAST_FLOAT16(1.0) - pp.y);
+
+ if (biUV)
+ w = (FfxFloat16x2(1.0, 0.0) + FfxFloat16x2(-pp.x, pp.x)) * FFX_BROADCAST_FLOAT16X2(pp.y);
+
+ // ABS is not free in the packed FP16 path.
+ FfxFloat16x2 dc = lD - lC;
+ FfxFloat16x2 cb = lC - lB;
+ FfxFloat16x2 lenX = max(abs(dc), abs(cb));
+ lenX = ffxReciprocalHalf(lenX);
+
+ FfxFloat16x2 dirX = lD - lB;
+ dirPX += dirX * w;
+ lenX = ffxSaturate(abs(dirX) * lenX);
+ lenX *= lenX;
+ lenP += lenX * w;
+ FfxFloat16x2 ec = lE - lC;
+ FfxFloat16x2 ca = lC - lA;
+ FfxFloat16x2 lenY = max(abs(ec), abs(ca));
+ lenY = ffxReciprocalHalf(lenY);
+ FfxFloat16x2 dirY = lE - lA;
+ dirPY += dirY * w;
+ lenY = ffxSaturate(abs(dirY) * lenY);
+ lenY *= lenY;
+ lenP += lenY * w;
+}
+
+void FsrEasuH(
+ FFX_PARAMETER_OUT FfxFloat16x3 pix,
+ FFX_PARAMETER_IN FfxUInt32x2 ip,
+ FFX_PARAMETER_IN FfxUInt32x4 con0,
+ FFX_PARAMETER_IN FfxUInt32x4 con1,
+ FFX_PARAMETER_IN FfxUInt32x4 con2,
+ FFX_PARAMETER_IN FfxUInt32x4 con3)
+{
+ FfxFloat32x2 pp = FfxFloat32x2(ip) * ffxAsFloat(con0.xy) + ffxAsFloat(con0.zw);
+ FfxFloat32x2 fp = floor(pp);
+ pp -= fp;
+ FfxFloat16x2 ppp = FfxFloat16x2(pp);
+
+ FfxFloat32x2 p0 = fp * ffxAsFloat(con1.xy) + ffxAsFloat(con1.zw);
+ FfxFloat32x2 p1 = p0 + ffxAsFloat(con2.xy);
+ FfxFloat32x2 p2 = p0 + ffxAsFloat(con2.zw);
+ FfxFloat32x2 p3 = p0 + ffxAsFloat(con3.xy);
+ FfxFloat16x4 bczzR = FsrEasuRH(p0);
+ FfxFloat16x4 bczzG = FsrEasuGH(p0);
+ FfxFloat16x4 bczzB = FsrEasuBH(p0);
+ FfxFloat16x4 ijfeR = FsrEasuRH(p1);
+ FfxFloat16x4 ijfeG = FsrEasuGH(p1);
+ FfxFloat16x4 ijfeB = FsrEasuBH(p1);
+ FfxFloat16x4 klhgR = FsrEasuRH(p2);
+ FfxFloat16x4 klhgG = FsrEasuGH(p2);
+ FfxFloat16x4 klhgB = FsrEasuBH(p2);
+ FfxFloat16x4 zzonR = FsrEasuRH(p3);
+ FfxFloat16x4 zzonG = FsrEasuGH(p3);
+ FfxFloat16x4 zzonB = FsrEasuBH(p3);
+
+ FfxFloat16x4 bczzL = bczzB * FFX_BROADCAST_FLOAT16X4(0.5) + (bczzR * FFX_BROADCAST_FLOAT16X4(0.5) + bczzG);
+ FfxFloat16x4 ijfeL = ijfeB * FFX_BROADCAST_FLOAT16X4(0.5) + (ijfeR * FFX_BROADCAST_FLOAT16X4(0.5) + ijfeG);
+ FfxFloat16x4 klhgL = klhgB * FFX_BROADCAST_FLOAT16X4(0.5) + (klhgR * FFX_BROADCAST_FLOAT16X4(0.5) + klhgG);
+ FfxFloat16x4 zzonL = zzonB * FFX_BROADCAST_FLOAT16X4(0.5) + (zzonR * FFX_BROADCAST_FLOAT16X4(0.5) + zzonG);
+ FfxFloat16 bL = bczzL.x;
+ FfxFloat16 cL = bczzL.y;
+ FfxFloat16 iL = ijfeL.x;
+ FfxFloat16 jL = ijfeL.y;
+ FfxFloat16 fL = ijfeL.z;
+ FfxFloat16 eL = ijfeL.w;
+ FfxFloat16 kL = klhgL.x;
+ FfxFloat16 lL = klhgL.y;
+ FfxFloat16 hL = klhgL.z;
+ FfxFloat16 gL = klhgL.w;
+ FfxFloat16 oL = zzonL.z;
+ FfxFloat16 nL = zzonL.w;
+
+ // This part is different, accumulating 2 taps in parallel.
+ FfxFloat16x2 dirPX = FFX_BROADCAST_FLOAT16X2(0.0);
+ FfxFloat16x2 dirPY = FFX_BROADCAST_FLOAT16X2(0.0);
+ FfxFloat16x2 lenP = FFX_BROADCAST_FLOAT16X2(0.0);
+ FsrEasuSetH(dirPX,
+ dirPY,
+ lenP,
+ ppp,
+ FfxBoolean(true),
+ FfxBoolean(false),
+ FfxFloat16x2(bL, cL),
+ FfxFloat16x2(eL, fL),
+ FfxFloat16x2(fL, gL),
+ FfxFloat16x2(gL, hL),
+ FfxFloat16x2(jL, kL));
+ FsrEasuSetH(dirPX,
+ dirPY,
+ lenP,
+ ppp,
+ FfxBoolean(false),
+ FfxBoolean(true),
+ FfxFloat16x2(fL, gL),
+ FfxFloat16x2(iL, jL),
+ FfxFloat16x2(jL, kL),
+ FfxFloat16x2(kL, lL),
+ FfxFloat16x2(nL, oL));
+ FfxFloat16x2 dir = FfxFloat16x2(dirPX.r + dirPX.g, dirPY.r + dirPY.g);
+ FfxFloat16 len = lenP.r + lenP.g;
+
+ FfxFloat16x2 dir2 = dir * dir;
+ FfxFloat16 dirR = dir2.x + dir2.y;
+ FfxUInt32 zro = FfxUInt32(dirR < FFX_BROADCAST_FLOAT16(1.0 / 32768.0));
+ dirR = ffxApproximateReciprocalSquareRootHalf(dirR);
+ dirR = (zro > 0) ? FFX_BROADCAST_FLOAT16(1.0) : dirR;
+ dir.x = (zro > 0) ? FFX_BROADCAST_FLOAT16(1.0) : dir.x;
+ dir *= FFX_BROADCAST_FLOAT16X2(dirR);
+ len = len * FFX_BROADCAST_FLOAT16(0.5);
+ len *= len;
+ FfxFloat16 stretch = (dir.x * dir.x + dir.y * dir.y) * ffxApproximateReciprocalHalf(max(abs(dir.x), abs(dir.y)));
+ FfxFloat16x2 len2 =
+ FfxFloat16x2(FFX_BROADCAST_FLOAT16(1.0) + (stretch - FFX_BROADCAST_FLOAT16(1.0)) * len, FFX_BROADCAST_FLOAT16(1.0) + FFX_BROADCAST_FLOAT16(-0.5) * len);
+ FfxFloat16 lob = FFX_BROADCAST_FLOAT16(0.5) + FFX_BROADCAST_FLOAT16((1.0 / 4.0 - 0.04) - 0.5) * len;
+ FfxFloat16 clp = ffxApproximateReciprocalHalf(lob);
+
+ // FP16 is different, using packed trick to do min and max in same operation.
+ FfxFloat16x2 bothR =
+ max(max(FfxFloat16x2(-ijfeR.z, ijfeR.z), FfxFloat16x2(-klhgR.w, klhgR.w)), max(FfxFloat16x2(-ijfeR.y, ijfeR.y), FfxFloat16x2(-klhgR.x, klhgR.x)));
+ FfxFloat16x2 bothG =
+ max(max(FfxFloat16x2(-ijfeG.z, ijfeG.z), FfxFloat16x2(-klhgG.w, klhgG.w)), max(FfxFloat16x2(-ijfeG.y, ijfeG.y), FfxFloat16x2(-klhgG.x, klhgG.x)));
+ FfxFloat16x2 bothB =
+ max(max(FfxFloat16x2(-ijfeB.z, ijfeB.z), FfxFloat16x2(-klhgB.w, klhgB.w)), max(FfxFloat16x2(-ijfeB.y, ijfeB.y), FfxFloat16x2(-klhgB.x, klhgB.x)));
+
+ // This part is different for FP16, working pairs of taps at a time.
+ FfxFloat16x2 pR = FFX_BROADCAST_FLOAT16X2(0.0);
+ FfxFloat16x2 pG = FFX_BROADCAST_FLOAT16X2(0.0);
+ FfxFloat16x2 pB = FFX_BROADCAST_FLOAT16X2(0.0);
+ FfxFloat16x2 pW = FFX_BROADCAST_FLOAT16X2(0.0);
+ FsrEasuTapH(pR, pG, pB, pW, FfxFloat16x2(0.0, 1.0) - ppp.xx, FfxFloat16x2(-1.0, -1.0) - ppp.yy, dir, len2, lob, clp, bczzR.xy, bczzG.xy, bczzB.xy);
+ FsrEasuTapH(pR, pG, pB, pW, FfxFloat16x2(-1.0, 0.0) - ppp.xx, FfxFloat16x2(1.0, 1.0) - ppp.yy, dir, len2, lob, clp, ijfeR.xy, ijfeG.xy, ijfeB.xy);
+ FsrEasuTapH(pR, pG, pB, pW, FfxFloat16x2(0.0, -1.0) - ppp.xx, FfxFloat16x2(0.0, 0.0) - ppp.yy, dir, len2, lob, clp, ijfeR.zw, ijfeG.zw, ijfeB.zw);
+ FsrEasuTapH(pR, pG, pB, pW, FfxFloat16x2(1.0, 2.0) - ppp.xx, FfxFloat16x2(1.0, 1.0) - ppp.yy, dir, len2, lob, clp, klhgR.xy, klhgG.xy, klhgB.xy);
+ FsrEasuTapH(pR, pG, pB, pW, FfxFloat16x2(2.0, 1.0) - ppp.xx, FfxFloat16x2(0.0, 0.0) - ppp.yy, dir, len2, lob, clp, klhgR.zw, klhgG.zw, klhgB.zw);
+ FsrEasuTapH(pR, pG, pB, pW, FfxFloat16x2(1.0, 0.0) - ppp.xx, FfxFloat16x2(2.0, 2.0) - ppp.yy, dir, len2, lob, clp, zzonR.zw, zzonG.zw, zzonB.zw);
+ FfxFloat16x3 aC = FfxFloat16x3(pR.x + pR.y, pG.x + pG.y, pB.x + pB.y);
+ FfxFloat16 aW = pW.x + pW.y;
+
+ // Slightly different for FP16 version due to combined min and max.
+ pix = min(FfxFloat16x3(bothR.y, bothG.y, bothB.y), max(-FfxFloat16x3(bothR.x, bothG.x, bothB.x), aC * FFX_BROADCAST_FLOAT16X3(ffxReciprocalHalf(aW))));
+}
+#endif // #if defined(FFX_GPU) && defined(FFX_HALF) && defined(FFX_FSR_EASU_HALF)
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//_____________________________________________________________/\_______________________________________________________________
+//==============================================================================================================================
+//
+// FSR - [RCAS] ROBUST CONTRAST ADAPTIVE SHARPENING
+//
+//------------------------------------------------------------------------------------------------------------------------------
+// CAS uses a simplified mechanism to convert local contrast into a variable amount of sharpness.
+// RCAS uses a more exact mechanism, solving for the maximum local sharpness possible before clipping.
+// RCAS also has a built in process to limit sharpening of what it detects as possible noise.
+// RCAS sharper does not support scaling, as it should be applied after EASU scaling.
+// Pass EASU output straight into RCAS, no color conversions necessary.
+//------------------------------------------------------------------------------------------------------------------------------
+// RCAS is based on the following logic.
+// RCAS uses a 5 tap filter in a cross pattern (same as CAS),
+// w n
+// w 1 w for taps w m e
+// w s
+// Where 'w' is the negative lobe weight.
+// output = (w*(n+e+w+s)+m)/(4*w+1)
+// RCAS solves for 'w' by seeing where the signal might clip out of the {0 to 1} input range,
+// 0 == (w*(n+e+w+s)+m)/(4*w+1) -> w = -m/(n+e+w+s)
+// 1 == (w*(n+e+w+s)+m)/(4*w+1) -> w = (1-m)/(n+e+w+s-4*1)
+// Then chooses the 'w' which results in no clipping, limits 'w', and multiplies by the 'sharp' amount.
+// This solution above has issues with MSAA input as the steps along the gradient cause edge detection issues.
+// So RCAS uses 4x the maximum and 4x the minimum (depending on equation)in place of the individual taps.
+// As well as switching from 'm' to either the minimum or maximum (depending on side), to help in energy conservation.
+// This stabilizes RCAS.
+// RCAS does a simple highpass which is normalized against the local contrast then shaped,
+// 0.25
+// 0.25 -1 0.25
+// 0.25
+// This is used as a noise detection filter, to reduce the effect of RCAS on grain, and focus on real edges.
+//
+// GLSL example for the required callbacks :
+//
+// FfxFloat16x4 FsrRcasLoadH(FfxInt16x2 p){return FfxFloat16x4(imageLoad(imgSrc,FfxInt32x2(p)));}
+// void FsrRcasInputH(inout FfxFloat16 r,inout FfxFloat16 g,inout FfxFloat16 b)
+// {
+// //do any simple input color conversions here or leave empty if none needed
+// }
+//
+// FsrRcasCon need to be called from the CPU or GPU to set up constants.
+// Including a GPU example here, the 'con' value would be stored out to a constant buffer.
+//
+// FfxUInt32x4 con;
+// FsrRcasCon(con,
+// 0.0); // The scale is {0.0 := maximum sharpness, to N>0, where N is the number of stops (halving) of the reduction of sharpness}.
+// ---------------
+// RCAS sharpening supports a CAS-like pass-through alpha via,
+// #define FSR_RCAS_PASSTHROUGH_ALPHA 1
+// RCAS also supports a define to enable a more expensive path to avoid some sharpening of noise.
+// Would suggest it is better to apply film grain after RCAS sharpening (and after scaling) instead of using this define,
+// #define FSR_RCAS_DENOISE 1
+//==============================================================================================================================
+// This is set at the limit of providing unnatural results for sharpening.
+#define FSR_RCAS_LIMIT (0.25-(1.0/16.0))
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//_____________________________________________________________/\_______________________________________________________________
+//==============================================================================================================================
+// CONSTANT SETUP
+//==============================================================================================================================
+// Call to setup required constant values (works on CPU or GPU).
+ FFX_STATIC void FsrRcasCon(FfxUInt32x4 con,
+ // The scale is {0.0 := maximum, to N>0, where N is the number of stops (halving) of the reduction of sharpness}.
+ FfxFloat32 sharpness)
+ {
+ // Transform from stops to linear value.
+ sharpness = exp2(-sharpness);
+ FfxFloat32x2 hSharp = {sharpness, sharpness};
+ con[0] = ffxAsUInt32(sharpness);
+ con[1] = ffxPackHalf2x16(hSharp);
+ con[2] = 0;
+ con[3] = 0;
+ }
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//_____________________________________________________________/\_______________________________________________________________
+//==============================================================================================================================
+// NON-PACKED 32-BIT VERSION
+//==============================================================================================================================
+#if defined(FFX_GPU)&&defined(FSR_RCAS_F)
+ // Input callback prototypes that need to be implemented by calling shader
+ FfxFloat32x4 FsrRcasLoadF(FfxInt32x2 p);
+ void FsrRcasInputF(inout FfxFloat32 r,inout FfxFloat32 g,inout FfxFloat32 b);
+//------------------------------------------------------------------------------------------------------------------------------
+ void FsrRcasF(out FfxFloat32 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy.
+ out FfxFloat32 pixG,
+ out FfxFloat32 pixB,
+#ifdef FSR_RCAS_PASSTHROUGH_ALPHA
+ out FfxFloat32 pixA,
+#endif
+ FfxUInt32x2 ip, // Integer pixel position in output.
+ FfxUInt32x4 con)
+ { // Constant generated by RcasSetup().
+ // Algorithm uses minimal 3x3 pixel neighborhood.
+ // b
+ // d e f
+ // h
+ FfxInt32x2 sp = FfxInt32x2(ip);
+ FfxFloat32x3 b = FsrRcasLoadF(sp + FfxInt32x2(0, -1)).rgb;
+ FfxFloat32x3 d = FsrRcasLoadF(sp + FfxInt32x2(-1, 0)).rgb;
+#ifdef FSR_RCAS_PASSTHROUGH_ALPHA
+ FfxFloat32x4 ee = FsrRcasLoadF(sp);
+ FfxFloat32x3 e = ee.rgb;
+ pixA = ee.a;
+#else
+ FfxFloat32x3 e = FsrRcasLoadF(sp).rgb;
+#endif
+ FfxFloat32x3 f = FsrRcasLoadF(sp + FfxInt32x2(1, 0)).rgb;
+ FfxFloat32x3 h = FsrRcasLoadF(sp + FfxInt32x2(0, 1)).rgb;
+ // Rename (32-bit) or regroup (16-bit).
+ FfxFloat32 bR = b.r;
+ FfxFloat32 bG = b.g;
+ FfxFloat32 bB = b.b;
+ FfxFloat32 dR = d.r;
+ FfxFloat32 dG = d.g;
+ FfxFloat32 dB = d.b;
+ FfxFloat32 eR = e.r;
+ FfxFloat32 eG = e.g;
+ FfxFloat32 eB = e.b;
+ FfxFloat32 fR = f.r;
+ FfxFloat32 fG = f.g;
+ FfxFloat32 fB = f.b;
+ FfxFloat32 hR = h.r;
+ FfxFloat32 hG = h.g;
+ FfxFloat32 hB = h.b;
+ // Run optional input transform.
+ FsrRcasInputF(bR, bG, bB);
+ FsrRcasInputF(dR, dG, dB);
+ FsrRcasInputF(eR, eG, eB);
+ FsrRcasInputF(fR, fG, fB);
+ FsrRcasInputF(hR, hG, hB);
+ // Luma times 2.
+ FfxFloat32 bL = bB * FfxFloat32(0.5) + (bR * FfxFloat32(0.5) + bG);
+ FfxFloat32 dL = dB * FfxFloat32(0.5) + (dR * FfxFloat32(0.5) + dG);
+ FfxFloat32 eL = eB * FfxFloat32(0.5) + (eR * FfxFloat32(0.5) + eG);
+ FfxFloat32 fL = fB * FfxFloat32(0.5) + (fR * FfxFloat32(0.5) + fG);
+ FfxFloat32 hL = hB * FfxFloat32(0.5) + (hR * FfxFloat32(0.5) + hG);
+ // Noise detection.
+ FfxFloat32 nz = FfxFloat32(0.25) * bL + FfxFloat32(0.25) * dL + FfxFloat32(0.25) * fL + FfxFloat32(0.25) * hL - eL;
+ nz = ffxSaturate(abs(nz) * ffxApproximateReciprocalMedium(ffxMax3(ffxMax3(bL, dL, eL), fL, hL) - ffxMin3(ffxMin3(bL, dL, eL), fL, hL)));
+ nz = FfxFloat32(-0.5) * nz + FfxFloat32(1.0);
+ // Min and max of ring.
+ FfxFloat32 mn4R = ffxMin(ffxMin3(bR, dR, fR), hR);
+ FfxFloat32 mn4G = ffxMin(ffxMin3(bG, dG, fG), hG);
+ FfxFloat32 mn4B = ffxMin(ffxMin3(bB, dB, fB), hB);
+ FfxFloat32 mx4R = max(ffxMax3(bR, dR, fR), hR);
+ FfxFloat32 mx4G = max(ffxMax3(bG, dG, fG), hG);
+ FfxFloat32 mx4B = max(ffxMax3(bB, dB, fB), hB);
+ // Immediate constants for peak range.
+ FfxFloat32x2 peakC = FfxFloat32x2(1.0, -1.0 * 4.0);
+ // Limiters, these need to be high precision RCPs.
+ FfxFloat32 hitMinR = mn4R * ffxReciprocal(FfxFloat32(4.0) * mx4R);
+ FfxFloat32 hitMinG = mn4G * ffxReciprocal(FfxFloat32(4.0) * mx4G);
+ FfxFloat32 hitMinB = mn4B * ffxReciprocal(FfxFloat32(4.0) * mx4B);
+ FfxFloat32 hitMaxR = (peakC.x - mx4R) * ffxReciprocal(FfxFloat32(4.0) * mn4R + peakC.y);
+ FfxFloat32 hitMaxG = (peakC.x - mx4G) * ffxReciprocal(FfxFloat32(4.0) * mn4G + peakC.y);
+ FfxFloat32 hitMaxB = (peakC.x - mx4B) * ffxReciprocal(FfxFloat32(4.0) * mn4B + peakC.y);
+ FfxFloat32 lobeR = max(-hitMinR, hitMaxR);
+ FfxFloat32 lobeG = max(-hitMinG, hitMaxG);
+ FfxFloat32 lobeB = max(-hitMinB, hitMaxB);
+ FfxFloat32 lobe = max(FfxFloat32(-FSR_RCAS_LIMIT), ffxMin(ffxMax3(lobeR, lobeG, lobeB), FfxFloat32(0.0))) * ffxAsFloat
+ (con.x);
+ // Apply noise removal.
+#ifdef FSR_RCAS_DENOISE
+ lobe *= nz;
+#endif
+ // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes.
+ FfxFloat32 rcpL = ffxApproximateReciprocalMedium(FfxFloat32(4.0) * lobe + FfxFloat32(1.0));
+ pixR = (lobe * bR + lobe * dR + lobe * hR + lobe * fR + eR) * rcpL;
+ pixG = (lobe * bG + lobe * dG + lobe * hG + lobe * fG + eG) * rcpL;
+ pixB = (lobe * bB + lobe * dB + lobe * hB + lobe * fB + eB) * rcpL;
+ return;
+ }
+#endif
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//_____________________________________________________________/\_______________________________________________________________
+//==============================================================================================================================
+// NON-PACKED 16-BIT VERSION
+//==============================================================================================================================
+#if defined(FFX_GPU) && FFX_HALF == 1 && defined(FSR_RCAS_H)
+ // Input callback prototypes that need to be implemented by calling shader
+ FfxFloat16x4 FsrRcasLoadH(FfxInt16x2 p);
+ void FsrRcasInputH(inout FfxFloat16 r,inout FfxFloat16 g,inout FfxFloat16 b);
+//------------------------------------------------------------------------------------------------------------------------------
+ void FsrRcasH(
+ out FfxFloat16 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy.
+ out FfxFloat16 pixG,
+ out FfxFloat16 pixB,
+ #ifdef FSR_RCAS_PASSTHROUGH_ALPHA
+ out FfxFloat16 pixA,
+ #endif
+ FfxUInt32x2 ip, // Integer pixel position in output.
+ FfxUInt32x4 con){ // Constant generated by RcasSetup().
+ // Sharpening algorithm uses minimal 3x3 pixel neighborhood.
+ // b
+ // d e f
+ // h
+ FfxInt16x2 sp=FfxInt16x2(ip);
+ FfxFloat16x3 b=FsrRcasLoadH(sp+FfxInt16x2( 0,-1)).rgb;
+ FfxFloat16x3 d=FsrRcasLoadH(sp+FfxInt16x2(-1, 0)).rgb;
+ #ifdef FSR_RCAS_PASSTHROUGH_ALPHA
+ FfxFloat16x4 ee=FsrRcasLoadH(sp);
+ FfxFloat16x3 e=ee.rgb;pixA=ee.a;
+ #else
+ FfxFloat16x3 e=FsrRcasLoadH(sp).rgb;
+ #endif
+ FfxFloat16x3 f=FsrRcasLoadH(sp+FfxInt16x2( 1, 0)).rgb;
+ FfxFloat16x3 h=FsrRcasLoadH(sp+FfxInt16x2( 0, 1)).rgb;
+ // Rename (32-bit) or regroup (16-bit).
+ FfxFloat16 bR=b.r;
+ FfxFloat16 bG=b.g;
+ FfxFloat16 bB=b.b;
+ FfxFloat16 dR=d.r;
+ FfxFloat16 dG=d.g;
+ FfxFloat16 dB=d.b;
+ FfxFloat16 eR=e.r;
+ FfxFloat16 eG=e.g;
+ FfxFloat16 eB=e.b;
+ FfxFloat16 fR=f.r;
+ FfxFloat16 fG=f.g;
+ FfxFloat16 fB=f.b;
+ FfxFloat16 hR=h.r;
+ FfxFloat16 hG=h.g;
+ FfxFloat16 hB=h.b;
+ // Run optional input transform.
+ FsrRcasInputH(bR,bG,bB);
+ FsrRcasInputH(dR,dG,dB);
+ FsrRcasInputH(eR,eG,eB);
+ FsrRcasInputH(fR,fG,fB);
+ FsrRcasInputH(hR,hG,hB);
+ // Luma times 2.
+ FfxFloat16 bL=bB*FFX_BROADCAST_FLOAT16(0.5)+(bR*FFX_BROADCAST_FLOAT16(0.5)+bG);
+ FfxFloat16 dL=dB*FFX_BROADCAST_FLOAT16(0.5)+(dR*FFX_BROADCAST_FLOAT16(0.5)+dG);
+ FfxFloat16 eL=eB*FFX_BROADCAST_FLOAT16(0.5)+(eR*FFX_BROADCAST_FLOAT16(0.5)+eG);
+ FfxFloat16 fL=fB*FFX_BROADCAST_FLOAT16(0.5)+(fR*FFX_BROADCAST_FLOAT16(0.5)+fG);
+ FfxFloat16 hL=hB*FFX_BROADCAST_FLOAT16(0.5)+(hR*FFX_BROADCAST_FLOAT16(0.5)+hG);
+ // Noise detection.
+ FfxFloat16 nz=FFX_BROADCAST_FLOAT16(0.25)*bL+FFX_BROADCAST_FLOAT16(0.25)*dL+FFX_BROADCAST_FLOAT16(0.25)*fL+FFX_BROADCAST_FLOAT16(0.25)*hL-eL;
+ nz=ffxSaturate(abs(nz)*ffxApproximateReciprocalMediumHalf(ffxMax3Half(ffxMax3Half(bL,dL,eL),fL,hL)-ffxMin3Half(ffxMin3Half(bL,dL,eL),fL,hL)));
+ nz=FFX_BROADCAST_FLOAT16(-0.5)*nz+FFX_BROADCAST_FLOAT16(1.0);
+ // Min and max of ring.
+ FfxFloat16 mn4R=min(ffxMin3Half(bR,dR,fR),hR);
+ FfxFloat16 mn4G=min(ffxMin3Half(bG,dG,fG),hG);
+ FfxFloat16 mn4B=min(ffxMin3Half(bB,dB,fB),hB);
+ FfxFloat16 mx4R=max(ffxMax3Half(bR,dR,fR),hR);
+ FfxFloat16 mx4G=max(ffxMax3Half(bG,dG,fG),hG);
+ FfxFloat16 mx4B=max(ffxMax3Half(bB,dB,fB),hB);
+ // Immediate constants for peak range.
+ FfxFloat16x2 peakC=FfxFloat16x2(1.0,-1.0*4.0);
+ // Limiters, these need to be high precision RCPs.
+ FfxFloat16 hitMinR=mn4R*ffxReciprocalHalf(FFX_BROADCAST_FLOAT16(4.0)*mx4R);
+ FfxFloat16 hitMinG=mn4G*ffxReciprocalHalf(FFX_BROADCAST_FLOAT16(4.0)*mx4G);
+ FfxFloat16 hitMinB=mn4B*ffxReciprocalHalf(FFX_BROADCAST_FLOAT16(4.0)*mx4B);
+ FfxFloat16 hitMaxR=(peakC.x-mx4R)*ffxReciprocalHalf(FFX_BROADCAST_FLOAT16(4.0)*mn4R+peakC.y);
+ FfxFloat16 hitMaxG=(peakC.x-mx4G)*ffxReciprocalHalf(FFX_BROADCAST_FLOAT16(4.0)*mn4G+peakC.y);
+ FfxFloat16 hitMaxB=(peakC.x-mx4B)*ffxReciprocalHalf(FFX_BROADCAST_FLOAT16(4.0)*mn4B+peakC.y);
+ FfxFloat16 lobeR=max(-hitMinR,hitMaxR);
+ FfxFloat16 lobeG=max(-hitMinG,hitMaxG);
+ FfxFloat16 lobeB=max(-hitMinB,hitMaxB);
+ FfxFloat16 lobe=max(FFX_BROADCAST_FLOAT16(-FSR_RCAS_LIMIT),min(ffxMax3Half(lobeR,lobeG,lobeB),FFX_BROADCAST_FLOAT16(0.0)))*FFX_UINT32_TO_FLOAT16X2(con.y).x;
+ // Apply noise removal.
+ #ifdef FSR_RCAS_DENOISE
+ lobe*=nz;
+ #endif
+ // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes.
+ FfxFloat16 rcpL=ffxApproximateReciprocalMediumHalf(FFX_BROADCAST_FLOAT16(4.0)*lobe+FFX_BROADCAST_FLOAT16(1.0));
+ pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL;
+ pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL;
+ pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;
+}
+#endif
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//_____________________________________________________________/\_______________________________________________________________
+//==============================================================================================================================
+// PACKED 16-BIT VERSION
+//==============================================================================================================================
+#if defined(FFX_GPU)&& FFX_HALF == 1 && defined(FSR_RCAS_HX2)
+ // Input callback prototypes that need to be implemented by the calling shader
+ FfxFloat16x4 FsrRcasLoadHx2(FfxInt16x2 p);
+ void FsrRcasInputHx2(inout FfxFloat16x2 r,inout FfxFloat16x2 g,inout FfxFloat16x2 b);
+//------------------------------------------------------------------------------------------------------------------------------
+ // Can be used to convert from packed Structures of Arrays to Arrays of Structures for store.
+ void FsrRcasDepackHx2(out FfxFloat16x4 pix0,out FfxFloat16x4 pix1,FfxFloat16x2 pixR,FfxFloat16x2 pixG,FfxFloat16x2 pixB){
+ #ifdef FFX_HLSL
+ // Invoke a slower path for DX only, since it won't allow uninitialized values.
+ pix0.a=pix1.a=0.0;
+ #endif
+ pix0.rgb=FfxFloat16x3(pixR.x,pixG.x,pixB.x);
+ pix1.rgb=FfxFloat16x3(pixR.y,pixG.y,pixB.y);}
+//------------------------------------------------------------------------------------------------------------------------------
+ void FsrRcasHx2(
+ // Output values are for 2 8x8 tiles in a 16x8 region.
+ // pix<R,G,B>.x = left 8x8 tile
+ // pix<R,G,B>.y = right 8x8 tile
+ // This enables later processing to easily be packed as well.
+ out FfxFloat16x2 pixR,
+ out FfxFloat16x2 pixG,
+ out FfxFloat16x2 pixB,
+ #ifdef FSR_RCAS_PASSTHROUGH_ALPHA
+ out FfxFloat16x2 pixA,
+ #endif
+ FfxUInt32x2 ip, // Integer pixel position in output.
+ FfxUInt32x4 con){ // Constant generated by RcasSetup().
+ // No scaling algorithm uses minimal 3x3 pixel neighborhood.
+ FfxInt16x2 sp0=FfxInt16x2(ip);
+ FfxFloat16x3 b0=FsrRcasLoadHx2(sp0+FfxInt16x2( 0,-1)).rgb;
+ FfxFloat16x3 d0=FsrRcasLoadHx2(sp0+FfxInt16x2(-1, 0)).rgb;
+ #ifdef FSR_RCAS_PASSTHROUGH_ALPHA
+ FfxFloat16x4 ee0=FsrRcasLoadHx2(sp0);
+ FfxFloat16x3 e0=ee0.rgb;pixA.r=ee0.a;
+ #else
+ FfxFloat16x3 e0=FsrRcasLoadHx2(sp0).rgb;
+ #endif
+ FfxFloat16x3 f0=FsrRcasLoadHx2(sp0+FfxInt16x2( 1, 0)).rgb;
+ FfxFloat16x3 h0=FsrRcasLoadHx2(sp0+FfxInt16x2( 0, 1)).rgb;
+ FfxInt16x2 sp1=sp0+FfxInt16x2(8,0);
+ FfxFloat16x3 b1=FsrRcasLoadHx2(sp1+FfxInt16x2( 0,-1)).rgb;
+ FfxFloat16x3 d1=FsrRcasLoadHx2(sp1+FfxInt16x2(-1, 0)).rgb;
+ #ifdef FSR_RCAS_PASSTHROUGH_ALPHA
+ FfxFloat16x4 ee1=FsrRcasLoadHx2(sp1);
+ FfxFloat16x3 e1=ee1.rgb;pixA.g=ee1.a;
+ #else
+ FfxFloat16x3 e1=FsrRcasLoadHx2(sp1).rgb;
+ #endif
+ FfxFloat16x3 f1=FsrRcasLoadHx2(sp1+FfxInt16x2( 1, 0)).rgb;
+ FfxFloat16x3 h1=FsrRcasLoadHx2(sp1+FfxInt16x2( 0, 1)).rgb;
+ // Arrays of Structures to Structures of Arrays conversion.
+ FfxFloat16x2 bR=FfxFloat16x2(b0.r,b1.r);
+ FfxFloat16x2 bG=FfxFloat16x2(b0.g,b1.g);
+ FfxFloat16x2 bB=FfxFloat16x2(b0.b,b1.b);
+ FfxFloat16x2 dR=FfxFloat16x2(d0.r,d1.r);
+ FfxFloat16x2 dG=FfxFloat16x2(d0.g,d1.g);
+ FfxFloat16x2 dB=FfxFloat16x2(d0.b,d1.b);
+ FfxFloat16x2 eR=FfxFloat16x2(e0.r,e1.r);
+ FfxFloat16x2 eG=FfxFloat16x2(e0.g,e1.g);
+ FfxFloat16x2 eB=FfxFloat16x2(e0.b,e1.b);
+ FfxFloat16x2 fR=FfxFloat16x2(f0.r,f1.r);
+ FfxFloat16x2 fG=FfxFloat16x2(f0.g,f1.g);
+ FfxFloat16x2 fB=FfxFloat16x2(f0.b,f1.b);
+ FfxFloat16x2 hR=FfxFloat16x2(h0.r,h1.r);
+ FfxFloat16x2 hG=FfxFloat16x2(h0.g,h1.g);
+ FfxFloat16x2 hB=FfxFloat16x2(h0.b,h1.b);
+ // Run optional input transform.
+ FsrRcasInputHx2(bR,bG,bB);
+ FsrRcasInputHx2(dR,dG,dB);
+ FsrRcasInputHx2(eR,eG,eB);
+ FsrRcasInputHx2(fR,fG,fB);
+ FsrRcasInputHx2(hR,hG,hB);
+ // Luma times 2.
+ FfxFloat16x2 bL=bB*FFX_BROADCAST_FLOAT16X2(0.5)+(bR*FFX_BROADCAST_FLOAT16X2(0.5)+bG);
+ FfxFloat16x2 dL=dB*FFX_BROADCAST_FLOAT16X2(0.5)+(dR*FFX_BROADCAST_FLOAT16X2(0.5)+dG);
+ FfxFloat16x2 eL=eB*FFX_BROADCAST_FLOAT16X2(0.5)+(eR*FFX_BROADCAST_FLOAT16X2(0.5)+eG);
+ FfxFloat16x2 fL=fB*FFX_BROADCAST_FLOAT16X2(0.5)+(fR*FFX_BROADCAST_FLOAT16X2(0.5)+fG);
+ FfxFloat16x2 hL=hB*FFX_BROADCAST_FLOAT16X2(0.5)+(hR*FFX_BROADCAST_FLOAT16X2(0.5)+hG);
+ // Noise detection.
+ FfxFloat16x2 nz=FFX_BROADCAST_FLOAT16X2(0.25)*bL+FFX_BROADCAST_FLOAT16X2(0.25)*dL+FFX_BROADCAST_FLOAT16X2(0.25)*fL+FFX_BROADCAST_FLOAT16X2(0.25)*hL-eL;
+ nz=ffxSaturate(abs(nz)*ffxApproximateReciprocalMediumHalf(ffxMax3Half(ffxMax3Half(bL,dL,eL),fL,hL)-ffxMin3Half(ffxMin3Half(bL,dL,eL),fL,hL)));
+ nz=FFX_BROADCAST_FLOAT16X2(-0.5)*nz+FFX_BROADCAST_FLOAT16X2(1.0);
+ // Min and max of ring.
+ FfxFloat16x2 mn4R=min(ffxMin3Half(bR,dR,fR),hR);
+ FfxFloat16x2 mn4G=min(ffxMin3Half(bG,dG,fG),hG);
+ FfxFloat16x2 mn4B=min(ffxMin3Half(bB,dB,fB),hB);
+ FfxFloat16x2 mx4R=max(ffxMax3Half(bR,dR,fR),hR);
+ FfxFloat16x2 mx4G=max(ffxMax3Half(bG,dG,fG),hG);
+ FfxFloat16x2 mx4B=max(ffxMax3Half(bB,dB,fB),hB);
+ // Immediate constants for peak range.
+ FfxFloat16x2 peakC=FfxFloat16x2(1.0,-1.0*4.0);
+ // Limiters, these need to be high precision RCPs.
+ FfxFloat16x2 hitMinR=mn4R*ffxReciprocalHalf(FFX_BROADCAST_FLOAT16X2(4.0)*mx4R);
+ FfxFloat16x2 hitMinG=mn4G*ffxReciprocalHalf(FFX_BROADCAST_FLOAT16X2(4.0)*mx4G);
+ FfxFloat16x2 hitMinB=mn4B*ffxReciprocalHalf(FFX_BROADCAST_FLOAT16X2(4.0)*mx4B);
+ FfxFloat16x2 hitMaxR=(peakC.x-mx4R)*ffxReciprocalHalf(FFX_BROADCAST_FLOAT16X2(4.0)*mn4R+peakC.y);
+ FfxFloat16x2 hitMaxG=(peakC.x-mx4G)*ffxReciprocalHalf(FFX_BROADCAST_FLOAT16X2(4.0)*mn4G+peakC.y);
+ FfxFloat16x2 hitMaxB=(peakC.x-mx4B)*ffxReciprocalHalf(FFX_BROADCAST_FLOAT16X2(4.0)*mn4B+peakC.y);
+ FfxFloat16x2 lobeR=max(-hitMinR,hitMaxR);
+ FfxFloat16x2 lobeG=max(-hitMinG,hitMaxG);
+ FfxFloat16x2 lobeB=max(-hitMinB,hitMaxB);
+ FfxFloat16x2 lobe=max(FFX_BROADCAST_FLOAT16X2(-FSR_RCAS_LIMIT),min(ffxMax3Half(lobeR,lobeG,lobeB),FFX_BROADCAST_FLOAT16X2(0.0)))*FFX_BROADCAST_FLOAT16X2(FFX_UINT32_TO_FLOAT16X2(con.y).x);
+ // Apply noise removal.
+ #ifdef FSR_RCAS_DENOISE
+ lobe*=nz;
+ #endif
+ // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes.
+ FfxFloat16x2 rcpL=ffxApproximateReciprocalMediumHalf(FFX_BROADCAST_FLOAT16X2(4.0)*lobe+FFX_BROADCAST_FLOAT16X2(1.0));
+ pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL;
+ pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL;
+ pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;}
+#endif
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//_____________________________________________________________/\_______________________________________________________________
+//==============================================================================================================================
+//
+// FSR - [LFGA] LINEAR FILM GRAIN APPLICATOR
+//
+//------------------------------------------------------------------------------------------------------------------------------
+// Adding output-resolution film grain after scaling is a good way to mask both rendering and scaling artifacts.
+// Suggest using tiled blue noise as film grain input, with peak noise frequency set for a specific look and feel.
+// The 'Lfga*()' functions provide a convenient way to introduce grain.
+// These functions limit grain based on distance to signal limits.
+// This is done so that the grain is temporally energy preserving, and thus won't modify image tonality.
+// Grain application should be done in a linear colorspace.
+// The grain should be temporally changing, but have a temporal sum per pixel that adds to zero (non-biased).
+//------------------------------------------------------------------------------------------------------------------------------
+// Usage,
+// FsrLfga*(
+// color, // In/out linear colorspace color {0 to 1} ranged.
+// grain, // Per pixel grain texture value {-0.5 to 0.5} ranged, input is 3-channel to support colored grain.
+// amount); // Amount of grain (0 to 1} ranged.
+//------------------------------------------------------------------------------------------------------------------------------
+// Example if grain texture is monochrome: 'FsrLfgaF(color,ffxBroadcast3(grain),amount)'
+//==============================================================================================================================
+#if defined(FFX_GPU)
+ // Maximum grain is the minimum distance to the signal limit.
+ void FsrLfgaF(inout FfxFloat32x3 c, FfxFloat32x3 t, FfxFloat32 a)
+ {
+ c += (t * ffxBroadcast3(a)) * ffxMin(ffxBroadcast3(1.0) - c, c);
+ }
+#endif
+//==============================================================================================================================
+#if defined(FFX_GPU)&& FFX_HALF == 1
+ // Half precision version (slower).
+ void FsrLfgaH(inout FfxFloat16x3 c, FfxFloat16x3 t, FfxFloat16 a)
+ {
+ c += (t * FFX_BROADCAST_FLOAT16X3(a)) * min(FFX_BROADCAST_FLOAT16X3(1.0) - c, c);
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ // Packed half precision version (faster).
+ void FsrLfgaHx2(inout FfxFloat16x2 cR,inout FfxFloat16x2 cG,inout FfxFloat16x2 cB,FfxFloat16x2 tR,FfxFloat16x2 tG,FfxFloat16x2 tB,FfxFloat16 a){
+ cR+=(tR*FFX_BROADCAST_FLOAT16X2(a))*min(FFX_BROADCAST_FLOAT16X2(1.0)-cR,cR);cG+=(tG*FFX_BROADCAST_FLOAT16X2(a))*min(FFX_BROADCAST_FLOAT16X2(1.0)-cG,cG);cB+=(tB*FFX_BROADCAST_FLOAT16X2(a))*min(FFX_BROADCAST_FLOAT16X2(1.0)-cB,cB);}
+#endif
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//_____________________________________________________________/\_______________________________________________________________
+//==============================================================================================================================
+//
+// FSR - [SRTM] SIMPLE REVERSIBLE TONE-MAPPER
+//
+//------------------------------------------------------------------------------------------------------------------------------
+// This provides a way to take linear HDR color {0 to FP16_MAX} and convert it into a temporary {0 to 1} ranged post-tonemapped linear.
+// The tonemapper preserves RGB ratio, which helps maintain HDR color bleed during filtering.
+//------------------------------------------------------------------------------------------------------------------------------
+// Reversible tonemapper usage,
+// FsrSrtm*(color); // {0 to FP16_MAX} converted to {0 to 1}.
+// FsrSrtmInv*(color); // {0 to 1} converted into {0 to 32768, output peak safe for FP16}.
+//==============================================================================================================================
+#if defined(FFX_GPU)
+ void FsrSrtmF(inout FfxFloat32x3 c)
+ {
+ c *= ffxBroadcast3(ffxReciprocal(ffxMax3(c.r, c.g, c.b) + FfxFloat32(1.0)));
+ }
+ // The extra max solves the c=1.0 case (which is a /0).
+ void FsrSrtmInvF(inout FfxFloat32x3 c){c*=ffxBroadcast3(ffxReciprocal(max(FfxFloat32(1.0/32768.0),FfxFloat32(1.0)-ffxMax3(c.r,c.g,c.b))));}
+#endif
+//==============================================================================================================================
+#if defined(FFX_GPU )&& FFX_HALF == 1
+ void FsrSrtmH(inout FfxFloat16x3 c)
+ {
+ c *= FFX_BROADCAST_FLOAT16X3(ffxReciprocalHalf(ffxMax3Half(c.r, c.g, c.b) + FFX_BROADCAST_FLOAT16(1.0)));
+ }
+ void FsrSrtmInvH(inout FfxFloat16x3 c)
+ {
+ c *= FFX_BROADCAST_FLOAT16X3(ffxReciprocalHalf(max(FFX_BROADCAST_FLOAT16(1.0 / 32768.0), FFX_BROADCAST_FLOAT16(1.0) - ffxMax3Half(c.r, c.g, c.b))));
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ void FsrSrtmHx2(inout FfxFloat16x2 cR, inout FfxFloat16x2 cG, inout FfxFloat16x2 cB)
+ {
+ FfxFloat16x2 rcp = ffxReciprocalHalf(ffxMax3Half(cR, cG, cB) + FFX_BROADCAST_FLOAT16X2(1.0));
+ cR *= rcp;
+ cG *= rcp;
+ cB *= rcp;
+ }
+ void FsrSrtmInvHx2(inout FfxFloat16x2 cR,inout FfxFloat16x2 cG,inout FfxFloat16x2 cB)
+ {
+ FfxFloat16x2 rcp=ffxReciprocalHalf(max(FFX_BROADCAST_FLOAT16X2(1.0/32768.0),FFX_BROADCAST_FLOAT16X2(1.0)-ffxMax3Half(cR,cG,cB)));
+ cR*=rcp;
+ cG*=rcp;
+ cB*=rcp;
+ }
+#endif
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//_____________________________________________________________/\_______________________________________________________________
+//==============================================================================================================================
+//
+// FSR - [TEPD] TEMPORAL ENERGY PRESERVING DITHER
+//
+//------------------------------------------------------------------------------------------------------------------------------
+// Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion.
+// Gamma 2.0 is used so that the conversion back to linear is just to square the color.
+// The conversion comes in 8-bit and 10-bit modes, designed for output to 8-bit UNORM or 10:10:10:2 respectively.
+// Given good non-biased temporal blue noise as dither input,
+// the output dither will temporally conserve energy.
+// This is done by choosing the linear nearest step point instead of perceptual nearest.
+// See code below for details.
+//------------------------------------------------------------------------------------------------------------------------------
+// DX SPEC RULES FOR FLOAT->UNORM 8-BIT CONVERSION
+// ===============================================
+// - Output is 'FfxUInt32(floor(saturate(n)*255.0+0.5))'.
+// - Thus rounding is to nearest.
+// - NaN gets converted to zero.
+// - INF is clamped to {0.0 to 1.0}.
+//==============================================================================================================================
+#if defined(FFX_GPU)
+ // Hand tuned integer position to dither value, with more values than simple checkerboard.
+ // Only 32-bit has enough precision for this compddation.
+ // Output is {0 to <1}.
+ FfxFloat32 FsrTepdDitF(FfxUInt32x2 p, FfxUInt32 f)
+ {
+ FfxFloat32 x = FfxFloat32(p.x + f);
+ FfxFloat32 y = FfxFloat32(p.y);
+ // The 1.61803 golden ratio.
+ FfxFloat32 a = FfxFloat32((1.0 + ffxSqrt(5.0f)) / 2.0);
+ // Number designed to provide a good visual pattern.
+ FfxFloat32 b = FfxFloat32(1.0 / 3.69);
+ x = x * a + (y * b);
+ return ffxFract(x);
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ // This version is 8-bit gamma 2.0.
+ // The 'c' input is {0 to 1}.
+ // Output is {0 to 1} ready for image store.
+ void FsrTepdC8F(inout FfxFloat32x3 c, FfxFloat32 dit)
+ {
+ FfxFloat32x3 n = ffxSqrt(c);
+ n = floor(n * ffxBroadcast3(255.0)) * ffxBroadcast3(1.0 / 255.0);
+ FfxFloat32x3 a = n * n;
+ FfxFloat32x3 b = n + ffxBroadcast3(1.0 / 255.0);
+ b = b * b;
+ // Ratio of 'a' to 'b' required to produce 'c'.
+ // ffxApproximateReciprocal() won't work here (at least for very high dynamic ranges).
+ // ffxApproximateReciprocalMedium() is an IADD,FMA,MUL.
+ FfxFloat32x3 r = (c - b) * ffxApproximateReciprocalMedium(a - b);
+ // Use the ratio as a cutoff to choose 'a' or 'b'.
+ // ffxIsGreaterThanZero() is a MUL.
+ c = ffxSaturate(n + ffxIsGreaterThanZero(ffxBroadcast3(dit) - r) * ffxBroadcast3(1.0 / 255.0));
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ // This version is 10-bit gamma 2.0.
+ // The 'c' input is {0 to 1}.
+ // Output is {0 to 1} ready for image store.
+ void FsrTepdC10F(inout FfxFloat32x3 c, FfxFloat32 dit)
+ {
+ FfxFloat32x3 n = ffxSqrt(c);
+ n = floor(n * ffxBroadcast3(1023.0)) * ffxBroadcast3(1.0 / 1023.0);
+ FfxFloat32x3 a = n * n;
+ FfxFloat32x3 b = n + ffxBroadcast3(1.0 / 1023.0);
+ b = b * b;
+ FfxFloat32x3 r = (c - b) * ffxApproximateReciprocalMedium(a - b);
+ c = ffxSaturate(n + ffxIsGreaterThanZero(ffxBroadcast3(dit) - r) * ffxBroadcast3(1.0 / 1023.0));
+ }
+#endif
+//==============================================================================================================================
+#if defined(FFX_GPU)&& FFX_HALF == 1
+ FfxFloat16 FsrTepdDitH(FfxUInt32x2 p, FfxUInt32 f)
+ {
+ FfxFloat32 x = FfxFloat32(p.x + f);
+ FfxFloat32 y = FfxFloat32(p.y);
+ FfxFloat32 a = FfxFloat32((1.0 + ffxSqrt(5.0f)) / 2.0);
+ FfxFloat32 b = FfxFloat32(1.0 / 3.69);
+ x = x * a + (y * b);
+ return FfxFloat16(ffxFract(x));
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ void FsrTepdC8H(inout FfxFloat16x3 c, FfxFloat16 dit)
+ {
+ FfxFloat16x3 n = sqrt(c);
+ n = floor(n * FFX_BROADCAST_FLOAT16X3(255.0)) * FFX_BROADCAST_FLOAT16X3(1.0 / 255.0);
+ FfxFloat16x3 a = n * n;
+ FfxFloat16x3 b = n + FFX_BROADCAST_FLOAT16X3(1.0 / 255.0);
+ b = b * b;
+ FfxFloat16x3 r = (c - b) * ffxApproximateReciprocalMediumHalf(a - b);
+ c = ffxSaturate(n + ffxIsGreaterThanZeroHalf(FFX_BROADCAST_FLOAT16X3(dit) - r) * FFX_BROADCAST_FLOAT16X3(1.0 / 255.0));
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ void FsrTepdC10H(inout FfxFloat16x3 c, FfxFloat16 dit)
+ {
+ FfxFloat16x3 n = sqrt(c);
+ n = floor(n * FFX_BROADCAST_FLOAT16X3(1023.0)) * FFX_BROADCAST_FLOAT16X3(1.0 / 1023.0);
+ FfxFloat16x3 a = n * n;
+ FfxFloat16x3 b = n + FFX_BROADCAST_FLOAT16X3(1.0 / 1023.0);
+ b = b * b;
+ FfxFloat16x3 r = (c - b) * ffxApproximateReciprocalMediumHalf(a - b);
+ c = ffxSaturate(n + ffxIsGreaterThanZeroHalf(FFX_BROADCAST_FLOAT16X3(dit) - r) * FFX_BROADCAST_FLOAT16X3(1.0 / 1023.0));
+ }
+ //==============================================================================================================================
+ // This computes dither for positions 'p' and 'p+{8,0}'.
+ FfxFloat16x2 FsrTepdDitHx2(FfxUInt32x2 p, FfxUInt32 f)
+ {
+ FfxFloat32x2 x;
+ x.x = FfxFloat32(p.x + f);
+ x.y = x.x + FfxFloat32(8.0);
+ FfxFloat32 y = FfxFloat32(p.y);
+ FfxFloat32 a = FfxFloat32((1.0 + ffxSqrt(5.0f)) / 2.0);
+ FfxFloat32 b = FfxFloat32(1.0 / 3.69);
+ x = x * ffxBroadcast2(a) + ffxBroadcast2(y * b);
+ return FfxFloat16x2(ffxFract(x));
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ void FsrTepdC8Hx2(inout FfxFloat16x2 cR, inout FfxFloat16x2 cG, inout FfxFloat16x2 cB, FfxFloat16x2 dit)
+ {
+ FfxFloat16x2 nR = sqrt(cR);
+ FfxFloat16x2 nG = sqrt(cG);
+ FfxFloat16x2 nB = sqrt(cB);
+ nR = floor(nR * FFX_BROADCAST_FLOAT16X2(255.0)) * FFX_BROADCAST_FLOAT16X2(1.0 / 255.0);
+ nG = floor(nG * FFX_BROADCAST_FLOAT16X2(255.0)) * FFX_BROADCAST_FLOAT16X2(1.0 / 255.0);
+ nB = floor(nB * FFX_BROADCAST_FLOAT16X2(255.0)) * FFX_BROADCAST_FLOAT16X2(1.0 / 255.0);
+ FfxFloat16x2 aR = nR * nR;
+ FfxFloat16x2 aG = nG * nG;
+ FfxFloat16x2 aB = nB * nB;
+ FfxFloat16x2 bR = nR + FFX_BROADCAST_FLOAT16X2(1.0 / 255.0);
+ bR = bR * bR;
+ FfxFloat16x2 bG = nG + FFX_BROADCAST_FLOAT16X2(1.0 / 255.0);
+ bG = bG * bG;
+ FfxFloat16x2 bB = nB + FFX_BROADCAST_FLOAT16X2(1.0 / 255.0);
+ bB = bB * bB;
+ FfxFloat16x2 rR = (cR - bR) * ffxApproximateReciprocalMediumHalf(aR - bR);
+ FfxFloat16x2 rG = (cG - bG) * ffxApproximateReciprocalMediumHalf(aG - bG);
+ FfxFloat16x2 rB = (cB - bB) * ffxApproximateReciprocalMediumHalf(aB - bB);
+ cR = ffxSaturate(nR + ffxIsGreaterThanZeroHalf(dit - rR) * FFX_BROADCAST_FLOAT16X2(1.0 / 255.0));
+ cG = ffxSaturate(nG + ffxIsGreaterThanZeroHalf(dit - rG) * FFX_BROADCAST_FLOAT16X2(1.0 / 255.0));
+ cB = ffxSaturate(nB + ffxIsGreaterThanZeroHalf(dit - rB) * FFX_BROADCAST_FLOAT16X2(1.0 / 255.0));
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ void FsrTepdC10Hx2(inout FfxFloat16x2 cR,inout FfxFloat16x2 cG,inout FfxFloat16x2 cB,FfxFloat16x2 dit){
+ FfxFloat16x2 nR=sqrt(cR);
+ FfxFloat16x2 nG=sqrt(cG);
+ FfxFloat16x2 nB=sqrt(cB);
+ nR=floor(nR*FFX_BROADCAST_FLOAT16X2(1023.0))*FFX_BROADCAST_FLOAT16X2(1.0/1023.0);
+ nG=floor(nG*FFX_BROADCAST_FLOAT16X2(1023.0))*FFX_BROADCAST_FLOAT16X2(1.0/1023.0);
+ nB=floor(nB*FFX_BROADCAST_FLOAT16X2(1023.0))*FFX_BROADCAST_FLOAT16X2(1.0/1023.0);
+ FfxFloat16x2 aR=nR*nR;
+ FfxFloat16x2 aG=nG*nG;
+ FfxFloat16x2 aB=nB*nB;
+ FfxFloat16x2 bR=nR+FFX_BROADCAST_FLOAT16X2(1.0/1023.0);bR=bR*bR;
+ FfxFloat16x2 bG=nG+FFX_BROADCAST_FLOAT16X2(1.0/1023.0);bG=bG*bG;
+ FfxFloat16x2 bB=nB+FFX_BROADCAST_FLOAT16X2(1.0/1023.0);bB=bB*bB;
+ FfxFloat16x2 rR=(cR-bR)*ffxApproximateReciprocalMediumHalf(aR-bR);
+ FfxFloat16x2 rG=(cG-bG)*ffxApproximateReciprocalMediumHalf(aG-bG);
+ FfxFloat16x2 rB=(cB-bB)*ffxApproximateReciprocalMediumHalf(aB-bB);
+ cR=ffxSaturate(nR+ffxIsGreaterThanZeroHalf(dit-rR)*FFX_BROADCAST_FLOAT16X2(1.0/1023.0));
+ cG=ffxSaturate(nG+ffxIsGreaterThanZeroHalf(dit-rG)*FFX_BROADCAST_FLOAT16X2(1.0/1023.0));
+ cB = ffxSaturate(nB + ffxIsGreaterThanZeroHalf(dit - rB) * FFX_BROADCAST_FLOAT16X2(1.0 / 1023.0));
+}
+#endif
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_accumulate.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_accumulate.h
new file mode 100644
index 0000000000..a8c6daa0e6
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_accumulate.h
@@ -0,0 +1,172 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+void Accumulate(const AccumulationPassCommonParams params, FFX_PARAMETER_INOUT AccumulationPassData data)
+{
+ // Avoid invalid values when accumulation and upsampled weight is 0
+ data.fHistoryWeight *= FfxFloat32(data.fHistoryWeight > FSR3UPSCALER_FP16_MIN);
+ data.fHistoryWeight = ffxMax(FSR3UPSCALER_EPSILON, data.fHistoryWeight + data.fUpsampledWeight);
+
+#if FFX_FSR3UPSCALER_OPTION_HDR_COLOR_INPUT
+ //YCoCg -> RGB -> Tonemap -> YCoCg (Use RGB tonemapper to avoid color desaturation)
+ data.fUpsampledColor = RGBToYCoCg(Tonemap(YCoCgToRGB(data.fUpsampledColor)));
+ data.fHistoryColor = RGBToYCoCg(Tonemap(YCoCgToRGB(data.fHistoryColor)));
+#endif
+
+ const FfxFloat32 fAlpha = ffxSaturate(data.fUpsampledWeight / data.fHistoryWeight);
+ data.fHistoryColor = ffxLerp(data.fHistoryColor, data.fUpsampledColor, fAlpha);
+ data.fHistoryColor = YCoCgToRGB(data.fHistoryColor);
+
+#if FFX_FSR3UPSCALER_OPTION_HDR_COLOR_INPUT
+ data.fHistoryColor = InverseTonemap(data.fHistoryColor);
+#endif
+}
+
+void RectifyHistory(
+ const AccumulationPassCommonParams params,
+ FFX_PARAMETER_INOUT AccumulationPassData data
+)
+{
+ const FfxFloat32 f4kVelocityFactor = ffxSaturate(params.f4KVelocity / 20.0f);
+ const FfxFloat32 fDistanceFactor = ffxSaturate(0.75f - params.fFarthestDepthInMeters / 20.0f);
+ const FfxFloat32 fAccumulationFactor = 1.0f - params.fAccumulation;
+ const FfxFloat32 fReactiveFactor = ffxPow(params.fReactiveMask, 1.0f / 2.0f);
+ const FfxFloat32 fShadingChangeFactor = params.fShadingChange;
+ const FfxFloat32 fBoxScaleT = ffxMax(f4kVelocityFactor, ffxMax(fDistanceFactor, ffxMax(fAccumulationFactor, ffxMax(fReactiveFactor, fShadingChangeFactor))));
+
+ const FfxFloat32 fBoxScale = ffxLerp(3.0f, 1.0f, fBoxScaleT);
+ const FfxFloat32x3 fScaledBoxVec = data.clippingBox.boxVec * FfxFloat32x3(1.7f, 1.0f, 1.0f) * fBoxScale;
+
+ const FfxFloat32x3 fClampedScaledBoxVec = ffxMax(fScaledBoxVec, FfxFloat32x3(1.193e-7f, 1.193e-7f, 1.193e-7f));
+ const FfxFloat32x3 fTransformedHistoryColor = (data.fHistoryColor - data.clippingBox.boxCenter) / fClampedScaledBoxVec;
+
+ if (length(fTransformedHistoryColor)>1.f) {
+ const FfxFloat32x3 fClampedHistoryColor = normalize(fTransformedHistoryColor);
+ const FfxFloat32x3 fFinalClampedHistoryColor = (fClampedHistoryColor * fScaledBoxVec) + data.clippingBox.boxCenter;
+
+ // Scale history color using rectification info, also using accumulation mask to avoid potential invalid color protection
+ const FfxFloat32 fHistoryContribution = ffxMax(params.fLumaInstabilityFactor, data.fLockContributionThisFrame) * params.fAccumulation * (1 - params.fDisocclusion);
+ data.fHistoryColor = ffxLerp(fFinalClampedHistoryColor, data.fHistoryColor, ffxSaturate(fHistoryContribution));
+ }
+}
+
+void UpdateLockStatus(AccumulationPassCommonParams params, FFX_PARAMETER_INOUT AccumulationPassData data)
+{
+ data.fLock *= FfxFloat32(params.bIsNewSample == false);
+
+ const FfxFloat32 fLifetimeDecreaseFactor = ffxMax(ffxSaturate(params.fShadingChange), ffxMax(params.fReactiveMask, params.fDisocclusion));
+ data.fLock = ffxMax(0.0f, data.fLock - fLifetimeDecreaseFactor * fLockMax);
+
+ // Compute this frame lock contribution
+ data.fLockContributionThisFrame = ffxSaturate(ffxSaturate(data.fLock - fLockThreshold) * (fLockMax - fLockThreshold));
+
+ const FfxFloat32 fNewLockIntensity = LoadRwNewLocks(params.iPxHrPos) * (1.0f - ffxMax(params.fShadingChange * 0, params.fReactiveMask));
+ data.fLock = ffxMax(0.0f, ffxMin(data.fLock + fNewLockIntensity, fLockMax));
+
+ // Preparing for next frame
+ const FfxFloat32 fLifetimeDecrease = (0.1f / JitterSequenceLength()) * (1.0f - fLifetimeDecreaseFactor);
+ data.fLock = ffxMax(0.0f, data.fLock - fLifetimeDecrease);
+
+ // we expect similar motion for next frame
+ // kill lock if that location is outside screen, avoid locks to be clamped to screen borders
+ const FfxFloat32x2 fEstimatedUvNextFrame = params.fHrUv - params.fMotionVector;
+ data.fLock *= FfxFloat32(IsUvInside(fEstimatedUvNextFrame) == true);
+}
+
+void ComputeBaseAccumulationWeight(const AccumulationPassCommonParams params, FFX_PARAMETER_INOUT AccumulationPassData data)
+{
+ FfxFloat32 fBaseAccumulation = params.fAccumulation;
+
+ fBaseAccumulation = ffxMin(fBaseAccumulation, ffxLerp(fBaseAccumulation, 0.15f, ffxSaturate(ffxMax(0.0f, (params.f4KVelocity * VelocityFactor()) / 0.5f))));
+
+ data.fHistoryWeight = fBaseAccumulation;
+}
+
+void InitPassData(FfxInt32x2 iPxHrPos, FFX_PARAMETER_INOUT AccumulationPassCommonParams params, FFX_PARAMETER_INOUT AccumulationPassData data)
+{
+ // Init constant params
+ params.iPxHrPos = iPxHrPos;
+ const FfxFloat32x2 fHrUv = (iPxHrPos + 0.5f) / UpscaleSize();
+ params.fHrUv = fHrUv;
+ params.fLrUvJittered = fHrUv + Jitter() / RenderSize();
+ params.fLrUv_HwSampler = ClampUv(params.fLrUvJittered, RenderSize(), MaxRenderSize());
+
+ params.fMotionVector = GetMotionVector(iPxHrPos, fHrUv);
+ params.f4KVelocity = Get4KVelocity(params.fMotionVector);
+
+ ComputeReprojectedUVs(params);
+
+ const FfxFloat32x2 fLumaInstabilityUv_HW = ClampUv(fHrUv, RenderSize(), MaxRenderSize());
+ params.fLumaInstabilityFactor = SampleLumaInstability(fLumaInstabilityUv_HW);
+
+ const FfxFloat32x2 fFarthestDepthUv = ClampUv(params.fLrUvJittered, RenderSize() / 2, GetFarthestDepthMip1ResourceDimensions());
+ params.fFarthestDepthInMeters = SampleFarthestDepthMip1(fFarthestDepthUv);
+ params.bIsNewSample = (params.bIsExistingSample == false || 0 == FrameIndex());
+
+ const FfxFloat32x4 fDilatedReactiveMasks = SampleDilatedReactiveMasks(params.fLrUv_HwSampler);
+ params.fReactiveMask = ffxSaturate(fDilatedReactiveMasks[REACTIVE]);
+ params.fDisocclusion = ffxSaturate(fDilatedReactiveMasks[DISOCCLUSION]);
+ params.fShadingChange = ffxSaturate(fDilatedReactiveMasks[SHADING_CHANGE]);
+ params.fAccumulation = ffxSaturate(fDilatedReactiveMasks[ACCUMULAION]);
+ params.fAccumulation *= FfxFloat32(round(params.fAccumulation * 100.0f) > 1.0f);
+
+ // Init variable data
+ data.fUpsampledColor = FfxFloat32x3(0.0f, 0.0f, 0.0f);
+ data.fHistoryColor = FfxFloat32x3(0.0f, 0.0f, 0.0f);
+ data.fHistoryWeight = 1.0f;
+ data.fUpsampledWeight = 0.0f;
+ data.fLock = 0.0f;
+ data.fLockContributionThisFrame = 0.0f;
+}
+
+void Accumulate(FfxInt32x2 iPxHrPos)
+{
+ AccumulationPassCommonParams params;
+ AccumulationPassData data;
+ InitPassData(iPxHrPos, params, data);
+
+ if (params.bIsExistingSample && !params.bIsNewSample) {
+ ReprojectHistoryColor(params, data);
+ }
+
+ UpdateLockStatus(params, data);
+
+ ComputeBaseAccumulationWeight(params, data);
+
+ ComputeUpsampledColorAndWeight(params, data);
+
+ RectifyHistory(params, data);
+
+ Accumulate(params, data);
+
+ data.fHistoryColor /= Exposure();
+
+ data.fHistoryColor = ffxMax(data.fHistoryColor, FfxFloat32x3(0.0f, 0.0f, 0.0f));
+
+ StoreInternalColorAndWeight(iPxHrPos, FfxFloat32x4(data.fHistoryColor, data.fLock));
+
+ // Output final color when RCAS is disabled
+#if FFX_FSR3UPSCALER_OPTION_APPLY_SHARPENING == 0
+ StoreUpscaledOutput(iPxHrPos, data.fHistoryColor);
+#endif
+ StoreNewLocks(iPxHrPos, 0);
+}
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_callbacks_glsl.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_callbacks_glsl.h
new file mode 100644
index 0000000000..d0cbf9d32e
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_callbacks_glsl.h
@@ -0,0 +1,903 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#include "ffx_fsr3upscaler_resources.h"
+
+#if defined(FFX_GPU)
+#include "ffx_core.h"
+#endif // #if defined(FFX_GPU)
+
+#if defined(FFX_GPU)
+#ifndef FFX_PREFER_WAVE64
+#define FFX_PREFER_WAVE64
+#endif // FFX_PREFER_WAVE64
+
+#if defined(FSR3UPSCALER_BIND_CB_FSR3UPSCALER)
+ layout (set = 0, binding = FSR3UPSCALER_BIND_CB_FSR3UPSCALER, std140) uniform cbFSR3UPSCALER_t
+ {
+ FfxInt32x2 iRenderSize;
+ FfxInt32x2 iPreviousFrameRenderSize;
+
+ FfxInt32x2 iUpscaleSize;
+ FfxInt32x2 iPreviousFrameUpscaleSize;
+
+ FfxInt32x2 iMaxRenderSize;
+ FfxInt32x2 iMaxUpscaleSize;
+
+ FfxFloat32x4 fDeviceToViewDepth;
+
+ FfxFloat32x2 fJitter;
+ FfxFloat32x2 fPreviousFrameJitter;
+
+ FfxFloat32x2 fMotionVectorScale;
+ FfxFloat32x2 fDownscaleFactor;
+
+ FfxFloat32x2 fMotionVectorJitterCancellation;
+ FfxFloat32 fTanHalfFOV;
+ FfxFloat32 fJitterSequenceLength;
+
+ FfxFloat32 fDeltaTime;
+ FfxFloat32 fDeltaPreExposure;
+ FfxFloat32 fViewSpaceToMetersFactor;
+ FfxFloat32 fFrameIndex;
+
+ FfxFloat32 fVelocityFactor;
+ FfxFloat32 fReactivenessScale;
+ FfxFloat32 fShadingChangeScale;
+ FfxFloat32 fAccumulationAddedPerFrame;
+ FfxFloat32 fMinDisocclusionAccumulation;
+ } cbFSR3Upscaler;
+
+
+FfxInt32x2 RenderSize()
+{
+ return cbFSR3Upscaler.iRenderSize;
+}
+
+FfxInt32x2 PreviousFrameRenderSize()
+{
+ return cbFSR3Upscaler.iPreviousFrameRenderSize;
+}
+
+FfxInt32x2 MaxRenderSize()
+{
+ return cbFSR3Upscaler.iMaxRenderSize;
+}
+
+FfxInt32x2 UpscaleSize()
+{
+ return cbFSR3Upscaler.iUpscaleSize;
+}
+
+FfxInt32x2 PreviousFrameUpscaleSize()
+{
+ return cbFSR3Upscaler.iPreviousFrameUpscaleSize;
+}
+
+FfxInt32x2 MaxUpscaleSize()
+{
+ return cbFSR3Upscaler.iMaxUpscaleSize;
+}
+
+FfxFloat32x2 Jitter()
+{
+ return cbFSR3Upscaler.fJitter;
+}
+
+FfxFloat32x2 PreviousFrameJitter()
+{
+ return cbFSR3Upscaler.fPreviousFrameJitter;
+}
+
+FfxFloat32x4 DeviceToViewSpaceTransformFactors()
+{
+ return cbFSR3Upscaler.fDeviceToViewDepth;
+}
+
+FfxFloat32x2 MotionVectorScale()
+{
+ return cbFSR3Upscaler.fMotionVectorScale;
+}
+
+FfxFloat32x2 DownscaleFactor()
+{
+ return cbFSR3Upscaler.fDownscaleFactor;
+}
+
+FfxFloat32x2 MotionVectorJitterCancellation()
+{
+ return cbFSR3Upscaler.fMotionVectorJitterCancellation;
+}
+
+FfxFloat32 TanHalfFoV()
+{
+ return cbFSR3Upscaler.fTanHalfFOV;
+}
+
+FfxFloat32 JitterSequenceLength()
+{
+ return cbFSR3Upscaler.fJitterSequenceLength;
+}
+
+FfxFloat32 DeltaTime()
+{
+ return cbFSR3Upscaler.fDeltaTime;
+}
+
+FfxFloat32 DeltaPreExposure()
+{
+ return cbFSR3Upscaler.fDeltaPreExposure;
+}
+
+FfxFloat32 ViewSpaceToMetersFactor()
+{
+ return cbFSR3Upscaler.fViewSpaceToMetersFactor;
+}
+
+FfxFloat32 FrameIndex()
+{
+ return cbFSR3Upscaler.fFrameIndex;
+}
+
+FfxFloat32 VelocityFactor()
+{
+ return cbFSR3Upscaler.fVelocityFactor;
+}
+
+FfxFloat32 AccumulationAddedPerFrame()
+{
+ return cbFSR3Upscaler.fAccumulationAddedPerFrame;
+}
+
+FfxFloat32 MinDisocclusionAccumulation()
+{
+ return cbFSR3Upscaler.fMinDisocclusionAccumulation;
+}
+
+#endif // #if defined(FSR3UPSCALER_BIND_CB_FSR3UPSCALER)
+
+
+#if defined(FSR3UPSCALER_BIND_CB_AUTOREACTIVE)
+layout(set = 0, binding = FSR3UPSCALER_BIND_CB_AUTOREACTIVE, std140) uniform cbGenerateReactive_t
+{
+ FfxFloat32 fTcThreshold; // 0.1 is a good starting value, lower will result in more TC pixels
+ FfxFloat32 fTcScale;
+ FfxFloat32 fReactiveScale;
+ FfxFloat32 fReactiveMax;
+} cbGenerateReactive;
+
+FfxFloat32 TcThreshold()
+{
+ return cbGenerateReactive.fTcThreshold;
+}
+
+FfxFloat32 TcScale()
+{
+ return cbGenerateReactive.fTcScale;
+}
+
+FfxFloat32 ReactiveScale()
+{
+ return cbGenerateReactive.fReactiveScale;
+}
+
+FfxFloat32 ReactiveMax()
+{
+ return cbGenerateReactive.fReactiveMax;
+}
+#endif // #if defined(FSR3UPSCALER_BIND_CB_AUTOREACTIVE)
+
+#if defined(FSR3UPSCALER_BIND_CB_RCAS)
+layout(set = 0, binding = FSR3UPSCALER_BIND_CB_RCAS, std140) uniform cbRCAS_t
+{
+ FfxUInt32x4 rcasConfig;
+} cbRCAS;
+
+FfxUInt32x4 RCASConfig()
+{
+ return cbRCAS.rcasConfig;
+}
+#endif // #if defined(FSR3UPSCALER_BIND_CB_RCAS)
+
+
+#if defined(FSR3UPSCALER_BIND_CB_REACTIVE)
+layout(set = 0, binding = FSR3UPSCALER_BIND_CB_REACTIVE, std140) uniform cbGenerateReactive_t
+{
+ FfxFloat32 gen_reactive_scale;
+ FfxFloat32 gen_reactive_threshold;
+ FfxFloat32 gen_reactive_binaryValue;
+ FfxUInt32 gen_reactive_flags;
+} cbGenerateReactive;
+
+FfxFloat32 GenReactiveScale()
+{
+ return cbGenerateReactive.gen_reactive_scale;
+}
+
+FfxFloat32 GenReactiveThreshold()
+{
+ return cbGenerateReactive.gen_reactive_threshold;
+}
+
+FfxFloat32 GenReactiveBinaryValue()
+{
+ return cbGenerateReactive.gen_reactive_binaryValue;
+}
+
+FfxUInt32 GenReactiveFlags()
+{
+ return cbGenerateReactive.gen_reactive_flags;
+}
+#endif // #if defined(FSR3UPSCALER_BIND_CB_REACTIVE)
+
+
+#if defined(FSR3UPSCALER_BIND_CB_SPD)
+layout(set = 0, binding = FSR3UPSCALER_BIND_CB_SPD, std140) uniform cbSPD_t
+{
+ FfxUInt32 mips;
+ FfxUInt32 numWorkGroups;
+ FfxUInt32x2 workGroupOffset;
+ FfxUInt32x2 renderSize;
+} cbSPD;
+
+FfxUInt32 MipCount()
+{
+ return cbSPD.mips;
+}
+
+FfxUInt32 NumWorkGroups()
+{
+ return cbSPD.numWorkGroups;
+}
+
+FfxUInt32x2 WorkGroupOffset()
+{
+ return cbSPD.workGroupOffset;
+}
+
+FfxUInt32x2 SPD_RenderSize()
+{
+ return cbSPD.renderSize;
+}
+#endif // #if defined(FSR3UPSCALER_BIND_CB_SPD)
+
+layout (set = 0, binding = 1000) uniform sampler s_PointClamp;
+layout (set = 0, binding = 1001) uniform sampler s_LinearClamp;
+
+#if defined(FSR3UPSCALER_BIND_SRV_SPD_MIPS)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_SPD_MIPS) uniform texture2D r_spd_mips;
+
+FfxInt32x2 GetSPDMipDimensions(FfxUInt32 uMipLevel)
+{
+ return textureSize(r_spd_mips, int(uMipLevel)).xy;
+}
+
+FfxFloat32x2 SampleSPDMipLevel(FfxFloat32x2 fUV, FfxUInt32 mipLevel)
+{
+ return textureLod(sampler2D(r_spd_mips, s_LinearClamp), fUV, float(mipLevel)).rg;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_INPUT_DEPTH)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_INPUT_DEPTH) uniform texture2D r_input_depth;
+
+FfxFloat32 LoadInputDepth(FfxInt32x2 iPxPos)
+{
+ return texelFetch(r_input_depth, iPxPos, 0).r;
+}
+
+FfxFloat32 SampleInputDepth(FfxFloat32x2 fUV)
+{
+ return textureLod(sampler2D(r_input_depth, s_LinearClamp), fUV, 0.0).x;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_REACTIVE_MASK)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_REACTIVE_MASK) uniform texture2D r_reactive_mask;
+
+FfxFloat32 LoadReactiveMask(FfxInt32x2 iPxPos)
+{
+ return texelFetch(r_reactive_mask, FfxInt32x2(iPxPos), 0).r * cbFSR3Upscaler.fReactivenessScale;
+}
+
+FfxInt32x2 GetReactiveMaskResourceDimensions()
+{
+ return textureSize(r_reactive_mask, 0).xy;
+}
+
+FfxFloat32 SampleReactiveMask(FfxFloat32x2 fUV)
+{
+ return textureLod(sampler2D(r_reactive_mask, s_LinearClamp), fUV, 0.0).x * cbFSR3Upscaler.fReactivenessScale;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_TRANSPARENCY_AND_COMPOSITION_MASK)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_TRANSPARENCY_AND_COMPOSITION_MASK) uniform texture2D r_transparency_and_composition_mask;
+
+FfxFloat32 LoadTransparencyAndCompositionMask(FfxUInt32x2 iPxPos)
+{
+ return texelFetch(r_transparency_and_composition_mask, FfxInt32x2(iPxPos), 0).r;
+}
+
+FfxInt32x2 GetTransparencyAndCompositionMaskResourceDimensions()
+{
+ return textureSize(r_transparency_and_composition_mask, 0).xy;
+}
+
+FfxFloat32 SampleTransparencyAndCompositionMask(FfxFloat32x2 fUV)
+{
+ return textureLod(sampler2D(r_transparency_and_composition_mask, s_LinearClamp), fUV, 0.0).x;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_INPUT_COLOR)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_INPUT_COLOR) uniform texture2D r_input_color_jittered;
+
+FfxFloat32x3 LoadInputColor(FfxInt32x2 iPxPos)
+{
+ return texelFetch(r_input_color_jittered, iPxPos, 0).rgb;
+}
+
+FfxFloat32x3 SampleInputColor(FfxFloat32x2 fUV)
+{
+ return textureLod(sampler2D(r_input_color_jittered, s_LinearClamp), fUV, 0.0).rgb;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_INPUT_MOTION_VECTORS)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_INPUT_MOTION_VECTORS) uniform texture2D r_input_motion_vectors;
+
+FfxFloat32x2 LoadInputMotionVector(FfxInt32x2 iPxDilatedMotionVectorPos)
+{
+ FfxFloat32x2 fSrcMotionVector = texelFetch(r_input_motion_vectors, iPxDilatedMotionVectorPos, 0).xy;
+
+ FfxFloat32x2 fUvMotionVector = fSrcMotionVector * MotionVectorScale();
+
+#if FFX_FSR3UPSCALER_OPTION_JITTERED_MOTION_VECTORS
+ fUvMotionVector -= MotionVectorJitterCancellation();
+#endif
+
+ return fUvMotionVector;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_INTERNAL_UPSCALED)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_INTERNAL_UPSCALED) uniform texture2D r_internal_upscaled_color;
+
+FfxFloat32x4 LoadHistory(FfxInt32x2 iPxHistory)
+{
+ return texelFetch(r_internal_upscaled_color, iPxHistory, 0);
+}
+
+FfxFloat32x4 SampleHistory(FfxFloat32x2 fUV)
+{
+ return textureLod(sampler2D(r_internal_upscaled_color, s_LinearClamp), fUV, 0.0);
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_LUMA_HISTORY)
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_LUMA_HISTORY, rgba8) uniform image2D rw_luma_history;
+
+void StoreLumaHistory(FfxInt32x2 iPxPos, FfxFloat32x4 fLumaHistory)
+{
+ imageStore(rw_luma_history, iPxPos, fLumaHistory);
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_LUMA_HISTORY)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_LUMA_HISTORY) uniform texture2D r_luma_history;
+
+FfxFloat32x4 LoadLumaHistory(FfxInt32x2 iPxPos)
+{
+ return texelFetch(r_luma_history, iPxPos, 0);
+}
+
+FfxFloat32x4 SampleLumaHistory(FfxFloat32x2 fUV)
+{
+ return textureLod(sampler2D(r_luma_history, s_LinearClamp), fUV, 0.0);
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_RCAS_INPUT)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_RCAS_INPUT) uniform texture2D r_rcas_input;
+
+FfxFloat32x4 LoadRCAS_Input(FfxInt32x2 iPxPos)
+{
+ return texelFetch(r_rcas_input, iPxPos, 0);
+}
+
+FfxFloat32x3 SampleRCAS_Input(FfxFloat32x2 fUV)
+{
+ return textureLod(sampler2D(r_rcas_input, s_LinearClamp), fUV, 0.0).rgb;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_INTERNAL_UPSCALED)
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_INTERNAL_UPSCALED, rgba16f) writeonly uniform image2D rw_internal_upscaled_color;
+
+void StoreReprojectedHistory(FfxInt32x2 iPxHistory, FfxFloat32x4 fHistory)
+{
+ imageStore(rw_internal_upscaled_color, iPxHistory, fHistory);
+}
+
+void StoreInternalColorAndWeight(FfxInt32x2 iPxPos, FfxFloat32x4 fColorAndWeight)
+{
+ imageStore(rw_internal_upscaled_color, iPxPos, fColorAndWeight);
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_UPSCALED_OUTPUT)
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_UPSCALED_OUTPUT /* app controlled format */) writeonly uniform image2D rw_upscaled_output;
+
+void StoreUpscaledOutput(FfxInt32x2 iPxPos, FfxFloat32x3 fColor)
+{
+ imageStore(rw_upscaled_output, iPxPos, FfxFloat32x4(fColor, 1.0));
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_ACCUMULATION)
+layout(set = 0, binding = FSR3UPSCALER_BIND_SRV_ACCUMULATION) uniform texture2D r_accumulation;
+
+FfxFloat32 SampleAccumulation(FfxFloat32x2 fUV)
+{
+ return textureLod(sampler2D(r_accumulation, s_LinearClamp), fUV, 0.0).x;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_ACCUMULATION)
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_ACCUMULATION, r8) uniform image2D rw_accumulation;
+
+void StoreAccumulation(FfxInt32x2 iPxPos, FfxFloat32 fAccumulation)
+{
+ imageStore(rw_accumulation, iPxPos, vec4(fAccumulation, 0.0, 0.0, 0.0));
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_SHADING_CHANGE)
+layout(set = 0, binding = FSR3UPSCALER_BIND_SRV_SHADING_CHANGE) uniform texture2D r_shading_change;
+
+FfxFloat32 LoadShadingChange(FfxInt32x2 iPxPos)
+{
+ return texelFetch(r_shading_change, iPxPos, 0).x * cbFSR3Upscaler.fShadingChangeScale;
+}
+
+FfxFloat32 SampleShadingChange(FfxFloat32x2 fUV)
+{
+ return textureLod(sampler2D(r_shading_change, s_LinearClamp), fUV, 0.0).x * cbFSR3Upscaler.fShadingChangeScale;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_SHADING_CHANGE)
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_SHADING_CHANGE, r8) uniform image2D rw_shading_change;
+
+void StoreShadingChange(FfxInt32x2 iPxPos, FfxFloat32 fShadingChange)
+{
+ imageStore(rw_shading_change, iPxPos, vec4(fShadingChange, 0.0, 0.0, 0.0));
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_FARTHEST_DEPTH)
+layout(set = 0, binding = FSR3UPSCALER_BIND_SRV_FARTHEST_DEPTH) uniform texture2D r_farthest_depth;
+
+FfxInt32x2 GetFarthestDepthResourceDimensions()
+{
+ return textureSize(r_farthest_depth, 0).xy;
+}
+
+FfxFloat32 LoadFarthestDepth(FfxInt32x2 iPxPos)
+{
+ return texelFetch(r_farthest_depth, iPxPos, 0).x;
+}
+
+FfxFloat32 SampleFarthestDepth(FfxFloat32x2 fUV)
+{
+ return textureLod(sampler2D(r_farthest_depth, s_LinearClamp), fUV, 0.0).x;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_FARTHEST_DEPTH)
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_FARTHEST_DEPTH, r16f) uniform image2D rw_farthest_depth;
+
+void StoreFarthestDepth(FfxInt32x2 iPxPos, FfxFloat32 fDepth)
+{
+ imageStore(rw_farthest_depth, iPxPos, vec4(fDepth, 0.0, 0.0, 0.0));
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_FARTHEST_DEPTH_MIP1)
+layout(set = 0, binding = FSR3UPSCALER_BIND_SRV_FARTHEST_DEPTH_MIP1) uniform texture2D r_farthest_depth_mip1;
+
+FfxInt32x2 GetFarthestDepthMip1ResourceDimensions()
+{
+ return textureSize(r_farthest_depth_mip1, 0).xy;
+}
+
+FfxFloat32 LoadFarthestDepthMip1(FfxInt32x2 iPxPos)
+{
+ return texelFetch(r_farthest_depth_mip1, iPxPos, 0).x;
+}
+
+FfxFloat32 SampleFarthestDepthMip1(FfxFloat32x2 fUV)
+{
+ return textureLod(sampler2D(r_farthest_depth_mip1, s_LinearClamp), fUV, 0.0).x;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_FARTHEST_DEPTH_MIP1)
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_FARTHEST_DEPTH_MIP1, r16f) uniform image2D rw_farthest_depth_mip1;
+
+void StoreFarthestDepthMip1(FfxInt32x2 iPxPos, FfxFloat32 fDepth)
+{
+ imageStore(rw_farthest_depth_mip1, iPxPos, vec4(fDepth, 0.0, 0.0, 0.0));
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_CURRENT_LUMA)
+layout(set = 0, binding = FSR3UPSCALER_BIND_SRV_CURRENT_LUMA) uniform texture2D r_current_luma;
+
+FfxFloat32 LoadCurrentLuma(FfxInt32x2 iPxPos)
+{
+ return texelFetch(r_current_luma, iPxPos, 0).r;
+}
+
+FfxFloat32 SampleCurrentLuma(FfxFloat32x2 uv)
+{
+ return textureLod(sampler2D(r_current_luma, s_LinearClamp), uv, 0.0).r;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_CURRENT_LUMA)
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_CURRENT_LUMA, r16f) uniform image2D rw_current_luma;
+
+void StoreCurrentLuma(FfxInt32x2 iPxPos, FfxFloat32 fLuma)
+{
+ imageStore(rw_current_luma, iPxPos, vec4(fLuma, 0.0, 0.0, 0.0));
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_LUMA_INSTABILITY)
+layout(set = 0, binding = FSR3UPSCALER_BIND_SRV_LUMA_INSTABILITY) uniform texture2D r_luma_instability;
+
+FfxFloat32 SampleLumaInstability(FfxFloat32x2 uv)
+{
+ return textureLod(sampler2D(r_luma_instability, s_LinearClamp), uv, 0.0).x;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_LUMA_INSTABILITY)
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_LUMA_INSTABILITY, r16f) uniform image2D rw_luma_instability;
+
+void StoreLumaInstability(FfxInt32x2 iPxPos, FfxFloat32 fLumaInstability)
+{
+ imageStore(rw_luma_instability, iPxPos, vec4(fLumaInstability, 0.0, 0.0, 0.0));
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_PREVIOUS_LUMA)
+layout(set = 0, binding = FSR3UPSCALER_BIND_SRV_PREVIOUS_LUMA) uniform texture2D r_previous_luma;
+
+FfxFloat32 LoadPreviousLuma(FfxInt32x2 iPxPos)
+{
+ return texelFetch(r_previous_luma, iPxPos, 0).r;
+}
+
+FfxFloat32 SamplePreviousLuma(FfxFloat32x2 uv)
+{
+ return textureLod(sampler2D(r_previous_luma, s_LinearClamp), uv, 0.0).r;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_NEW_LOCKS)
+layout(set = 0, binding = FSR3UPSCALER_BIND_SRV_NEW_LOCKS) uniform texture2D r_new_locks;
+
+FfxFloat32 LoadNewLocks(FfxInt32x2 iPxPos)
+{
+ return texelFetch(r_new_locks, iPxPos, 0).r;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_NEW_LOCKS)
+layout(set = 0, binding = FSR3UPSCALER_BIND_UAV_NEW_LOCKS, r8) uniform image2D rw_new_locks;
+
+FfxFloat32 LoadRwNewLocks(FfxInt32x2 iPxPos)
+{
+ return imageLoad(rw_new_locks, iPxPos).r;
+}
+
+void StoreNewLocks(FfxInt32x2 iPxPos, FfxFloat32 newLock)
+{
+ imageStore(rw_new_locks, iPxPos, vec4(newLock, 0, 0, 0));
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_RECONSTRUCTED_PREV_NEAREST_DEPTH)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_RECONSTRUCTED_PREV_NEAREST_DEPTH) uniform utexture2D r_reconstructed_previous_nearest_depth;
+
+FfxFloat32 LoadReconstructedPrevDepth(FfxInt32x2 iPxPos)
+{
+ return uintBitsToFloat(texelFetch(r_reconstructed_previous_nearest_depth, iPxPos, 0).r);
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_RECONSTRUCTED_PREV_NEAREST_DEPTH)
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_RECONSTRUCTED_PREV_NEAREST_DEPTH, r32ui) uniform uimage2D rw_reconstructed_previous_nearest_depth;
+
+void StoreReconstructedDepth(FfxInt32x2 iPxSample, FfxFloat32 fDepth)
+{
+ FfxUInt32 uDepth = floatBitsToUint(fDepth);
+
+ #if FFX_FSR3UPSCALER_OPTION_INVERTED_DEPTH
+ imageAtomicMax(rw_reconstructed_previous_nearest_depth, iPxSample, uDepth);
+ #else
+ imageAtomicMin(rw_reconstructed_previous_nearest_depth, iPxSample, uDepth); // min for standard, max for inverted depth
+ #endif
+}
+
+void SetReconstructedDepth(FfxInt32x2 iPxSample, FfxUInt32 uValue)
+{
+ imageStore(rw_reconstructed_previous_nearest_depth, iPxSample, uvec4(uValue, 0, 0, 0));
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_DILATED_DEPTH)
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_DILATED_DEPTH, r32f) writeonly uniform image2D rw_dilated_depth;
+
+void StoreDilatedDepth(FFX_PARAMETER_IN FfxInt32x2 iPxPos, FFX_PARAMETER_IN FfxFloat32 fDepth)
+{
+ imageStore(rw_dilated_depth, iPxPos, vec4(fDepth, 0.0, 0.0, 0.0));
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_DILATED_MOTION_VECTORS)
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_DILATED_MOTION_VECTORS, rg16f) writeonly uniform image2D rw_dilated_motion_vectors;
+
+void StoreDilatedMotionVector(FFX_PARAMETER_IN FfxInt32x2 iPxPos, FFX_PARAMETER_IN FfxFloat32x2 fMotionVector)
+{
+ imageStore(rw_dilated_motion_vectors, iPxPos, vec4(fMotionVector, 0.0, 0.0));
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_DILATED_MOTION_VECTORS)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_DILATED_MOTION_VECTORS) uniform texture2D r_dilated_motion_vectors;
+
+FfxFloat32x2 LoadDilatedMotionVector(FfxInt32x2 iPxInput)
+{
+ return texelFetch(r_dilated_motion_vectors, iPxInput, 0).xy;
+}
+
+FfxFloat32x2 SampleDilatedMotionVector(FfxFloat32x2 fUV)
+{
+ return textureLod(sampler2D(r_dilated_motion_vectors, s_LinearClamp), fUV, 0.0).xy;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_DILATED_DEPTH)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_DILATED_DEPTH) uniform texture2D r_dilated_depth;
+
+FfxFloat32 LoadDilatedDepth(FfxInt32x2 iPxInput)
+{
+ return texelFetch(r_dilated_depth, iPxInput, 0).r;
+}
+
+FfxFloat32 SampleDilatedDepth(FfxFloat32x2 fUV)
+{
+ return textureLod(sampler2D(r_dilated_depth, s_LinearClamp), fUV, 0.0).r;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_INPUT_EXPOSURE)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_INPUT_EXPOSURE) uniform texture2D r_input_exposure;
+
+FfxFloat32 Exposure()
+{
+ FfxFloat32 exposure = texelFetch(r_input_exposure, FfxInt32x2(0, 0), 0).x;
+
+ if (exposure == 0.0) {
+ exposure = 1.0;
+ }
+
+ return exposure;
+}
+#endif
+
+// BEGIN: FSR3UPSCALER_BIND_SRV_LANCZOS_LUT
+#if defined(FSR3UPSCALER_BIND_SRV_LANCZOS_LUT)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_LANCZOS_LUT) uniform texture2D r_lanczos_lut;
+#endif
+
+FfxFloat32 SampleLanczos2Weight(FfxFloat32 x)
+{
+#if defined(FSR3UPSCALER_BIND_SRV_LANCZOS_LUT)
+ return textureLod(sampler2D(r_lanczos_lut, s_LinearClamp), FfxFloat32x2(x / 2.0, 0.5), 0.0).x;
+#else
+ return 0.f;
+#endif
+}
+// END: FSR3UPSCALER_BIND_SRV_LANCZOS_LUT
+
+#if defined(FSR3UPSCALER_BIND_SRV_DILATED_REACTIVE_MASKS)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_DILATED_REACTIVE_MASKS) uniform texture2D r_dilated_reactive_masks;
+
+FfxFloat32x4 SampleDilatedReactiveMasks(FfxFloat32x2 fUV)
+{
+ return textureLod(sampler2D(r_dilated_reactive_masks, s_LinearClamp), fUV, 0.0);
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_DILATED_REACTIVE_MASKS)
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_DILATED_REACTIVE_MASKS, rgba8) writeonly uniform image2D rw_dilated_reactive_masks;
+
+void StoreDilatedReactiveMasks(FFX_PARAMETER_IN FfxInt32x2 iPxPos, FFX_PARAMETER_IN FfxFloat32x4 fDilatedReactiveMasks)
+{
+ imageStore(rw_dilated_reactive_masks, iPxPos, fDilatedReactiveMasks);
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_INPUT_OPAQUE_ONLY)
+layout (set = 0, binding = FSR3UPSCALER_BIND_SRV_INPUT_OPAQUE_ONLY) uniform texture2D r_input_opaque_only;
+
+FfxFloat32x3 LoadOpaqueOnly(FFX_PARAMETER_IN FFX_MIN16_I2 iPxPos)
+{
+ return texelFetch(r_input_opaque_only, iPxPos, 0).xyz;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_PREV_PRE_ALPHA_COLOR)
+layout(set = 0, binding = FSR3UPSCALER_BIND_SRV_PREV_PRE_ALPHA_COLOR) uniform texture2D r_input_prev_color_pre_alpha;
+
+FfxFloat32x3 LoadPrevPreAlpha(FFX_PARAMETER_IN FFX_MIN16_I2 iPxPos)
+{
+ return texelFetch(r_input_prev_color_pre_alpha, iPxPos, 0).xyz;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_PREV_POST_ALPHA_COLOR)
+layout(set = 0, binding = FSR3UPSCALER_BIND_SRV_PREV_POST_ALPHA_COLOR) uniform texture2D r_input_prev_color_post_alpha;
+
+FfxFloat32x3 LoadPrevPostAlpha(FFX_PARAMETER_IN FFX_MIN16_I2 iPxPos)
+{
+ return texelFetch(r_input_prev_color_post_alpha, iPxPos, 0).xyz;
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_AUTOREACTIVE) && \
+ defined(FSR3UPSCALER_BIND_UAV_AUTOCOMPOSITION)
+
+layout(set = 0, binding = FSR3UPSCALER_BIND_UAV_AUTOREACTIVE, r32f) uniform image2D rw_output_autoreactive;
+layout(set = 0, binding = FSR3UPSCALER_BIND_UAV_AUTOCOMPOSITION, r32f) uniform image2D rw_output_autocomposition;
+
+void StoreAutoReactive(FFX_PARAMETER_IN FFX_MIN16_I2 iPxPos, FFX_PARAMETER_IN FFX_MIN16_F2 fReactive)
+{
+ imageStore(rw_output_autoreactive, iPxPos, FfxFloat32x4(FfxFloat32(fReactive.x), 0.0, 0.0, 0.0));
+
+ imageStore(rw_output_autocomposition, iPxPos, FfxFloat32x4(FfxFloat32(fReactive.y), 0.0, 0.0, 0.0));
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_PREV_PRE_ALPHA_COLOR)
+layout(set = 0, binding = FSR3UPSCALER_BIND_UAV_PREV_PRE_ALPHA_COLOR, r11f_g11f_b10f) uniform image2D rw_output_prev_color_pre_alpha;
+
+void StorePrevPreAlpha(FFX_PARAMETER_IN FFX_MIN16_I2 iPxPos, FFX_PARAMETER_IN FFX_MIN16_F3 color)
+{
+ imageStore(rw_output_prev_color_pre_alpha, iPxPos, FfxFloat32x4(color, 0.0));
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_PREV_POST_ALPHA_COLOR)
+layout(set = 0, binding = FSR3UPSCALER_BIND_UAV_PREV_POST_ALPHA_COLOR, r11f_g11f_b10f) uniform image2D rw_output_prev_color_post_alpha;
+
+void StorePrevPostAlpha(FFX_PARAMETER_IN FFX_MIN16_I2 iPxPos, FFX_PARAMETER_IN FFX_MIN16_F3 color)
+{
+ imageStore(rw_output_prev_color_post_alpha, iPxPos, FfxFloat32x4(color, 0.0));
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_FRAME_INFO)
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_FRAME_INFO, rgba32f) uniform image2D rw_frame_info;
+
+FfxFloat32x4 LoadFrameInfo()
+{
+ return imageLoad(rw_frame_info, ivec2(0, 0));
+}
+
+void StoreFrameInfo(FfxFloat32x4 fInfo)
+{
+ imageStore(rw_frame_info, ivec2(0, 0), fInfo);
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_SRV_FRAME_INFO)
+layout(set = 0, binding = FSR3UPSCALER_BIND_SRV_FRAME_INFO) uniform texture2D r_frame_info;
+
+FfxFloat32x4 FrameInfo()
+{
+ return texelFetch(r_frame_info, ivec2(0, 0), 0);
+}
+#endif
+
+#if defined(FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_0) && \
+ defined(FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_1) && \
+ defined(FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_2) && \
+ defined(FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_3) && \
+ defined(FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_4) && \
+ defined(FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_5)
+
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_0, rg16f) uniform image2D rw_spd_mip0;
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_1, rg16f) uniform image2D rw_spd_mip1;
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_2, rg16f) uniform image2D rw_spd_mip2;
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_3, rg16f) uniform image2D rw_spd_mip3;
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_4, rg16f) uniform image2D rw_spd_mip4;
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_5, rg16f) coherent uniform image2D rw_spd_mip5;
+
+FfxFloat32x2 RWLoadPyramid(FFX_PARAMETER_IN FfxInt32x2 iPxPos, FFX_PARAMETER_IN FfxUInt32 index)
+{
+#define LOAD(idx) \
+ if (index == idx) \
+ { \
+ return imageLoad(rw_spd_mip##idx, iPxPos).xy; \
+ }
+ LOAD(0);
+ LOAD(1);
+ LOAD(2);
+ LOAD(3);
+ LOAD(4);
+ LOAD(5);
+
+ return FfxFloat32x2(0.0, 0.0);
+
+#undef LOAD
+}
+
+void StorePyramid(FFX_PARAMETER_IN FfxInt32x2 iPxPos, FFX_PARAMETER_IN FfxFloat32x2 outValue, FFX_PARAMETER_IN FfxUInt32 index)
+{
+#define STORE(idx) \
+ if (index == idx) \
+ { \
+ imageStore(rw_spd_mip##idx, iPxPos, vec4(outValue, 0.0, 0.0)); \
+ }
+
+ STORE(0);
+ STORE(1);
+ STORE(2);
+ STORE(3);
+ STORE(4);
+ STORE(5);
+
+#undef STORE
+}
+#endif
+
+#if defined FSR3UPSCALER_BIND_UAV_SPD_GLOBAL_ATOMIC
+layout (set = 0, binding = FSR3UPSCALER_BIND_UAV_SPD_GLOBAL_ATOMIC, r32ui) coherent uniform uimage2D rw_spd_global_atomic;
+
+void SPD_IncreaseAtomicCounter(inout FfxUInt32 spdCounter)
+{
+ spdCounter = imageAtomicAdd(rw_spd_global_atomic, ivec2(0, 0), 1);
+}
+
+void SPD_ResetAtomicCounter()
+{
+ imageStore(rw_spd_global_atomic, ivec2(0, 0), uvec4(0));
+}
+#endif
+
+#endif // #if defined(FFX_GPU)
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_common.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_common.h
new file mode 100644
index 0000000000..87aa596db4
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_common.h
@@ -0,0 +1,403 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#if !defined(FFX_FSR3UPSCALER_COMMON_H)
+#define FFX_FSR3UPSCALER_COMMON_H
+
+#if defined(FFX_GPU)
+#pragma warning(error : 3206) // treat vector truncation warnings as errors
+#pragma warning(disable : 3205) // conversion from larger type to smaller
+#pragma warning(disable : 3571) // in ffxPow(f, e), f could be negative
+
+FFX_STATIC const FfxFloat32 FSR3UPSCALER_FP16_MIN = 6.10e-05f;
+FFX_STATIC const FfxFloat32 FSR3UPSCALER_FP16_MAX = 65504.0f;
+FFX_STATIC const FfxFloat32 FSR3UPSCALER_EPSILON = FSR3UPSCALER_FP16_MIN;
+FFX_STATIC const FfxFloat32 FSR3UPSCALER_TONEMAP_EPSILON = FSR3UPSCALER_FP16_MIN;
+FFX_STATIC const FfxFloat32 FSR3UPSCALER_FP32_MAX = 3.402823466e+38f;
+FFX_STATIC const FfxFloat32 FSR3UPSCALER_FP32_MIN = 1.175494351e-38f;
+
+// Reconstructed depth usage
+FFX_STATIC const FfxFloat32 fReconstructedDepthBilinearWeightThreshold = FSR3UPSCALER_EPSILON * 10;
+
+FfxFloat32 ReconstructedDepthMvPxThreshold(FfxFloat32 fNearestDepthInMeters)
+{
+ return ffxLerp(0.25f, 0.75f, ffxSaturate(fNearestDepthInMeters / 100.0f));
+}
+
+// Accumulation
+FFX_STATIC const FfxFloat32 fUpsampleLanczosWeightScale = 1.0f / 16.0f;
+FFX_STATIC const FfxFloat32 fAverageLanczosWeightPerFrame = 0.74f * fUpsampleLanczosWeightScale; // Average lanczos weight for jitter accumulated samples
+FFX_STATIC const FfxFloat32 fAccumulationMaxOnMotion = 3.0f * fUpsampleLanczosWeightScale;
+
+#define SHADING_CHANGE_SET_SIZE 5
+FFX_STATIC const FfxInt32 iShadingChangeMipStart = 0;
+FFX_STATIC const FfxFloat32 fShadingChangeSamplePow = 1.0f / 1.0f;
+
+
+FFX_STATIC const FfxFloat32 fLockThreshold = 1.0f;
+FFX_STATIC const FfxFloat32 fLockMax = 2.0f;
+
+FFX_STATIC const FfxInt32 REACTIVE = 0;
+FFX_STATIC const FfxInt32 DISOCCLUSION = 1;
+FFX_STATIC const FfxInt32 SHADING_CHANGE = 2;
+FFX_STATIC const FfxInt32 ACCUMULAION = 3;
+
+FFX_STATIC const FfxInt32 FRAME_INFO_EXPOSURE = 0;
+FFX_STATIC const FfxInt32 FRAME_INFO_LOG_LUMA = 1;
+FFX_STATIC const FfxInt32 FRAME_INFO_SCENE_AVERAGE_LUMA = 2;
+
+FfxBoolean TonemapFirstFrame()
+{
+ const FfxBoolean bEnabled = true;
+ return FrameIndex() == 0 && bEnabled;
+}
+
+FfxFloat32 AverageLanczosWeightPerFrame()
+{
+ return 0.74f;
+}
+
+FfxInt32x2 ShadingChangeRenderSize()
+{
+ return FfxInt32x2(RenderSize() * 0.5f);
+}
+
+FfxInt32x2 ShadingChangeMaxRenderSize()
+{
+ return FfxInt32x2(MaxRenderSize() * 0.5f);
+}
+
+FfxInt32x2 PreviousFrameShadingChangeRenderSize()
+{
+ return FfxInt32x2(PreviousFrameRenderSize() * 0.5f);
+}
+
+#if defined(FSR3UPSCALER_BIND_SRV_FRAME_INFO)
+FfxFloat32 SceneAverageLuma()
+{
+ return FrameInfo()[FRAME_INFO_SCENE_AVERAGE_LUMA];
+}
+#endif
+
+// Auto exposure
+FFX_STATIC const FfxFloat32 resetAutoExposureAverageSmoothing = 1e4f;
+
+struct AccumulationPassCommonParams
+{
+ FfxInt32x2 iPxHrPos;
+ FfxFloat32x2 fHrUv;
+ FfxFloat32x2 fLrUvJittered;
+ FfxFloat32x2 fLrUv_HwSampler;
+ FfxFloat32x2 fMotionVector;
+ FfxFloat32x2 fReprojectedHrUv;
+ FfxFloat32 f4KVelocity;
+ FfxFloat32 fDisocclusion;
+ FfxFloat32 fReactiveMask;
+ FfxFloat32 fShadingChange;
+ FfxFloat32 fAccumulation;
+ FfxFloat32 fLumaInstabilityFactor;
+ FfxFloat32 fFarthestDepthInMeters;
+
+ FfxBoolean bIsExistingSample;
+ FfxBoolean bIsNewSample;
+};
+
+FfxFloat32 Get4KVelocity(FfxFloat32x2 fMotionVector)
+{
+ return length(fMotionVector * FfxFloat32x2(3840.0f, 2160.0f));
+}
+
+struct RectificationBox
+{
+ FfxFloat32x3 boxCenter;
+ FfxFloat32x3 boxVec;
+ FfxFloat32x3 aabbMin;
+ FfxFloat32x3 aabbMax;
+ FfxFloat32 fBoxCenterWeight;
+};
+
+struct AccumulationPassData
+{
+ RectificationBox clippingBox;
+ FfxFloat32x3 fUpsampledColor;
+ FfxFloat32 fUpsampledWeight;
+ FfxFloat32x3 fHistoryColor;
+ FfxFloat32 fHistoryWeight;
+ FfxFloat32 fLock;
+ FfxFloat32 fLockContributionThisFrame;
+};
+
+void RectificationBoxAddInitialSample(FFX_PARAMETER_INOUT RectificationBox rectificationBox, const FfxFloat32x3 colorSample, const FfxFloat32 fSampleWeight)
+{
+ rectificationBox.aabbMin = colorSample;
+ rectificationBox.aabbMax = colorSample;
+
+ FfxFloat32x3 weightedSample = colorSample * fSampleWeight;
+ rectificationBox.boxCenter = weightedSample;
+ rectificationBox.boxVec = colorSample * weightedSample;
+ rectificationBox.fBoxCenterWeight = fSampleWeight;
+}
+
+void RectificationBoxAddSample(FfxBoolean bInitialSample, FFX_PARAMETER_INOUT RectificationBox rectificationBox, const FfxFloat32x3 colorSample, const FfxFloat32 fSampleWeight)
+{
+ if (bInitialSample) {
+ RectificationBoxAddInitialSample(rectificationBox, colorSample, fSampleWeight);
+ } else {
+ rectificationBox.aabbMin = ffxMin(rectificationBox.aabbMin, colorSample);
+ rectificationBox.aabbMax = ffxMax(rectificationBox.aabbMax, colorSample);
+
+ FfxFloat32x3 weightedSample = colorSample * fSampleWeight;
+ rectificationBox.boxCenter += weightedSample;
+ rectificationBox.boxVec += colorSample * weightedSample;
+ rectificationBox.fBoxCenterWeight += fSampleWeight;
+ }
+}
+
+void RectificationBoxComputeVarianceBoxData(FFX_PARAMETER_INOUT RectificationBox rectificationBox)
+{
+ rectificationBox.fBoxCenterWeight = (abs(rectificationBox.fBoxCenterWeight) > FfxFloat32(FSR3UPSCALER_FP32_MIN) ? rectificationBox.fBoxCenterWeight : FfxFloat32(1.f));
+ rectificationBox.boxCenter /= rectificationBox.fBoxCenterWeight;
+ rectificationBox.boxVec /= rectificationBox.fBoxCenterWeight;
+ FfxFloat32x3 stdDev = sqrt(abs(rectificationBox.boxVec - rectificationBox.boxCenter * rectificationBox.boxCenter));
+ rectificationBox.boxVec = stdDev;
+}
+
+FfxFloat32x3 SafeRcp3(FfxFloat32x3 v)
+{
+ return (all(FFX_NOT_EQUAL(v, FfxFloat32x3(0, 0, 0)))) ? (FfxFloat32x3(1, 1, 1) / v) : FfxFloat32x3(0, 0, 0);
+}
+
+FfxFloat32 MinDividedByMax(const FfxFloat32 v0, const FfxFloat32 v1, const FfxFloat32 fOnZeroReturnValue)
+{
+ const FfxFloat32 m = ffxMax(v0, v1);
+ return m != 0 ? ffxMin(v0, v1) / m : fOnZeroReturnValue;
+}
+
+FfxFloat32 MinDividedByMax(const FfxFloat32 v0, const FfxFloat32 v1)
+{
+ const FfxFloat32 m = ffxMax(v0, v1);
+ return m != 0 ? ffxMin(v0, v1) / m : 0;
+}
+
+FfxFloat32x3 YCoCgToRGB(FfxFloat32x3 fYCoCg)
+{
+ FfxFloat32x3 fRgb;
+
+ fRgb = FfxFloat32x3(
+ fYCoCg.x + fYCoCg.y - fYCoCg.z,
+ fYCoCg.x + fYCoCg.z,
+ fYCoCg.x - fYCoCg.y - fYCoCg.z);
+
+ return fRgb;
+}
+
+FfxFloat32x3 RGBToYCoCg(FfxFloat32x3 fRgb)
+{
+ FfxFloat32x3 fYCoCg;
+
+ fYCoCg = FfxFloat32x3(
+ 0.25f * fRgb.r + 0.5f * fRgb.g + 0.25f * fRgb.b,
+ 0.5f * fRgb.r - 0.5f * fRgb.b,
+ -0.25f * fRgb.r + 0.5f * fRgb.g - 0.25f * fRgb.b);
+
+ return fYCoCg;
+}
+
+FfxFloat32 RGBToLuma(FfxFloat32x3 fLinearRgb)
+{
+ return dot(fLinearRgb, FfxFloat32x3(0.2126f, 0.7152f, 0.0722f));
+}
+
+FfxFloat32 RGBToPerceivedLuma(FfxFloat32x3 fLinearRgb)
+{
+ FfxFloat32 fLuminance = RGBToLuma(fLinearRgb);
+
+ FfxFloat32 fPercievedLuminance = 0;
+ if (fLuminance <= 216.0f / 24389.0f) {
+ fPercievedLuminance = fLuminance * (24389.0f / 27.0f);
+ }
+ else {
+ fPercievedLuminance = ffxPow(fLuminance, 1.0f / 3.0f) * 116.0f - 16.0f;
+ }
+
+ return fPercievedLuminance * 0.01f;
+}
+
+FfxFloat32x3 Tonemap(FfxFloat32x3 fRgb)
+{
+ return fRgb / (ffxMax(ffxMax(0.f, fRgb.r), ffxMax(fRgb.g, fRgb.b)) + 1.f).xxx;
+}
+
+FfxFloat32x3 InverseTonemap(FfxFloat32x3 fRgb)
+{
+ return fRgb / ffxMax(FSR3UPSCALER_TONEMAP_EPSILON, 1.f - ffxMax(fRgb.r, ffxMax(fRgb.g, fRgb.b))).xxx;
+}
+
+FfxBoolean IsUvInside(FfxFloat32x2 fUv)
+{
+ return (fUv.x >= 0.0f && fUv.x <= 1.0f) && (fUv.y >= 0.0f && fUv.y <= 1.0f);
+}
+
+FfxInt32x2 ClampLoad(FfxInt32x2 iPxSample, FfxInt32x2 iPxOffset, FfxInt32x2 iTextureSize)
+{
+ FfxInt32x2 result = iPxSample + iPxOffset;
+ result.x = ffxMax(0, ffxMin(result.x, iTextureSize.x - 1));
+ result.y = ffxMax(0, ffxMin(result.y, iTextureSize.y - 1));
+ return result;
+}
+
+FfxFloat32x2 ClampUv(FfxFloat32x2 fUv, FfxInt32x2 iTextureSize, FfxInt32x2 iResourceSize)
+{
+ const FfxFloat32x2 fSampleLocation = fUv * iTextureSize;
+ const FfxFloat32x2 fClampedLocation = ffxMax(FfxFloat32x2(0.5f, 0.5f), ffxMin(fSampleLocation, FfxFloat32x2(iTextureSize) - FfxFloat32x2(0.5f, 0.5f)));
+ const FfxFloat32x2 fClampedUv = fClampedLocation / FfxFloat32x2(iResourceSize);
+
+ return fClampedUv;
+}
+
+FfxBoolean IsOnScreen(FfxInt32x2 pos, FfxInt32x2 size)
+{
+ return all(FFX_LESS_THAN(FfxUInt32x2(pos), FfxUInt32x2(size)));
+}
+
+FfxFloat32 ComputeAutoExposureFromLavg(FfxFloat32 Lavg)
+{
+ Lavg = exp(Lavg);
+
+ const FfxFloat32 S = 100.0f; //ISO arithmetic speed
+ const FfxFloat32 K = 12.5f;
+ FfxFloat32 ExposureISO100 = log2((Lavg * S) / K);
+
+ const FfxFloat32 q = 0.65f;
+ FfxFloat32 Lmax = (78.0f / (q * S)) * ffxPow(2.0f, ExposureISO100);
+
+ return 1.0f / Lmax;
+}
+
+FfxInt32x2 ComputeHrPosFromLrPos(FfxInt32x2 iPxLrPos)
+{
+ FfxFloat32x2 fSrcJitteredPos = FfxFloat32x2(iPxLrPos) + 0.5f - Jitter();
+ FfxFloat32x2 fLrPosInHr = (fSrcJitteredPos / RenderSize()) * UpscaleSize();
+ FfxInt32x2 iPxHrPos = FfxInt32x2(floor(fLrPosInHr));
+ return iPxHrPos;
+}
+
+FfxFloat32x2 ComputeNdc(FfxFloat32x2 fPxPos, FfxInt32x2 iSize)
+{
+ return fPxPos / FfxFloat32x2(iSize) * FfxFloat32x2(2.0f, -2.0f) + FfxFloat32x2(-1.0f, 1.0f);
+}
+
+FfxFloat32 GetViewSpaceDepth(FfxFloat32 fDeviceDepth)
+{
+ const FfxFloat32x4 fDeviceToViewDepth = DeviceToViewSpaceTransformFactors();
+
+ // fDeviceToViewDepth details found in ffx_fsr3upscaler.cpp
+ return (fDeviceToViewDepth[1] / (fDeviceDepth - fDeviceToViewDepth[0]));
+}
+
+FfxFloat32 GetViewSpaceDepthInMeters(FfxFloat32 fDeviceDepth)
+{
+ return GetViewSpaceDepth(fDeviceDepth) * ViewSpaceToMetersFactor();
+}
+
+FfxFloat32x3 GetViewSpacePosition(FfxInt32x2 iViewportPos, FfxInt32x2 iViewportSize, FfxFloat32 fDeviceDepth)
+{
+ const FfxFloat32x4 fDeviceToViewDepth = DeviceToViewSpaceTransformFactors();
+
+ const FfxFloat32 Z = GetViewSpaceDepth(fDeviceDepth);
+
+ const FfxFloat32x2 fNdcPos = ComputeNdc(iViewportPos, iViewportSize);
+ const FfxFloat32 X = fDeviceToViewDepth[2] * fNdcPos.x * Z;
+ const FfxFloat32 Y = fDeviceToViewDepth[3] * fNdcPos.y * Z;
+
+ return FfxFloat32x3(X, Y, Z);
+}
+
+FfxFloat32x3 GetViewSpacePositionInMeters(FfxInt32x2 iViewportPos, FfxInt32x2 iViewportSize, FfxFloat32 fDeviceDepth)
+{
+ return GetViewSpacePosition(iViewportPos, iViewportSize, fDeviceDepth) * ViewSpaceToMetersFactor();
+}
+
+FfxFloat32 GetMaxDistanceInMeters()
+{
+#if FFX_FSR3UPSCALER_OPTION_INVERTED_DEPTH
+ return GetViewSpaceDepth(0.0f) * ViewSpaceToMetersFactor();
+#else
+ return GetViewSpaceDepth(1.0f) * ViewSpaceToMetersFactor();
+#endif
+}
+
+
+struct BilinearSamplingData
+{
+ FfxInt32x2 iOffsets[4];
+ FfxFloat32 fWeights[4];
+ FfxInt32x2 iBasePos;
+};
+
+BilinearSamplingData GetBilinearSamplingData(FfxFloat32x2 fUv, FfxInt32x2 iSize)
+{
+ BilinearSamplingData data;
+
+ FfxFloat32x2 fPxSample = (fUv * iSize) - FfxFloat32x2(0.5f, 0.5f);
+ data.iBasePos = FfxInt32x2(floor(fPxSample));
+ FfxFloat32x2 fPxFrac = ffxFract(fPxSample);
+
+ data.iOffsets[0] = FfxInt32x2(0, 0);
+ data.iOffsets[1] = FfxInt32x2(1, 0);
+ data.iOffsets[2] = FfxInt32x2(0, 1);
+ data.iOffsets[3] = FfxInt32x2(1, 1);
+
+ data.fWeights[0] = (1 - fPxFrac.x) * (1 - fPxFrac.y);
+ data.fWeights[1] = (fPxFrac.x) * (1 - fPxFrac.y);
+ data.fWeights[2] = (1 - fPxFrac.x) * (fPxFrac.y);
+ data.fWeights[3] = (fPxFrac.x) * (fPxFrac.y);
+
+ return data;
+}
+
+struct PlaneData
+{
+ FfxFloat32x3 fNormal;
+ FfxFloat32 fDistanceFromOrigin;
+};
+
+PlaneData GetPlaneFromPoints(FfxFloat32x3 fP0, FfxFloat32x3 fP1, FfxFloat32x3 fP2)
+{
+ PlaneData plane;
+
+ FfxFloat32x3 v0 = fP0 - fP1;
+ FfxFloat32x3 v1 = fP0 - fP2;
+ plane.fNormal = normalize(cross(v0, v1));
+ plane.fDistanceFromOrigin = -dot(fP0, plane.fNormal);
+
+ return plane;
+}
+
+FfxFloat32 PointToPlaneDistance(PlaneData plane, FfxFloat32x3 fPoint)
+{
+ return abs(dot(plane.fNormal, fPoint) + plane.fDistanceFromOrigin);
+}
+
+#endif // #if defined(FFX_GPU)
+
+#endif //!defined(FFX_FSR3UPSCALER_COMMON_H)
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_luma_instability.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_luma_instability.h
new file mode 100644
index 0000000000..624b7ca941
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_luma_instability.h
@@ -0,0 +1,115 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+struct LumaInstabilityFactorData
+{
+ FfxFloat32x4 fLumaHistory;
+ FfxFloat32 fLumaInstabilityFactor;
+};
+
+LumaInstabilityFactorData ComputeLumaInstabilityFactor(LumaInstabilityFactorData data, FfxFloat32 fCurrentFrameLuma, FfxFloat32 fFarthestDepthInMeters)
+{
+ const FfxInt32 N_MINUS_1 = 0;
+ const FfxInt32 N_MINUS_2 = 1;
+ const FfxInt32 N_MINUS_3 = 2;
+ const FfxInt32 N_MINUS_4 = 3;
+
+ FfxFloat32 fLumaInstability = 0.0f;
+ const FfxFloat32 fDiffs0 = (fCurrentFrameLuma - data.fLumaHistory[N_MINUS_1]);
+ const FfxFloat32 fSimilarity0 = MinDividedByMax(fCurrentFrameLuma, data.fLumaHistory[N_MINUS_1], 1.0f);
+
+ FfxFloat32 fMaxSimilarity = fSimilarity0;
+
+ if (fSimilarity0 < 1.0f) {
+ for (int i = N_MINUS_2; i <= N_MINUS_4; i++) {
+ const FfxFloat32 fDiffs1 = (fCurrentFrameLuma - data.fLumaHistory[i]);
+ const FfxFloat32 fSimilarity1 = MinDividedByMax(fCurrentFrameLuma, data.fLumaHistory[i]);
+
+ if (sign(fDiffs0) == sign(fDiffs1)) {
+
+ fMaxSimilarity = ffxMax(fMaxSimilarity, fSimilarity1);
+ }
+ }
+
+ fLumaInstability = FfxFloat32(fMaxSimilarity > fSimilarity0);
+ }
+
+ // Shift history
+ data.fLumaHistory[N_MINUS_4] = data.fLumaHistory[N_MINUS_3];
+ data.fLumaHistory[N_MINUS_3] = data.fLumaHistory[N_MINUS_2];
+ data.fLumaHistory[N_MINUS_2] = data.fLumaHistory[N_MINUS_1];
+ data.fLumaHistory[N_MINUS_1] = fCurrentFrameLuma;
+
+ data.fLumaHistory /= Exposure();
+
+ data.fLumaInstabilityFactor = fLumaInstability * FfxFloat32(data.fLumaHistory[N_MINUS_4] != 0);
+
+ return data;
+}
+
+void LumaInstability(FfxInt32x2 iPxPos)
+{
+ LumaInstabilityFactorData data;
+ data.fLumaInstabilityFactor = 0.0f;
+ data.fLumaHistory = FfxFloat32x4(0.0f, 0.0f, 0.0f, 0.0f);
+
+ const FfxFloat32x2 fDilatedMotionVector = LoadDilatedMotionVector(iPxPos);
+ const FfxFloat32x2 fUv = (iPxPos + 0.5f) / RenderSize();
+ const FfxFloat32x2 fUvCurrFrameJittered = fUv + Jitter() / RenderSize();
+ const FfxFloat32x2 fUvPrevFrameJittered = fUv + PreviousFrameJitter() / PreviousFrameRenderSize();
+ const FfxFloat32x2 fReprojectedUv = fUvPrevFrameJittered + fDilatedMotionVector;
+
+ if (IsUvInside(fReprojectedUv))
+ {
+ const FfxFloat32x2 fUvReactive_HW = ClampUv(fUvCurrFrameJittered, RenderSize(), MaxRenderSize());
+
+ const FfxFloat32x4 fDilatedReactiveMasks = SampleDilatedReactiveMasks(fUvReactive_HW);
+ const FfxFloat32 fReactiveMask = ffxSaturate(fDilatedReactiveMasks[REACTIVE]);
+ const FfxFloat32 fDisocclusion = ffxSaturate(fDilatedReactiveMasks[DISOCCLUSION]);
+ const FfxFloat32 fShadingChange = ffxSaturate(fDilatedReactiveMasks[SHADING_CHANGE]);
+ const FfxFloat32 fAccumulation = ffxSaturate(fDilatedReactiveMasks[ACCUMULAION]);
+
+ const FfxBoolean bAccumulationFactor = fAccumulation > 0.9f;
+
+ const FfxBoolean bComputeInstability = bAccumulationFactor;
+
+ if (bComputeInstability) {
+
+ const FfxFloat32x2 fUv_HW = ClampUv(fUvCurrFrameJittered, RenderSize(), MaxRenderSize());
+ const FfxFloat32 fCurrentFrameLuma = SampleCurrentLuma(fUv_HW) * Exposure();
+
+ const FfxFloat32x2 fReprojectedUv_HW = ClampUv(fReprojectedUv, PreviousFrameRenderSize(), MaxRenderSize());
+ data.fLumaHistory = SampleLumaHistory(fReprojectedUv_HW) * DeltaPreExposure() * Exposure();
+
+ const FfxFloat32x2 fFarthestDepthUv_HW = ClampUv(fUvCurrFrameJittered, RenderSize() / 2, GetFarthestDepthMip1ResourceDimensions());
+ const FfxFloat32 fFarthestDepthInMeters = SampleFarthestDepthMip1(fFarthestDepthUv_HW);
+
+ data = ComputeLumaInstabilityFactor(data, fCurrentFrameLuma, fFarthestDepthInMeters);
+
+ const FfxFloat32 fVelocityWeight = 1.0f - ffxSaturate(Get4KVelocity(fDilatedMotionVector) / 20.0f);
+ data.fLumaInstabilityFactor *= fVelocityWeight * (1.0f - fDisocclusion) * (1.0f - fReactiveMask) * (1.0f - fShadingChange);
+ }
+ }
+
+ StoreLumaHistory(iPxPos, data.fLumaHistory);
+ StoreLumaInstability(iPxPos, data.fLumaInstabilityFactor);
+}
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_luma_pyramid.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_luma_pyramid.h
new file mode 100644
index 0000000000..41c5dc7f45
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_luma_pyramid.h
@@ -0,0 +1,192 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+FFX_GROUPSHARED FfxUInt32 spdCounter;
+
+void SpdIncreaseAtomicCounter(FfxUInt32 slice)
+{
+ SPD_IncreaseAtomicCounter(spdCounter);
+}
+
+FfxUInt32 SpdGetAtomicCounter()
+{
+ return spdCounter;
+}
+
+void SpdResetAtomicCounter(FfxUInt32 slice)
+{
+ SPD_ResetAtomicCounter();
+}
+
+#ifndef SPD_PACKED_ONLY
+FFX_GROUPSHARED FfxFloat32 spdIntermediateR[16][16];
+FFX_GROUPSHARED FfxFloat32 spdIntermediateG[16][16];
+FFX_GROUPSHARED FfxFloat32 spdIntermediateB[16][16];
+FFX_GROUPSHARED FfxFloat32 spdIntermediateA[16][16];
+
+FFX_STATIC const FfxInt32 LOG_LUMA = 0;
+FFX_STATIC const FfxInt32 LUMA = 1;
+FFX_STATIC const FfxInt32 DEPTH_IN_METERS = 2;
+
+FfxFloat32x4 SpdLoadSourceImage(FfxFloat32x2 iPxPos, FfxUInt32 slice)
+{
+ //We assume linear data. if non-linear input (sRGB, ...),
+ //then we should convert to linear first and back to sRGB on output.
+ const FfxInt32x2 iPxSamplePos = ClampLoad(FfxInt32x2(iPxPos), FfxInt32x2(0, 0), FfxInt32x2(RenderSize()));
+
+ const FfxFloat32 fLuma = LoadCurrentLuma(iPxSamplePos);
+ const FfxFloat32 fLogLuma = ffxMax(FSR3UPSCALER_EPSILON, log(fLuma));
+ const FfxFloat32 fFarthestDepthInMeters = LoadFarthestDepth(iPxSamplePos);
+
+ FfxFloat32x4 fOutput = FfxFloat32x4(0.0f, 0.0f, 0.0f, 0.0f);
+ fOutput[LOG_LUMA] = fLogLuma;
+ fOutput[LUMA] = fLuma;
+ fOutput[DEPTH_IN_METERS] = fFarthestDepthInMeters;
+
+ return fOutput;
+}
+
+FfxFloat32x4 SpdLoad(FfxInt32x2 tex, FfxUInt32 slice)
+{
+ return FfxFloat32x4(RWLoadPyramid(tex, 5), 0, 0);
+}
+
+FfxFloat32x4 SpdReduce4(FfxFloat32x4 v0, FfxFloat32x4 v1, FfxFloat32x4 v2, FfxFloat32x4 v3)
+{
+ return (v0 + v1 + v2 + v3) * 0.25f;
+}
+
+void SpdStore(FfxInt32x2 pix, FfxFloat32x4 outValue, FfxUInt32 index, FfxUInt32 slice)
+{
+ if (index == 5)
+ {
+ StorePyramid(pix, outValue.xy, index);
+ }
+ else if (index == 0) {
+ StoreFarthestDepthMip1(pix, outValue[DEPTH_IN_METERS]);
+ }
+
+ if (index == MipCount() - 1) { //accumulate on 1x1 level
+
+ if (all(FFX_EQUAL(pix, FfxInt32x2(0, 0))))
+ {
+ FfxFloat32x4 frameInfo = LoadFrameInfo();
+ const FfxFloat32 fSceneAvgLuma = outValue[LUMA];
+ const FfxFloat32 fPrevLogLuma = frameInfo[FRAME_INFO_LOG_LUMA];
+ FfxFloat32 fLogLuma = outValue[LOG_LUMA];
+
+ if (fPrevLogLuma < resetAutoExposureAverageSmoothing) // Compare Lavg, so small or negative values
+ {
+ fLogLuma = fPrevLogLuma + (fLogLuma - fPrevLogLuma) * (1.0f - exp(-DeltaTime()));
+ fLogLuma = ffxMax(0.0f, fLogLuma);
+ }
+
+ frameInfo[FRAME_INFO_EXPOSURE] = ComputeAutoExposureFromLavg(fLogLuma);
+ frameInfo[FRAME_INFO_LOG_LUMA] = fLogLuma;
+ frameInfo[FRAME_INFO_SCENE_AVERAGE_LUMA] = fSceneAvgLuma;
+
+ StoreFrameInfo(frameInfo);
+ }
+ }
+}
+
+FfxFloat32x4 SpdLoadIntermediate(FfxUInt32 x, FfxUInt32 y)
+{
+ return FfxFloat32x4(
+ spdIntermediateR[x][y],
+ spdIntermediateG[x][y],
+ spdIntermediateB[x][y],
+ spdIntermediateA[x][y]);
+}
+void SpdStoreIntermediate(FfxUInt32 x, FfxUInt32 y, FfxFloat32x4 value)
+{
+ spdIntermediateR[x][y] = value.x;
+ spdIntermediateG[x][y] = value.y;
+ spdIntermediateB[x][y] = value.z;
+ spdIntermediateA[x][y] = value.w;
+}
+
+#endif
+
+// define fetch and store functions Packed
+#if FFX_HALF
+
+FFX_GROUPSHARED FfxFloat16x2 spdIntermediateRG[16][16];
+FFX_GROUPSHARED FfxFloat16x2 spdIntermediateBA[16][16];
+
+FfxFloat16x4 SpdLoadSourceImageH(FfxFloat32x2 tex, FfxUInt32 slice)
+{
+ return FfxFloat16x4(0, 0, 0, 0);
+}
+
+FfxFloat16x4 SpdLoadH(FfxInt32x2 p, FfxUInt32 slice)
+{
+ return FfxFloat16x4(0, 0, 0, 0);
+}
+
+void SpdStoreH(FfxInt32x2 p, FfxFloat16x4 value, FfxUInt32 mip, FfxUInt32 slice)
+{
+}
+
+FfxFloat16x4 SpdLoadIntermediateH(FfxUInt32 x, FfxUInt32 y)
+{
+ return FfxFloat16x4(
+ spdIntermediateRG[x][y].x,
+ spdIntermediateRG[x][y].y,
+ spdIntermediateBA[x][y].x,
+ spdIntermediateBA[x][y].y);
+}
+
+void SpdStoreIntermediateH(FfxUInt32 x, FfxUInt32 y, FfxFloat16x4 value)
+{
+ spdIntermediateRG[x][y] = value.xy;
+ spdIntermediateBA[x][y] = value.zw;
+}
+
+FfxFloat16x4 SpdReduce4H(FfxFloat16x4 v0, FfxFloat16x4 v1, FfxFloat16x4 v2, FfxFloat16x4 v3)
+{
+ return (v0 + v1 + v2 + v3) * FfxFloat16(0.25);
+}
+#endif
+
+#include "spd/ffx_spd.h"
+
+void ComputeAutoExposure(FfxUInt32x3 WorkGroupId, FfxUInt32 LocalThreadIndex)
+{
+#if FFX_HALF
+ SpdDownsampleH(
+ FfxUInt32x2(WorkGroupId.xy),
+ FfxUInt32(LocalThreadIndex),
+ FfxUInt32(MipCount()),
+ FfxUInt32(NumWorkGroups()),
+ FfxUInt32(WorkGroupId.z),
+ FfxUInt32x2(WorkGroupOffset()));
+#else
+ SpdDownsample(
+ FfxUInt32x2(WorkGroupId.xy),
+ FfxUInt32(LocalThreadIndex),
+ FfxUInt32(MipCount()),
+ FfxUInt32(NumWorkGroups()),
+ FfxUInt32(WorkGroupId.z),
+ FfxUInt32x2(WorkGroupOffset()));
+#endif
+}
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_prepare_inputs.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_prepare_inputs.h
new file mode 100644
index 0000000000..59c765b527
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_prepare_inputs.h
@@ -0,0 +1,152 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+void ReconstructPrevDepth(FfxInt32x2 iPxPos, FfxFloat32 fDepth, FfxFloat32x2 fMotionVector)
+{
+ const FfxFloat32 fNearestDepthInMeters = ffxMin(GetViewSpaceDepthInMeters(fDepth), FSR3UPSCALER_FP16_MAX);
+ const FfxFloat32 fReconstructedDeptMvThreshold = ReconstructedDepthMvPxThreshold(fNearestDepthInMeters);
+
+ // Discard small mvs
+ fMotionVector *= FfxFloat32(Get4KVelocity(fMotionVector) > fReconstructedDeptMvThreshold);
+
+ const FfxFloat32x2 fUv = (iPxPos + FfxFloat32(0.5)) / RenderSize();
+ const FfxFloat32x2 fReprojectedUv = fUv + fMotionVector;
+ const BilinearSamplingData bilinearInfo = GetBilinearSamplingData(fReprojectedUv, RenderSize());
+
+ // Project current depth into previous frame locations.
+ // Push to all pixels having some contribution if reprojection is using bilinear logic.
+ for (FfxInt32 iSampleIndex = 0; iSampleIndex < 4; iSampleIndex++) {
+
+ const FfxInt32x2 iOffset = bilinearInfo.iOffsets[iSampleIndex];
+ const FfxFloat32 fWeight = bilinearInfo.fWeights[iSampleIndex];
+
+ if (fWeight > fReconstructedDepthBilinearWeightThreshold) {
+
+ const FfxInt32x2 iStorePos = bilinearInfo.iBasePos + iOffset;
+ if (IsOnScreen(iStorePos, RenderSize())) {
+ StoreReconstructedDepth(iStorePos, fDepth);
+ }
+ }
+ }
+}
+
+struct DepthExtents
+{
+ FfxFloat32 fNearest;
+ FfxInt32x2 fNearestCoord;
+ FfxFloat32 fFarthest;
+};
+
+DepthExtents FindDepthExtents(FFX_PARAMETER_IN FfxInt32x2 iPxPos)
+{
+ DepthExtents extents;
+ const FfxInt32 iSampleCount = 9;
+ const FfxInt32x2 iSampleOffsets[iSampleCount] = {
+ FfxInt32x2(+0, +0),
+ FfxInt32x2(+1, +0),
+ FfxInt32x2(+0, +1),
+ FfxInt32x2(+0, -1),
+ FfxInt32x2(-1, +0),
+ FfxInt32x2(-1, +1),
+ FfxInt32x2(+1, +1),
+ FfxInt32x2(-1, -1),
+ FfxInt32x2(+1, -1),
+ };
+
+ // pull out the depth loads to allow SC to batch them
+ FfxFloat32 depth[9];
+ FfxInt32 iSampleIndex = 0;
+ FFX_UNROLL
+ for (iSampleIndex = 0; iSampleIndex < iSampleCount; ++iSampleIndex) {
+
+ FfxInt32x2 iPos = iPxPos + iSampleOffsets[iSampleIndex];
+ depth[iSampleIndex] = LoadInputDepth(iPos);
+ }
+
+ // find closest depth
+ extents.fNearestCoord = iPxPos;
+ extents.fNearest = depth[0];
+ extents.fFarthest = depth[0];
+ FFX_UNROLL
+ for (iSampleIndex = 1; iSampleIndex < iSampleCount; ++iSampleIndex) {
+
+ const FfxInt32x2 iPos = iPxPos + iSampleOffsets[iSampleIndex];
+ if (IsOnScreen(iPos, RenderSize())) {
+
+ FfxFloat32 fNdDepth = depth[iSampleIndex];
+#if FFX_FSR3UPSCALER_OPTION_INVERTED_DEPTH
+ if (fNdDepth > extents.fNearest) {
+ extents.fFarthest = ffxMin(extents.fFarthest, fNdDepth);
+#else
+ if (fNdDepth < extents.fNearest) {
+ extents.fFarthest = ffxMax(extents.fFarthest, fNdDepth);
+#endif
+ extents.fNearestCoord = iPos;
+ extents.fNearest = fNdDepth;
+ }
+ }
+ }
+
+ return extents;
+}
+
+FfxFloat32x2 DilateMotionVector(FfxInt32x2 iPxPos, const DepthExtents depthExtents)
+{
+#if FFX_FSR3UPSCALER_OPTION_LOW_RESOLUTION_MOTION_VECTORS
+ const FfxInt32x2 iSamplePos = iPxPos;
+ const FfxInt32x2 iMotionVectorPos = depthExtents.fNearestCoord;
+#else
+ const FfxInt32x2 iSamplePos = ComputeHrPosFromLrPos(iPxPos);
+ const FfxInt32x2 iMotionVectorPos = ComputeHrPosFromLrPos(depthExtents.fNearestCoord);
+#endif
+
+ const FfxFloat32x2 fDilatedMotionVector = LoadInputMotionVector(iMotionVectorPos);
+
+ return fDilatedMotionVector;
+}
+
+FfxFloat32 GetCurrentFrameLuma(FfxInt32x2 iPxPos)
+{
+ //We assume linear data. if non-linear input (sRGB, ...),
+ //then we should convert to linear first and back to sRGB on output.
+ const FfxFloat32x3 fRgb = ffxMax(FfxFloat32x3(0, 0, 0), LoadInputColor(iPxPos));
+ const FfxFloat32 fLuma = RGBToLuma(fRgb);
+
+ return fLuma;
+}
+
+void PrepareInputs(FfxInt32x2 iPxPos)
+{
+ const DepthExtents depthExtents = FindDepthExtents(iPxPos);
+ const FfxFloat32x2 fDilatedMotionVector = DilateMotionVector(iPxPos, depthExtents);
+
+ ReconstructPrevDepth(iPxPos, depthExtents.fNearest, fDilatedMotionVector);
+
+ StoreDilatedMotionVector(iPxPos, fDilatedMotionVector);
+ StoreDilatedDepth(iPxPos, depthExtents.fNearest);
+
+ const FfxFloat32 fFarthestDepthInMeters = ffxMin(GetViewSpaceDepthInMeters(depthExtents.fFarthest), FSR3UPSCALER_FP16_MAX);
+ StoreFarthestDepth(iPxPos, fFarthestDepthInMeters);
+
+ const FfxFloat32 fLuma = GetCurrentFrameLuma(iPxPos);
+ StoreCurrentLuma(iPxPos, fLuma);
+}
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_prepare_reactivity.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_prepare_reactivity.h
new file mode 100644
index 0000000000..ae24545c80
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_prepare_reactivity.h
@@ -0,0 +1,283 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+FfxFloat32 ComputeDisocclusions(FfxFloat32x2 fUv, FfxFloat32x2 fMotionVector, FfxFloat32 fCurrentDepthViewSpace)
+{
+ const FfxFloat32 fNearestDepthInMeters = ffxMin(fCurrentDepthViewSpace * ViewSpaceToMetersFactor(), FSR3UPSCALER_FP16_MAX);
+ const FfxFloat32 fReconstructedDeptMvThreshold = ReconstructedDepthMvPxThreshold(fNearestDepthInMeters);
+
+ fMotionVector *= FfxFloat32(Get4KVelocity(fMotionVector) > fReconstructedDeptMvThreshold);
+
+ const FfxFloat32x2 fReprojectedUv = fUv + fMotionVector;
+ const BilinearSamplingData bilinearInfo = GetBilinearSamplingData(fReprojectedUv, RenderSize());
+
+ FfxFloat32 fDisocclusion = 0.0f;
+ FfxFloat32 fWeightSum = 0.0f;
+ FfxBoolean bPotentialDisocclusion = true;
+
+ for (FfxInt32 iSampleIndex = 0; iSampleIndex < 4 && bPotentialDisocclusion; iSampleIndex++)
+ {
+
+ const FfxInt32x2 iOffset = bilinearInfo.iOffsets[iSampleIndex];
+ const FfxInt32x2 iSamplePos = ClampLoad(bilinearInfo.iBasePos, iOffset, FfxInt32x2(RenderSize()));
+
+ if (IsOnScreen(iSamplePos, RenderSize())) {
+ const FfxFloat32 fWeight = bilinearInfo.fWeights[iSampleIndex];
+ if (fWeight > fReconstructedDepthBilinearWeightThreshold) {
+
+ const FfxFloat32 fPrevNearestDepthViewSpace = GetViewSpaceDepth(LoadReconstructedPrevDepth(iSamplePos));
+ const FfxFloat32 fDepthDifference = fCurrentDepthViewSpace - fPrevNearestDepthViewSpace;
+
+ bPotentialDisocclusion = bPotentialDisocclusion && (fDepthDifference > FSR3UPSCALER_FP32_MIN);
+
+ if (bPotentialDisocclusion) {
+ const FfxFloat32 fHalfViewportWidth = length(FfxFloat32x2(RenderSize()) * 0.5f);
+ const FfxFloat32 fDepthThreshold = ffxMax(fCurrentDepthViewSpace, fPrevNearestDepthViewSpace);
+
+ const FfxFloat32 Ksep = 1.37e-05f;
+ const FfxFloat32 fRequiredDepthSeparation = Ksep * fHalfViewportWidth * fDepthThreshold;
+
+ fDisocclusion += ffxSaturate(FfxFloat32(fRequiredDepthSeparation / fDepthDifference)) * fWeight;
+ fWeightSum += fWeight;
+ }
+ }
+ }
+ }
+
+ fDisocclusion = (bPotentialDisocclusion && fWeightSum > 0) ? ffxSaturate(1.0f - fDisocclusion / fWeightSum) : 0.0f;
+
+ return fDisocclusion;
+}
+
+FfxFloat32 ComputeMotionDivergence(FfxFloat32x2 fUv, FfxFloat32x2 fMotionVector, FfxFloat32 fCurrentDepthSample)
+{
+ const FfxInt32x2 iPxReprojectedPos = FfxInt32x2((fUv + fMotionVector) * RenderSize());
+ const FfxFloat32 fReprojectedDepth = LoadDilatedDepth(iPxReprojectedPos);
+ const FfxFloat32x2 fReprojectedMotionVector = LoadDilatedMotionVector(iPxReprojectedPos);
+
+ const FfxFloat32 fReprojectedVelocity = Get4KVelocity(fReprojectedMotionVector);
+ const FfxFloat32 f4KVelocity = Get4KVelocity(fMotionVector);
+
+ const FfxFloat32 fMaxLen = max(length(fMotionVector), length(fReprojectedMotionVector));
+
+ const FfxFloat32 fNucleusDepthInMeters = GetViewSpaceDepthInMeters(fReprojectedDepth);
+ const FfxFloat32 fCurrentDepthInMeters = GetViewSpaceDepthInMeters(fCurrentDepthSample);
+
+ const FfxFloat32 fDistanceFactor = MinDividedByMax(fNucleusDepthInMeters, fCurrentDepthInMeters);
+ const FfxFloat32 fVelocityFactor = ffxSaturate(f4KVelocity / 10.0f);
+ const FfxFloat32 fMotionVectorFieldConfidence = (1.0f - ffxSaturate(fReprojectedVelocity / f4KVelocity)) * fDistanceFactor * fVelocityFactor;
+
+ return fMotionVectorFieldConfidence;
+}
+
+FfxFloat32 DilateReactiveMasks(FfxInt32x2 iPxPos, FfxFloat32x2 fUv)
+{
+ FfxFloat32 fDilatedReactiveMasks = 0.0f;
+
+ FFX_UNROLL
+ for (FfxInt32 y = -1; y <=1; y++)
+ {
+ FFX_UNROLL
+ for (FfxInt32 x = -1; x <= 1; x++)
+ {
+ const FfxInt32x2 sampleCoord = ClampLoad(iPxPos, FfxInt32x2(x, y), FfxInt32x2(RenderSize()));
+ fDilatedReactiveMasks = ffxMax(fDilatedReactiveMasks, LoadReactiveMask(sampleCoord));
+ }
+ }
+
+ return fDilatedReactiveMasks;
+}
+
+FfxFloat32 DilateTransparencyAndCompositionMasks(FfxInt32x2 iPxPos, FfxFloat32x2 fUv)
+{
+ const FfxFloat32x2 fUvTransparencyAndCompositionMask = ClampUv(fUv, RenderSize(), GetTransparencyAndCompositionMaskResourceDimensions());
+ return SampleTransparencyAndCompositionMask(fUvTransparencyAndCompositionMask);
+}
+
+FfxFloat32 ComputeThinFeatureConfidence(FfxInt32x2 iPxPos)
+{
+ /*
+ 1 2 3
+ 4 0 5
+ 6 7 8
+ */
+
+ const FfxInt32 iNucleusIndex = 0;
+ const FfxInt32 iSampleCount = 9;
+ const FfxInt32x2 iSampleOffsets[iSampleCount] = {
+ FfxInt32x2(+0, +0),
+ FfxInt32x2(-1, -1),
+ FfxInt32x2(+0, -1),
+ FfxInt32x2(+1, -1),
+ FfxInt32x2(-1, +0),
+ FfxInt32x2(+1, +0),
+ FfxInt32x2(-1, +1),
+ FfxInt32x2(+0, +1),
+ FfxInt32x2(+1, +1),
+ };
+
+ FfxFloat32 fSamples[iSampleCount];
+
+ FfxFloat32 fLumaMin = FSR3UPSCALER_FP32_MAX;
+ FfxFloat32 fLumaMax = FSR3UPSCALER_FP32_MIN;
+
+ FFX_UNROLL
+ for (FfxInt32 iSampleIndex = 0; iSampleIndex < iSampleCount; ++iSampleIndex) {
+ const FfxInt32x2 iPxSamplePos = ClampLoad(iPxPos, iSampleOffsets[iSampleIndex], FfxInt32x2(RenderSize()));
+ fSamples[iSampleIndex] = LoadCurrentLuma(iPxSamplePos) * Exposure();
+
+ fLumaMin = ffxMin(fLumaMin, fSamples[iSampleIndex]);
+ fLumaMax = ffxMax(fLumaMax, fSamples[iSampleIndex]);
+ }
+
+ const FfxFloat32 fThreshold = 0.9f;
+ FfxFloat32 fDissimilarLumaMin = FSR3UPSCALER_FP32_MAX;
+ FfxFloat32 fDissimilarLumaMax = 0;
+
+#define SETBIT(x) (1U << x)
+
+ FfxUInt32 uPatternMask = SETBIT(iNucleusIndex); // Flag nucleus as similar
+
+ const FfxUInt32 uNumRejectionMasks = 4;
+ const FfxUInt32 uRejectionMasks[uNumRejectionMasks] = {
+ SETBIT(1) | SETBIT(2) | SETBIT(4) | SETBIT(iNucleusIndex), // Upper left
+ SETBIT(2) | SETBIT(3) | SETBIT(5) | SETBIT(iNucleusIndex), // Upper right
+ SETBIT(4) | SETBIT(6) | SETBIT(7) | SETBIT(iNucleusIndex), // Lower left
+ SETBIT(5) | SETBIT(7) | SETBIT(8) | SETBIT(iNucleusIndex) // Lower right
+ };
+
+ FfxInt32 iBitIndex = 1;
+ FFX_UNROLL
+ for (FfxInt32 iSampleIndex = 1; iSampleIndex < iSampleCount; ++iSampleIndex, ++iBitIndex) {
+
+ const FfxFloat32 fDifference = abs(fSamples[iSampleIndex] - fSamples[iNucleusIndex]) / (fLumaMax - fLumaMin);
+
+ if (fDifference < fThreshold)
+ {
+ uPatternMask |= SETBIT(iBitIndex);
+ }
+ else
+ {
+ fDissimilarLumaMin = ffxMin(fDissimilarLumaMin, fSamples[iSampleIndex]);
+ fDissimilarLumaMax = ffxMax(fDissimilarLumaMax, fSamples[iSampleIndex]);
+ }
+ }
+
+ const FfxBoolean bIsRidge = fSamples[iNucleusIndex] > fDissimilarLumaMax || fSamples[iNucleusIndex] < fDissimilarLumaMin;
+
+ if (FFX_FALSE == bIsRidge)
+ {
+ return 0.0f;
+ }
+
+ FFX_UNROLL
+ for (FfxInt32 i = 0; i < uNumRejectionMasks; i++)
+ {
+ if ((uPatternMask & uRejectionMasks[i]) == uRejectionMasks[i])
+ {
+ return 0.0f;
+ }
+ }
+
+ return 1.0f - fLumaMin / fLumaMax;
+}
+
+FfxFloat32 UpdateAccumulation(FfxInt32x2 iPxPos, FfxFloat32x2 fUv, FfxFloat32x2 fMotionVector, FfxFloat32 fDisocclusion, FfxFloat32 fShadingChange)
+{
+ const FfxFloat32x2 fReprojectedUv = fUv + fMotionVector;
+ FfxFloat32 fAccumulation = 0.0f;
+
+ if (IsUvInside(fReprojectedUv)) {
+ const FfxFloat32x2 fReprojectedUv_HW = ClampUv(fReprojectedUv, PreviousFrameRenderSize(), MaxRenderSize());
+ fAccumulation = ffxSaturate(SampleAccumulation(fReprojectedUv_HW));
+ }
+ const FfxFloat32 fAccumulationAddedPerFrame= AccumulationAddedPerFrame(); //default is 0.333
+
+ // Assume at frame N+0 fShadingChange is 1.0, and all subsequent frames fShadingChange is 0.0 and fDisocclusion is 0.0. Then,
+ // frame N+0 fAccumulation will be 0.000
+ // frame N+2 fAccumulation will be 0.000 + 0.333 * 1 == 0.333
+ // frame N+3 fAccumulation will be 0.000 + 0.333 * 2 == 0.666
+ // frame N+4 fAccumulation will be 0.000 + 0.333 * 3 == 0.999
+ fAccumulation = ffxLerp(fAccumulation, 0.0f, fShadingChange);
+
+ const FfxFloat32 fMinDisocclusionAccumulation = MinDisocclusionAccumulation(); //default is -0.333
+ // Assume at frame N+0 fDisocclusion is 1.0, and all subsequent frames fShadingChange is 0.0 and fDisocclusion is 0.0. Then,
+ // frame N+0 fAccumulation will be -0.333f (but normalized to store in unorm)
+ // frame N+1 fAccumulation will be -0.333f + 0.333 * 1 == 0.000
+ // frame N+2 fAccumulation will be -0.333f + 0.333 * 2 == 0.333
+ // frame N+3 fAccumulation will be -0.333f + 0.333 * 3 == 0.666
+ // frame N+4 fAccumulation will be -0.333f + 0.333 * 4 == 0.999
+ fAccumulation = ffxLerp(fAccumulation, ffxMin(fMinDisocclusionAccumulation, fAccumulation), fDisocclusion);
+
+ fAccumulation *= FfxFloat32(round(fAccumulation * 100.0f) > 1.0f);
+
+ // Update for next frame, normalize to store in unorm
+ const FfxFloat32 fAccumulatedFramesToStore = ffxSaturate(fAccumulation + fAccumulationAddedPerFrame);
+ StoreAccumulation(iPxPos, fAccumulatedFramesToStore);
+
+ return fAccumulation;
+}
+
+FfxFloat32 ComputeShadingChange(FfxFloat32x2 fUv)
+{
+ // NOTE: Here we re-apply jitter, will be reverted again when sampled in accumulation pass
+ const FfxFloat32x2 fShadingChangeUv = ClampUv(fUv - Jitter() / RenderSize(), ShadingChangeRenderSize(), ShadingChangeMaxRenderSize());
+ const FfxFloat32 fShadingChange = ffxSaturate(SampleShadingChange(fShadingChangeUv));
+
+ return fShadingChange;
+}
+
+void PrepareReactivity(FfxInt32x2 iPxPos)
+{
+ const FfxFloat32x2 fUv = (iPxPos + 0.5f) / RenderSize();
+ const FfxFloat32x2 fMotionVector = LoadDilatedMotionVector(iPxPos);
+
+ // Discard small mvs
+ const FfxFloat32 f4KVelocity = Get4KVelocity(fMotionVector);
+
+ const FfxFloat32x2 fDilatedUv = fUv + fMotionVector;
+ const FfxFloat32 fDilatedDepth = LoadDilatedDepth(iPxPos);
+ const FfxFloat32 fDepthInMeters = GetViewSpaceDepthInMeters(fDilatedDepth);
+
+ const FfxFloat32 fDisocclusion = ComputeDisocclusions(fUv, fMotionVector, GetViewSpaceDepth(fDilatedDepth));
+ const FfxFloat32 fShadingChange = ffxMax(DilateReactiveMasks(iPxPos, fUv), ComputeShadingChange(fUv));
+
+ const FfxFloat32 fMotionDivergence = ComputeMotionDivergence(fUv, fMotionVector, fDilatedDepth);
+ const FfxFloat32 fDilatedTransparencyAndComposition = DilateTransparencyAndCompositionMasks(iPxPos, fUv);
+ const FfxFloat32 fFinalReactiveness = ffxMax(fMotionDivergence, fDilatedTransparencyAndComposition);
+
+ const FfxFloat32 fAccumulation = UpdateAccumulation(iPxPos, fUv, fMotionVector, fDisocclusion, fShadingChange);
+
+ FfxFloat32x4 fOutput;
+ fOutput[REACTIVE] = fFinalReactiveness;
+ fOutput[DISOCCLUSION] = fDisocclusion;
+ fOutput[SHADING_CHANGE] = fShadingChange;
+ fOutput[ACCUMULAION] = fAccumulation;
+
+ StoreDilatedReactiveMasks(iPxPos, fOutput);
+
+ const FfxFloat32 fLockStrength = ComputeThinFeatureConfidence(iPxPos);
+ if (fLockStrength > (1.0f / 100.0f))
+ {
+ StoreNewLocks(ComputeHrPosFromLrPos(FfxInt32x2(iPxPos)), fLockStrength);
+ }
+}
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_rcas.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_rcas.h
new file mode 100644
index 0000000000..f48cffb93f
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_rcas.h
@@ -0,0 +1,112 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#define GROUP_SIZE 8
+#define FSR_RCAS_DENOISE 1
+
+#include "ffx_core.h"
+
+#if FFX_HALF && defined(__XBOX_SCARLETT) && defined(__XBATG_EXTRA_16_BIT_OPTIMISATION) && (__XBATG_EXTRA_16_BIT_OPTIMISATION == 1)
+ #define FSR_RCAS_PREFER_PAIRED_VERSION 1
+#else
+ #define FSR_RCAS_PREFER_PAIRED_VERSION 0
+#endif
+
+void WriteUpscaledOutput(FFX_MIN16_U2 iPxHrPos, FfxFloat32x3 fUpscaledColor)
+{
+ StoreUpscaledOutput(FFX_MIN16_I2(iPxHrPos), fUpscaledColor);
+}
+
+#if FSR_RCAS_PREFER_PAIRED_VERSION
+ #define FSR_RCAS_HX2 1
+ FfxFloat16x4 FsrRcasLoadHx2(FfxInt16x2 p)
+ {
+ return FfxFloat16x4(LoadRCAS_Input(p));
+ }
+ void FsrRcasInputHx2(inout FfxFloat16x2 r, inout FfxFloat16x2 g, inout FfxFloat16x2 b)
+ {
+ FfxFloat32 e = Exposure();
+ r = FfxFloat16x2(r * e);
+ g = FfxFloat16x2(g * e);
+ b = FfxFloat16x2(b * e);
+ }
+
+ #include "fsr1/ffx_fsr1.h"
+
+ void CurrFilterPaired(FFX_MIN16_U2 pos)
+ {
+ FfxFloat16x2 cr;
+ FfxFloat16x2 cg;
+ FfxFloat16x2 cb;
+ FsrRcasHx2(cr, cg, cb, pos, RCASConfig());
+ FfxFloat32 InvExposure = 1.0f / Exposure();
+ cr = FfxFloat16x2(cr * InvExposure);
+ cg = FfxFloat16x2(cg * InvExposure);
+ cb = FfxFloat16x2(cb * InvExposure);
+ WriteUpscaledOutput(pos, FfxFloat16x3(cr.x, cg.x, cb.x)); //TODO: fix type
+ pos.x += 8;
+ WriteUpscaledOutput(pos, FfxFloat16x3(cr.y, cg.y, cb.y)); //TODO: fix type
+ }
+#else
+ #define FSR_RCAS_F 1
+ FfxFloat32x4 FsrRcasLoadF(FfxInt32x2 p)
+ {
+ FfxFloat32x4 fColor = LoadRCAS_Input(p);
+
+ fColor.rgb *= Exposure();
+
+ return fColor;
+ }
+ void FsrRcasInputF(inout FfxFloat32 r, inout FfxFloat32 g, inout FfxFloat32 b) {}
+
+ #include "fsr1/ffx_fsr1.h"
+
+ void CurrFilter(FFX_MIN16_U2 pos)
+ {
+ FfxFloat32x3 c;
+ FsrRcasF(c.r, c.g, c.b, pos, RCASConfig());
+
+ c /= Exposure();
+
+ WriteUpscaledOutput(pos, c);
+ }
+
+#endif // #if FSR_RCAS_PREFER_PAIRED_VERSION
+
+void RCAS(FfxUInt32x3 LocalThreadId, FfxUInt32x3 WorkGroupId, FfxUInt32x3 Dtid)
+{
+ // Do remapping of local xy in workgroup for a more PS-like swizzle pattern.
+ FfxUInt32x2 gxy = ffxRemapForQuad(LocalThreadId.x) + FfxUInt32x2(WorkGroupId.x << 4u, WorkGroupId.y << 4u);
+#if FSR_RCAS_PREFER_PAIRED_VERSION
+ CurrFilterPaired(FFX_MIN16_U2(gxy));
+ gxy.y += 8u;
+ CurrFilterPaired(FFX_MIN16_U2(gxy));
+#else
+ CurrFilter(FFX_MIN16_U2(gxy));
+ gxy.x += 8u;
+ CurrFilter(FFX_MIN16_U2(gxy));
+ gxy.y += 8u;
+ CurrFilter(FFX_MIN16_U2(gxy));
+ gxy.x -= 8u;
+ CurrFilter(FFX_MIN16_U2(gxy));
+#endif
+}
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_reproject.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_reproject.h
new file mode 100644
index 0000000000..c72f40fb6f
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_reproject.h
@@ -0,0 +1,79 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#ifndef FFX_FSR3UPSCALER_OPTION_REPROJECT_USE_LANCZOS_TYPE
+#define FFX_FSR3UPSCALER_OPTION_REPROJECT_USE_LANCZOS_TYPE 0 // Reference
+#endif
+
+FfxFloat32x4 WrapHistory(FfxInt32x2 iPxSample)
+{
+ return LoadHistory(iPxSample);
+}
+
+DeclareCustomFetchBicubicSamples(FetchHistorySamples, WrapHistory)
+DeclareCustomTextureSample(HistorySample, FFX_FSR3UPSCALER_GET_LANCZOS_SAMPLER1D(FFX_FSR3UPSCALER_OPTION_REPROJECT_USE_LANCZOS_TYPE), FetchHistorySamples)
+
+#if FFX_HALF
+FFX_MIN16_F4 WrapHistory16(FfxInt32x2 iPxSample)
+{
+ return FFX_MIN16_F4(LoadHistory(iPxSample));
+}
+
+DeclareCustomFetchBicubicSamplesMin16(FetchHistorySamples16, WrapHistory16)
+DeclareCustomTextureSampleMin16(HistorySample16, FFX_FSR3UPSCALER_GET_LANCZOS_SAMPLER1D(FFX_FSR3UPSCALER_OPTION_REPROJECT_USE_LANCZOS_TYPE), FetchHistorySamples16)
+#endif
+
+FfxFloat32x2 GetMotionVector(FfxInt32x2 iPxHrPos, FfxFloat32x2 fHrUv)
+{
+#if FFX_FSR3UPSCALER_OPTION_LOW_RESOLUTION_MOTION_VECTORS
+ const FfxFloat32x2 fDilatedMotionVector = LoadDilatedMotionVector(FFX_MIN16_I2(fHrUv * RenderSize()));
+#else
+ const FfxFloat32x2 fDilatedMotionVector = LoadInputMotionVector(iPxHrPos);
+#endif
+
+ return fDilatedMotionVector;
+}
+
+void ComputeReprojectedUVs(FFX_PARAMETER_INOUT AccumulationPassCommonParams params)
+{
+ params.fReprojectedHrUv = params.fHrUv + params.fMotionVector;
+
+ params.bIsExistingSample = IsUvInside(params.fReprojectedHrUv);
+}
+
+void ReprojectHistoryColor(const AccumulationPassCommonParams params, FFX_PARAMETER_INOUT AccumulationPassData data)
+
+{
+#if FFX_HALF && FFX_FSR3UPSCALER_OPTION_REPROJECT_SAMPLERS_USE_DATA_HALF
+ const FfxFloat32x4 fReprojectedHistory = FfxFloat32x4(HistorySample16(params.fReprojectedHrUv, UpscaleSize()));
+#else
+ const FfxFloat32x4 fReprojectedHistory = HistorySample(params.fReprojectedHrUv, PreviousFrameUpscaleSize());
+#endif
+
+ data.fHistoryColor = fReprojectedHistory.rgb;
+ data.fHistoryColor *= DeltaPreExposure();
+ data.fHistoryColor *= Exposure();
+
+ data.fHistoryColor = RGBToYCoCg(data.fHistoryColor);
+
+ data.fLock = fReprojectedHistory.w;
+}
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_resources.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_resources.h
new file mode 100644
index 0000000000..b3d8ddb17a
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_resources.h
@@ -0,0 +1,100 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#ifndef FFX_FSR3UPSCALER_RESOURCES_H
+#define FFX_FSR3UPSCALER_RESOURCES_H
+
+#if defined(FFX_CPU) || defined(FFX_GPU)
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_NULL 0
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_INPUT_OPAQUE_ONLY 1
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_INPUT_COLOR 2
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_INPUT_MOTION_VECTORS 3
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_INPUT_DEPTH 4
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_INPUT_EXPOSURE 5
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_INPUT_REACTIVE_MASK 6
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_INPUT_TRANSPARENCY_AND_COMPOSITION_MASK 7
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_RECONSTRUCTED_PREVIOUS_NEAREST_DEPTH 8
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_DILATED_MOTION_VECTORS 9
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_DILATED_DEPTH 10
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_INTERNAL_UPSCALED_COLOR 11
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_ACCUMULATION 12
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_NEW_LOCKS 13
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_LUMA_HISTORY 14
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_DEBUG_OUTPUT 15
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_LANCZOS_LUT 16
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SPD_ATOMIC_COUNT 17
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_UPSCALED_OUTPUT 18
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_RCAS_INPUT 19
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_ACCUMULATION_1 20
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_ACCUMULATION_2 21
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_INTERNAL_UPSCALED_COLOR_1 22
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_INTERNAL_UPSCALED_COLOR_2 23
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_INTERNAL_DEFAULT_REACTIVITY 24
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_INTERNAL_DEFAULT_TRANSPARENCY_AND_COMPOSITION 25
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_DILATED_REACTIVE_MASKS 26
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SPD_MIPS 27 // same as FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SPD_MIPS_LEVEL_0
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SPD_MIPS_LEVEL_0 27
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SPD_MIPS_LEVEL_1 28
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SPD_MIPS_LEVEL_2 29
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SPD_MIPS_LEVEL_3 30
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SPD_MIPS_LEVEL_4 31
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SPD_MIPS_LEVEL_5 32
+
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_INTERNAL_DEFAULT_EXPOSURE 33
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_FRAME_INFO 34
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_AUTOREACTIVE 35
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_AUTOCOMPOSITION_DEPRECATED 36
+
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_LUMA_HISTORY_1 37
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_LUMA_HISTORY_2 38
+
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_LUMA_1 40
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_LUMA_2 41
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SHADING_CHANGE 42
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_FARTHEST_DEPTH 43
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_FARTHEST_DEPTH_MIP1 44
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_CURRENT_LUMA 45
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_PREVIOUS_LUMA 46
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_LUMA_INSTABILITY 48
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_INTERMEDIATE_FP16x1 49
+
+
+// Shading change detection mip level setting, value must be in the range [FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SCENE_LUMINANCE_MIPMAP_0, FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SCENE_LUMINANCE_MIPMAP_12]
+//#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SCENE_LUMINANCE_MIPMAP_SHADING_CHANGE FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SCENE_LUMINANCE_MIPMAP_4
+//#define FFX_FSR3UPSCALER_SHADING_CHANGE_MIP_LEVEL (FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SCENE_LUMINANCE_MIPMAP_SHADING_CHANGE - FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_SCENE_LUMINANCE)
+
+#define FFX_FSR3UPSCALER_RESOURCE_IDENTIFIER_COUNT 60
+
+#define FFX_FSR3UPSCALER_CONSTANTBUFFER_IDENTIFIER_FSR3UPSCALER 0
+#define FFX_FSR3UPSCALER_CONSTANTBUFFER_IDENTIFIER_SPD 1
+#define FFX_FSR3UPSCALER_CONSTANTBUFFER_IDENTIFIER_RCAS 2
+#define FFX_FSR3UPSCALER_CONSTANTBUFFER_IDENTIFIER_GENREACTIVE 3
+#define FFX_FSR3UPSCALER_CONSTANTBUFFER_COUNT 4
+
+#define FFX_FSR3UPSCALER_AUTOREACTIVEFLAGS_APPLY_TONEMAP 1
+#define FFX_FSR3UPSCALER_AUTOREACTIVEFLAGS_APPLY_INVERSETONEMAP 2
+#define FFX_FSR3UPSCALER_AUTOREACTIVEFLAGS_APPLY_THRESHOLD 4
+#define FFX_FSR3UPSCALER_AUTOREACTIVEFLAGS_USE_COMPONENTS_MAX 8
+
+#endif // #if defined(FFX_CPU) || defined(FFX_GPU)
+
+#endif //!defined( FFX_FSR3UPSCALER_RESOURCES_H )
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_sample.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_sample.h
new file mode 100644
index 0000000000..7a723d5562
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_sample.h
@@ -0,0 +1,628 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#ifndef FFX_FSR3UPSCALER_SAMPLE_H
+#define FFX_FSR3UPSCALER_SAMPLE_H
+
+// suppress warnings
+#ifdef FFX_HLSL
+#pragma warning(disable: 4008) // potentially divide by zero
+#endif //FFX_HLSL
+
+struct FetchedBilinearSamples {
+
+ FfxFloat32x4 fColor00;
+ FfxFloat32x4 fColor10;
+
+ FfxFloat32x4 fColor01;
+ FfxFloat32x4 fColor11;
+};
+
+struct FetchedBicubicSamples {
+
+ FfxFloat32x4 fColor00;
+ FfxFloat32x4 fColor10;
+ FfxFloat32x4 fColor20;
+ FfxFloat32x4 fColor30;
+
+ FfxFloat32x4 fColor01;
+ FfxFloat32x4 fColor11;
+ FfxFloat32x4 fColor21;
+ FfxFloat32x4 fColor31;
+
+ FfxFloat32x4 fColor02;
+ FfxFloat32x4 fColor12;
+ FfxFloat32x4 fColor22;
+ FfxFloat32x4 fColor32;
+
+ FfxFloat32x4 fColor03;
+ FfxFloat32x4 fColor13;
+ FfxFloat32x4 fColor23;
+ FfxFloat32x4 fColor33;
+};
+
+#if FFX_HALF
+struct FetchedBilinearSamplesMin16 {
+
+ FFX_MIN16_F4 fColor00;
+ FFX_MIN16_F4 fColor10;
+
+ FFX_MIN16_F4 fColor01;
+ FFX_MIN16_F4 fColor11;
+};
+
+struct FetchedBicubicSamplesMin16 {
+
+ FFX_MIN16_F4 fColor00;
+ FFX_MIN16_F4 fColor10;
+ FFX_MIN16_F4 fColor20;
+ FFX_MIN16_F4 fColor30;
+
+ FFX_MIN16_F4 fColor01;
+ FFX_MIN16_F4 fColor11;
+ FFX_MIN16_F4 fColor21;
+ FFX_MIN16_F4 fColor31;
+
+ FFX_MIN16_F4 fColor02;
+ FFX_MIN16_F4 fColor12;
+ FFX_MIN16_F4 fColor22;
+ FFX_MIN16_F4 fColor32;
+
+ FFX_MIN16_F4 fColor03;
+ FFX_MIN16_F4 fColor13;
+ FFX_MIN16_F4 fColor23;
+ FFX_MIN16_F4 fColor33;
+};
+#else //FFX_HALF
+#define FetchedBicubicSamplesMin16 FetchedBicubicSamples
+#define FetchedBilinearSamplesMin16 FetchedBilinearSamples
+#endif //FFX_HALF
+
+FfxFloat32x4 Linear(FfxFloat32x4 A, FfxFloat32x4 B, FfxFloat32 t)
+{
+ return A + (B - A) * t;
+}
+
+FfxFloat32x4 Bilinear(FetchedBilinearSamples BilinearSamples, FfxFloat32x2 fPxFrac)
+{
+ FfxFloat32x4 fColorX0 = Linear(BilinearSamples.fColor00, BilinearSamples.fColor10, fPxFrac.x);
+ FfxFloat32x4 fColorX1 = Linear(BilinearSamples.fColor01, BilinearSamples.fColor11, fPxFrac.x);
+ FfxFloat32x4 fColorXY = Linear(fColorX0, fColorX1, fPxFrac.y);
+ return fColorXY;
+}
+
+#if FFX_HALF
+FFX_MIN16_F4 Linear(FFX_MIN16_F4 A, FFX_MIN16_F4 B, FFX_MIN16_F t)
+{
+ return A + (B - A) * t;
+}
+
+FFX_MIN16_F4 Bilinear(FetchedBilinearSamplesMin16 BilinearSamples, FFX_MIN16_F2 fPxFrac)
+{
+ FFX_MIN16_F4 fColorX0 = Linear(BilinearSamples.fColor00, BilinearSamples.fColor10, fPxFrac.x);
+ FFX_MIN16_F4 fColorX1 = Linear(BilinearSamples.fColor01, BilinearSamples.fColor11, fPxFrac.x);
+ FFX_MIN16_F4 fColorXY = Linear(fColorX0, fColorX1, fPxFrac.y);
+ return fColorXY;
+}
+#endif
+
+FfxFloat32 Lanczos2NoClamp(FfxFloat32 x)
+{
+ const FfxFloat32 PI = 3.141592653589793f; // TODO: share SDK constants
+ return abs(x) < FSR3UPSCALER_EPSILON ? 1.f : (sin(PI * x) / (PI * x)) * (sin(0.5f * PI * x) / (0.5f * PI * x));
+}
+
+FfxFloat32 Lanczos2(FfxFloat32 x)
+{
+ x = ffxMin(abs(x), 2.0f);
+ return Lanczos2NoClamp(x);
+}
+
+#if FFX_HALF
+
+#if 0
+FFX_MIN16_F Lanczos2NoClamp(FFX_MIN16_F x)
+{
+ const FFX_MIN16_F PI = FFX_MIN16_F(3.141592653589793f); // TODO: share SDK constants
+ return abs(x) < FFX_MIN16_F(FSR3UPSCALER_EPSILON) ? FFX_MIN16_F(1.f) : (sin(PI * x) / (PI * x)) * (sin(FFX_MIN16_F(0.5f) * PI * x) / (FFX_MIN16_F(0.5f) * PI * x));
+}
+#endif
+
+FFX_MIN16_F Lanczos2(FFX_MIN16_F x)
+{
+ x = ffxMin(abs(x), FFX_MIN16_F(2.0f));
+ return FFX_MIN16_F(Lanczos2NoClamp(x));
+}
+#endif //FFX_HALF
+
+// FSR1 lanczos approximation. Input is x*x and must be <= 4.
+FfxFloat32 Lanczos2ApproxSqNoClamp(FfxFloat32 x2)
+{
+ FfxFloat32 a = (2.0f / 5.0f) * x2 - 1;
+ FfxFloat32 b = (1.0f / 4.0f) * x2 - 1;
+ return ((25.0f / 16.0f) * a * a - (25.0f / 16.0f - 1)) * (b * b);
+}
+
+#if FFX_HALF
+FFX_MIN16_F Lanczos2ApproxSqNoClamp(FFX_MIN16_F x2)
+{
+ FFX_MIN16_F a = FFX_MIN16_F(2.0f / 5.0f) * x2 - FFX_MIN16_F(1);
+ FFX_MIN16_F b = FFX_MIN16_F(1.0f / 4.0f) * x2 - FFX_MIN16_F(1);
+ return (FFX_MIN16_F(25.0f / 16.0f) * a * a - FFX_MIN16_F(25.0f / 16.0f - 1)) * (b * b);
+}
+
+#if defined(__XBOX_SCARLETT) && defined(__XBATG_EXTRA_16_BIT_OPTIMISATION) && (__XBATG_EXTRA_16_BIT_OPTIMISATION == 1)
+FFX_MIN16_F2 PairedLanczos2ApproxSqNoClamp(FFX_MIN16_F2 x2)
+{
+ // Xbox ATG (Pavel):
+ //
+ // 2.0 * x2 - 5.0 25.0 25.0 - 16.0 (2.0 * x2 - 5.0)^2 - (3.0)^2 (2.0 * x2 - 8.0) * (2.0 * x2 - 2.0) (x2 - 4.0) * (x2 - 1.0)
+ // a = -------------- ==> ---- * a^2 - -------------- = ----------------------------- = ---------------------------------- = ----------------------- = b * (x2 - 1.0)
+ // 5.0 16.0 16.0 16.0 16.0 4.0
+ //
+ // so we need to compute just (b * b) * (b * x2 - b), so we should get four packed instructions: 2 fma + 2 mul
+ //
+
+ FFX_MIN16_F2 b = (0.25 * x2 - 1.0);
+ return (b * b) * (b * x2 - b);
+}
+#endif
+
+#endif //FFX_HALF
+
+FfxFloat32 Lanczos2ApproxSq(FfxFloat32 x2)
+{
+ x2 = ffxMin(x2, 4.0f);
+ return Lanczos2ApproxSqNoClamp(x2);
+}
+
+#if FFX_HALF
+FFX_MIN16_F Lanczos2ApproxSq(FFX_MIN16_F x2)
+{
+ x2 = ffxMin(x2, FFX_MIN16_F(4.0f));
+ return Lanczos2ApproxSqNoClamp(x2);
+}
+
+#if defined(__XBOX_SCARLETT) && defined(__XBATG_EXTRA_16_BIT_OPTIMISATION) && (__XBATG_EXTRA_16_BIT_OPTIMISATION == 1)
+FFX_MIN16_F2 PairedLanczos2ApproxSq(FFX_MIN16_F2 x2)
+{
+ x2 = ffxMin(x2, FFX_MIN16_F2(4.0, 4.0));
+ return PairedLanczos2ApproxSqNoClamp(x2);
+}
+#endif
+#endif //FFX_HALF
+
+FfxFloat32 Lanczos2ApproxNoClamp(FfxFloat32 x)
+{
+ return Lanczos2ApproxSqNoClamp(x * x);
+}
+
+#if FFX_HALF
+FFX_MIN16_F Lanczos2ApproxNoClamp(FFX_MIN16_F x)
+{
+ return Lanczos2ApproxSqNoClamp(x * x);
+}
+#endif //FFX_HALF
+
+FfxFloat32 Lanczos2Approx(FfxFloat32 x)
+{
+ return Lanczos2ApproxSq(x * x);
+}
+
+#if FFX_HALF
+FFX_MIN16_F Lanczos2Approx(FFX_MIN16_F x)
+{
+ return Lanczos2ApproxSq(x * x);
+}
+#endif //FFX_HALF
+
+FfxFloat32 Lanczos2_UseLUT(FfxFloat32 x)
+{
+ return SampleLanczos2Weight(abs(x));
+}
+
+#if FFX_HALF
+FFX_MIN16_F Lanczos2_UseLUT(FFX_MIN16_F x)
+{
+ return FFX_MIN16_F(SampleLanczos2Weight(abs(x)));
+}
+#endif //FFX_HALF
+
+FfxFloat32x4 Lanczos2_UseLUT(FfxFloat32x4 fColor0, FfxFloat32x4 fColor1, FfxFloat32x4 fColor2, FfxFloat32x4 fColor3, FfxFloat32 t)
+{
+ FfxFloat32 fWeight0 = Lanczos2_UseLUT(-1.f - t);
+ FfxFloat32 fWeight1 = Lanczos2_UseLUT(-0.f - t);
+ FfxFloat32 fWeight2 = Lanczos2_UseLUT(+1.f - t);
+ FfxFloat32 fWeight3 = Lanczos2_UseLUT(+2.f - t);
+ return (fWeight0 * fColor0 + fWeight1 * fColor1 + fWeight2 * fColor2 + fWeight3 * fColor3) / (fWeight0 + fWeight1 + fWeight2 + fWeight3);
+}
+#if FFX_HALF
+FFX_MIN16_F4 Lanczos2_UseLUT(FFX_MIN16_F4 fColor0, FFX_MIN16_F4 fColor1, FFX_MIN16_F4 fColor2, FFX_MIN16_F4 fColor3, FFX_MIN16_F t)
+{
+ FFX_MIN16_F fWeight0 = Lanczos2_UseLUT(FFX_MIN16_F(-1.f) - t);
+ FFX_MIN16_F fWeight1 = Lanczos2_UseLUT(FFX_MIN16_F(-0.f) - t);
+ FFX_MIN16_F fWeight2 = Lanczos2_UseLUT(FFX_MIN16_F(+1.f) - t);
+ FFX_MIN16_F fWeight3 = Lanczos2_UseLUT(FFX_MIN16_F(+2.f) - t);
+ return (fWeight0 * fColor0 + fWeight1 * fColor1 + fWeight2 * fColor2 + fWeight3 * fColor3) / (fWeight0 + fWeight1 + fWeight2 + fWeight3);
+}
+#endif
+
+FfxFloat32x4 Lanczos2(FfxFloat32x4 fColor0, FfxFloat32x4 fColor1, FfxFloat32x4 fColor2, FfxFloat32x4 fColor3, FfxFloat32 t)
+{
+ FfxFloat32 fWeight0 = Lanczos2(-1.f - t);
+ FfxFloat32 fWeight1 = Lanczos2(-0.f - t);
+ FfxFloat32 fWeight2 = Lanczos2(+1.f - t);
+ FfxFloat32 fWeight3 = Lanczos2(+2.f - t);
+ return (fWeight0 * fColor0 + fWeight1 * fColor1 + fWeight2 * fColor2 + fWeight3 * fColor3) / (fWeight0 + fWeight1 + fWeight2 + fWeight3);
+}
+
+FfxFloat32x4 Lanczos2(FetchedBicubicSamples Samples, FfxFloat32x2 fPxFrac)
+{
+ FfxFloat32x4 fColorX0 = Lanczos2(Samples.fColor00, Samples.fColor10, Samples.fColor20, Samples.fColor30, fPxFrac.x);
+ FfxFloat32x4 fColorX1 = Lanczos2(Samples.fColor01, Samples.fColor11, Samples.fColor21, Samples.fColor31, fPxFrac.x);
+ FfxFloat32x4 fColorX2 = Lanczos2(Samples.fColor02, Samples.fColor12, Samples.fColor22, Samples.fColor32, fPxFrac.x);
+ FfxFloat32x4 fColorX3 = Lanczos2(Samples.fColor03, Samples.fColor13, Samples.fColor23, Samples.fColor33, fPxFrac.x);
+ FfxFloat32x4 fColorXY = Lanczos2(fColorX0, fColorX1, fColorX2, fColorX3, fPxFrac.y);
+
+ // Deringing
+
+ // TODO: only use 4 by checking jitter
+ const FfxInt32 iDeringingSampleCount = 4;
+ const FfxFloat32x4 fDeringingSamples[4] = {
+ Samples.fColor11,
+ Samples.fColor21,
+ Samples.fColor12,
+ Samples.fColor22,
+ };
+
+ FfxFloat32x4 fDeringingMin = fDeringingSamples[0];
+ FfxFloat32x4 fDeringingMax = fDeringingSamples[0];
+
+ FFX_UNROLL
+ for (FfxInt32 iSampleIndex = 1; iSampleIndex < iDeringingSampleCount; ++iSampleIndex) {
+
+ fDeringingMin = ffxMin(fDeringingMin, fDeringingSamples[iSampleIndex]);
+ fDeringingMax = ffxMax(fDeringingMax, fDeringingSamples[iSampleIndex]);
+ }
+
+ fColorXY = clamp(fColorXY, fDeringingMin, fDeringingMax);
+
+ return fColorXY;
+}
+
+#if FFX_HALF
+FFX_MIN16_F4 Lanczos2(FFX_MIN16_F4 fColor0, FFX_MIN16_F4 fColor1, FFX_MIN16_F4 fColor2, FFX_MIN16_F4 fColor3, FFX_MIN16_F t)
+{
+ FFX_MIN16_F fWeight0 = Lanczos2(FFX_MIN16_F(-1.f) - t);
+ FFX_MIN16_F fWeight1 = Lanczos2(FFX_MIN16_F(-0.f) - t);
+ FFX_MIN16_F fWeight2 = Lanczos2(FFX_MIN16_F(+1.f) - t);
+ FFX_MIN16_F fWeight3 = Lanczos2(FFX_MIN16_F(+2.f) - t);
+ return (fWeight0 * fColor0 + fWeight1 * fColor1 + fWeight2 * fColor2 + fWeight3 * fColor3) / (fWeight0 + fWeight1 + fWeight2 + fWeight3);
+}
+
+FFX_MIN16_F4 Lanczos2(FetchedBicubicSamplesMin16 Samples, FFX_MIN16_F2 fPxFrac)
+{
+ FFX_MIN16_F4 fColorX0 = Lanczos2(Samples.fColor00, Samples.fColor10, Samples.fColor20, Samples.fColor30, fPxFrac.x);
+ FFX_MIN16_F4 fColorX1 = Lanczos2(Samples.fColor01, Samples.fColor11, Samples.fColor21, Samples.fColor31, fPxFrac.x);
+ FFX_MIN16_F4 fColorX2 = Lanczos2(Samples.fColor02, Samples.fColor12, Samples.fColor22, Samples.fColor32, fPxFrac.x);
+ FFX_MIN16_F4 fColorX3 = Lanczos2(Samples.fColor03, Samples.fColor13, Samples.fColor23, Samples.fColor33, fPxFrac.x);
+ FFX_MIN16_F4 fColorXY = Lanczos2(fColorX0, fColorX1, fColorX2, fColorX3, fPxFrac.y);
+
+ // Deringing
+
+ // TODO: only use 4 by checking jitter
+ const FfxInt32 iDeringingSampleCount = 4;
+ const FFX_MIN16_F4 fDeringingSamples[4] = {
+ Samples.fColor11,
+ Samples.fColor21,
+ Samples.fColor12,
+ Samples.fColor22,
+ };
+
+ FFX_MIN16_F4 fDeringingMin = fDeringingSamples[0];
+ FFX_MIN16_F4 fDeringingMax = fDeringingSamples[0];
+
+ FFX_UNROLL
+ for (FfxInt32 iSampleIndex = 1; iSampleIndex < iDeringingSampleCount; ++iSampleIndex)
+ {
+ fDeringingMin = ffxMin(fDeringingMin, fDeringingSamples[iSampleIndex]);
+ fDeringingMax = ffxMax(fDeringingMax, fDeringingSamples[iSampleIndex]);
+ }
+
+ fColorXY = clamp(fColorXY, fDeringingMin, fDeringingMax);
+
+ return fColorXY;
+}
+#endif //FFX_HALF
+
+
+FfxFloat32x4 Lanczos2LUT(FetchedBicubicSamples Samples, FfxFloat32x2 fPxFrac)
+{
+ FfxFloat32x4 fColorX0 = Lanczos2_UseLUT(Samples.fColor00, Samples.fColor10, Samples.fColor20, Samples.fColor30, fPxFrac.x);
+ FfxFloat32x4 fColorX1 = Lanczos2_UseLUT(Samples.fColor01, Samples.fColor11, Samples.fColor21, Samples.fColor31, fPxFrac.x);
+ FfxFloat32x4 fColorX2 = Lanczos2_UseLUT(Samples.fColor02, Samples.fColor12, Samples.fColor22, Samples.fColor32, fPxFrac.x);
+ FfxFloat32x4 fColorX3 = Lanczos2_UseLUT(Samples.fColor03, Samples.fColor13, Samples.fColor23, Samples.fColor33, fPxFrac.x);
+ FfxFloat32x4 fColorXY = Lanczos2_UseLUT(fColorX0, fColorX1, fColorX2, fColorX3, fPxFrac.y);
+
+ // Deringing
+
+ // TODO: only use 4 by checking jitter
+ const FfxInt32 iDeringingSampleCount = 4;
+ const FfxFloat32x4 fDeringingSamples[4] = {
+ Samples.fColor11,
+ Samples.fColor21,
+ Samples.fColor12,
+ Samples.fColor22,
+ };
+
+ FfxFloat32x4 fDeringingMin = fDeringingSamples[0];
+ FfxFloat32x4 fDeringingMax = fDeringingSamples[0];
+
+ FFX_UNROLL
+ for (FfxInt32 iSampleIndex = 1; iSampleIndex < iDeringingSampleCount; ++iSampleIndex) {
+
+ fDeringingMin = ffxMin(fDeringingMin, fDeringingSamples[iSampleIndex]);
+ fDeringingMax = ffxMax(fDeringingMax, fDeringingSamples[iSampleIndex]);
+ }
+
+ fColorXY = clamp(fColorXY, fDeringingMin, fDeringingMax);
+
+ return fColorXY;
+}
+
+#if FFX_HALF
+FFX_MIN16_F4 Lanczos2LUT(FetchedBicubicSamplesMin16 Samples, FFX_MIN16_F2 fPxFrac)
+{
+ FFX_MIN16_F4 fColorX0 = Lanczos2_UseLUT(Samples.fColor00, Samples.fColor10, Samples.fColor20, Samples.fColor30, fPxFrac.x);
+ FFX_MIN16_F4 fColorX1 = Lanczos2_UseLUT(Samples.fColor01, Samples.fColor11, Samples.fColor21, Samples.fColor31, fPxFrac.x);
+ FFX_MIN16_F4 fColorX2 = Lanczos2_UseLUT(Samples.fColor02, Samples.fColor12, Samples.fColor22, Samples.fColor32, fPxFrac.x);
+ FFX_MIN16_F4 fColorX3 = Lanczos2_UseLUT(Samples.fColor03, Samples.fColor13, Samples.fColor23, Samples.fColor33, fPxFrac.x);
+ FFX_MIN16_F4 fColorXY = Lanczos2_UseLUT(fColorX0, fColorX1, fColorX2, fColorX3, fPxFrac.y);
+
+ // Deringing
+
+ // TODO: only use 4 by checking jitter
+ const FfxInt32 iDeringingSampleCount = 4;
+ const FFX_MIN16_F4 fDeringingSamples[4] = {
+ Samples.fColor11,
+ Samples.fColor21,
+ Samples.fColor12,
+ Samples.fColor22,
+ };
+
+ FFX_MIN16_F4 fDeringingMin = fDeringingSamples[0];
+ FFX_MIN16_F4 fDeringingMax = fDeringingSamples[0];
+
+ FFX_UNROLL
+ for (FfxInt32 iSampleIndex = 1; iSampleIndex < iDeringingSampleCount; ++iSampleIndex)
+ {
+ fDeringingMin = ffxMin(fDeringingMin, fDeringingSamples[iSampleIndex]);
+ fDeringingMax = ffxMax(fDeringingMax, fDeringingSamples[iSampleIndex]);
+ }
+
+ fColorXY = clamp(fColorXY, fDeringingMin, fDeringingMax);
+
+ return fColorXY;
+}
+#endif //FFX_HALF
+
+
+
+FfxFloat32x4 Lanczos2Approx(FfxFloat32x4 fColor0, FfxFloat32x4 fColor1, FfxFloat32x4 fColor2, FfxFloat32x4 fColor3, FfxFloat32 t)
+{
+ FfxFloat32 fWeight0 = Lanczos2ApproxNoClamp(-1.f - t);
+ FfxFloat32 fWeight1 = Lanczos2ApproxNoClamp(-0.f - t);
+ FfxFloat32 fWeight2 = Lanczos2ApproxNoClamp(+1.f - t);
+ FfxFloat32 fWeight3 = Lanczos2ApproxNoClamp(+2.f - t);
+ return (fWeight0 * fColor0 + fWeight1 * fColor1 + fWeight2 * fColor2 + fWeight3 * fColor3) / (fWeight0 + fWeight1 + fWeight2 + fWeight3);
+}
+
+#if FFX_HALF
+FFX_MIN16_F4 Lanczos2Approx(FFX_MIN16_F4 fColor0, FFX_MIN16_F4 fColor1, FFX_MIN16_F4 fColor2, FFX_MIN16_F4 fColor3, FFX_MIN16_F t)
+{
+ FFX_MIN16_F fWeight0 = Lanczos2ApproxNoClamp(FFX_MIN16_F(-1.f) - t);
+ FFX_MIN16_F fWeight1 = Lanczos2ApproxNoClamp(FFX_MIN16_F(-0.f) - t);
+ FFX_MIN16_F fWeight2 = Lanczos2ApproxNoClamp(FFX_MIN16_F(+1.f) - t);
+ FFX_MIN16_F fWeight3 = Lanczos2ApproxNoClamp(FFX_MIN16_F(+2.f) - t);
+ return (fWeight0 * fColor0 + fWeight1 * fColor1 + fWeight2 * fColor2 + fWeight3 * fColor3) / (fWeight0 + fWeight1 + fWeight2 + fWeight3);
+}
+#endif //FFX_HALF
+
+FfxFloat32x4 Lanczos2Approx(FetchedBicubicSamples Samples, FfxFloat32x2 fPxFrac)
+{
+ FfxFloat32x4 fColorX0 = Lanczos2Approx(Samples.fColor00, Samples.fColor10, Samples.fColor20, Samples.fColor30, fPxFrac.x);
+ FfxFloat32x4 fColorX1 = Lanczos2Approx(Samples.fColor01, Samples.fColor11, Samples.fColor21, Samples.fColor31, fPxFrac.x);
+ FfxFloat32x4 fColorX2 = Lanczos2Approx(Samples.fColor02, Samples.fColor12, Samples.fColor22, Samples.fColor32, fPxFrac.x);
+ FfxFloat32x4 fColorX3 = Lanczos2Approx(Samples.fColor03, Samples.fColor13, Samples.fColor23, Samples.fColor33, fPxFrac.x);
+ FfxFloat32x4 fColorXY = Lanczos2Approx(fColorX0, fColorX1, fColorX2, fColorX3, fPxFrac.y);
+
+ // Deringing
+
+ // TODO: only use 4 by checking jitter
+ const FfxInt32 iDeringingSampleCount = 4;
+ const FfxFloat32x4 fDeringingSamples[4] = {
+ Samples.fColor11,
+ Samples.fColor21,
+ Samples.fColor12,
+ Samples.fColor22,
+ };
+
+ FfxFloat32x4 fDeringingMin = fDeringingSamples[0];
+ FfxFloat32x4 fDeringingMax = fDeringingSamples[0];
+
+ FFX_UNROLL
+ for (FfxInt32 iSampleIndex = 1; iSampleIndex < iDeringingSampleCount; ++iSampleIndex)
+ {
+ fDeringingMin = ffxMin(fDeringingMin, fDeringingSamples[iSampleIndex]);
+ fDeringingMax = ffxMax(fDeringingMax, fDeringingSamples[iSampleIndex]);
+ }
+
+ fColorXY = clamp(fColorXY, fDeringingMin, fDeringingMax);
+
+ return fColorXY;
+}
+
+#if FFX_HALF
+FFX_MIN16_F4 Lanczos2Approx(FetchedBicubicSamplesMin16 Samples, FFX_MIN16_F2 fPxFrac)
+{
+ FFX_MIN16_F4 fColorX0 = Lanczos2Approx(Samples.fColor00, Samples.fColor10, Samples.fColor20, Samples.fColor30, fPxFrac.x);
+ FFX_MIN16_F4 fColorX1 = Lanczos2Approx(Samples.fColor01, Samples.fColor11, Samples.fColor21, Samples.fColor31, fPxFrac.x);
+ FFX_MIN16_F4 fColorX2 = Lanczos2Approx(Samples.fColor02, Samples.fColor12, Samples.fColor22, Samples.fColor32, fPxFrac.x);
+ FFX_MIN16_F4 fColorX3 = Lanczos2Approx(Samples.fColor03, Samples.fColor13, Samples.fColor23, Samples.fColor33, fPxFrac.x);
+ FFX_MIN16_F4 fColorXY = Lanczos2Approx(fColorX0, fColorX1, fColorX2, fColorX3, fPxFrac.y);
+
+ // Deringing
+
+ // TODO: only use 4 by checking jitter
+ const FfxInt32 iDeringingSampleCount = 4;
+ const FFX_MIN16_F4 fDeringingSamples[4] = {
+ Samples.fColor11,
+ Samples.fColor21,
+ Samples.fColor12,
+ Samples.fColor22,
+ };
+
+ FFX_MIN16_F4 fDeringingMin = fDeringingSamples[0];
+ FFX_MIN16_F4 fDeringingMax = fDeringingSamples[0];
+
+ FFX_UNROLL
+ for (FfxInt32 iSampleIndex = 1; iSampleIndex < iDeringingSampleCount; ++iSampleIndex)
+ {
+ fDeringingMin = ffxMin(fDeringingMin, fDeringingSamples[iSampleIndex]);
+ fDeringingMax = ffxMax(fDeringingMax, fDeringingSamples[iSampleIndex]);
+ }
+
+ fColorXY = clamp(fColorXY, fDeringingMin, fDeringingMax);
+
+ return fColorXY;
+}
+#endif
+
+// Clamp by offset direction. Assuming iPxSample is already in range and iPxOffset is compile time constant.
+FfxInt32x2 ClampCoord(FfxInt32x2 iPxSample, FfxInt32x2 iPxOffset, FfxInt32x2 iTextureSize)
+{
+ FfxInt32x2 result = iPxSample + iPxOffset;
+ result.x = ffxMax(1, ffxMin(result.x, iTextureSize.x - 2));
+ result.y = ffxMax(1, ffxMin(result.y, iTextureSize.y - 2));
+ return result;
+}
+#if FFX_HALF
+FFX_MIN16_I2 ClampCoord(FFX_MIN16_I2 iPxSample, FFX_MIN16_I2 iPxOffset, FFX_MIN16_I2 iTextureSize)
+{
+ FFX_MIN16_I2 result = iPxSample + iPxOffset;
+ result.x = ffxMax(FFX_MIN16_I(1), ffxMin(result.x, iTextureSize.x - FFX_MIN16_I(2)));
+ result.y = ffxMax(FFX_MIN16_I(1), ffxMin(result.y, iTextureSize.y - FFX_MIN16_I(2)));
+ return result;
+}
+#endif //FFX_HALF
+
+
+#define DeclareCustomFetchBicubicSamplesWithType(SampleType, TextureType, AddrType, Name, LoadTexture) \
+ SampleType Name(AddrType iPxSample, AddrType iTextureSize) \
+ { \
+ SampleType Samples; \
+ \
+ Samples.fColor00 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(-1, -1), iTextureSize))); \
+ Samples.fColor10 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+0, -1), iTextureSize))); \
+ Samples.fColor20 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+1, -1), iTextureSize))); \
+ Samples.fColor30 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+2, -1), iTextureSize))); \
+ \
+ Samples.fColor01 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(-1, +0), iTextureSize))); \
+ Samples.fColor11 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+0, +0), iTextureSize))); \
+ Samples.fColor21 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+1, +0), iTextureSize))); \
+ Samples.fColor31 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+2, +0), iTextureSize))); \
+ \
+ Samples.fColor02 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(-1, +1), iTextureSize))); \
+ Samples.fColor12 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+0, +1), iTextureSize))); \
+ Samples.fColor22 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+1, +1), iTextureSize))); \
+ Samples.fColor32 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+2, +1), iTextureSize))); \
+ \
+ Samples.fColor03 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(-1, +2), iTextureSize))); \
+ Samples.fColor13 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+0, +2), iTextureSize))); \
+ Samples.fColor23 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+1, +2), iTextureSize))); \
+ Samples.fColor33 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+2, +2), iTextureSize))); \
+ \
+ return Samples; \
+ }
+
+#define DeclareCustomFetchBicubicSamples(Name, LoadTexture) \
+ DeclareCustomFetchBicubicSamplesWithType(FetchedBicubicSamples, FfxFloat32x4, FfxInt32x2, Name, LoadTexture)
+
+#define DeclareCustomFetchBicubicSamplesMin16(Name, LoadTexture) \
+ DeclareCustomFetchBicubicSamplesWithType(FetchedBicubicSamplesMin16, FFX_MIN16_F4, FfxInt32x2, Name, LoadTexture)
+
+#define DeclareCustomFetchBilinearSamplesWithType(SampleType, TextureType,AddrType, Name, LoadTexture) \
+ SampleType Name(AddrType iPxSample, AddrType iTextureSize) \
+ { \
+ SampleType Samples; \
+ Samples.fColor00 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+0, +0), iTextureSize))); \
+ Samples.fColor10 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+1, +0), iTextureSize))); \
+ Samples.fColor01 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+0, +1), iTextureSize))); \
+ Samples.fColor11 = TextureType(LoadTexture(ClampCoord(iPxSample, AddrType(+1, +1), iTextureSize))); \
+ return Samples; \
+ }
+
+#define DeclareCustomFetchBilinearSamples(Name, LoadTexture) \
+ DeclareCustomFetchBilinearSamplesWithType(FetchedBilinearSamples, FfxFloat32x4, FfxInt32x2, Name, LoadTexture)
+
+#define DeclareCustomFetchBilinearSamplesMin16(Name, LoadTexture) \
+ DeclareCustomFetchBilinearSamplesWithType(FetchedBilinearSamplesMin16, FFX_MIN16_F4, FfxInt32x2, Name, LoadTexture)
+
+// BE CAREFUL: there is some precision issues and (3253, 125) leading to (3252.9989778, 125.001102)
+// is common, so iPxSample can "jitter"
+#define DeclareCustomTextureSample(Name, InterpolateSamples, FetchSamples) \
+ FfxFloat32x4 Name(FfxFloat32x2 fUvSample, FfxInt32x2 iTextureSize) \
+ { \
+ FfxFloat32x2 fPxSample = (fUvSample * FfxFloat32x2(iTextureSize)) - FfxFloat32x2(0.5f, 0.5f); \
+ FfxFloat32x2 fPxFrac = ffxFract(fPxSample); \
+ /* Clamp base coords */ \
+ fPxSample.x = ffxMax(0.0f, ffxMin(FfxFloat32(iTextureSize.x-1), fPxSample.x)); \
+ fPxSample.y = ffxMax(0.0f, ffxMin(FfxFloat32(iTextureSize.y-1), fPxSample.y)); \
+ /* */ \
+ FfxInt32x2 iPxSample = FfxInt32x2(floor(fPxSample)); \
+ FfxFloat32x4 fColorXY = FfxFloat32x4(InterpolateSamples(FetchSamples(iPxSample, iTextureSize), fPxFrac)); \
+ return fColorXY; \
+ }
+
+#define DeclareCustomTextureSampleMin16(Name, InterpolateSamples, FetchSamples) \
+ FFX_MIN16_F4 Name(FfxFloat32x2 fUvSample, FfxInt32x2 iTextureSize) \
+ { \
+ FfxFloat32x2 fPxSample = (fUvSample * FfxFloat32x2(iTextureSize)) - FfxFloat32x2(0.5f, 0.5f); \
+ FFX_MIN16_F2 fPxFrac = FFX_MIN16_F2(ffxFract(fPxSample)); \
+ /* Clamp base coords */ \
+ fPxSample.x = ffxMax(0.0f, ffxMin(FfxFloat32(iTextureSize.x), fPxSample.x)); \
+ fPxSample.y = ffxMax(0.0f, ffxMin(FfxFloat32(iTextureSize.y), fPxSample.y)); \
+ /* */ \
+ FfxInt32x2 iPxSample = FfxInt32x2(floor(fPxSample)); \
+ FFX_MIN16_F4 fColorXY = FFX_MIN16_F4(InterpolateSamples(FetchSamples(iPxSample, iTextureSize), fPxFrac)); \
+ return fColorXY; \
+ }
+
+#define FFX_FSR3UPSCALER_CONCAT_ID(x, y) x ## y
+#define FFX_FSR3UPSCALER_CONCAT(x, y) FFX_FSR3UPSCALER_CONCAT_ID(x, y)
+#define FFX_FSR3UPSCALER_SAMPLER_1D_0 Lanczos2
+#define FFX_FSR3UPSCALER_SAMPLER_1D_1 Lanczos2LUT
+#define FFX_FSR3UPSCALER_SAMPLER_1D_2 Lanczos2Approx
+
+#define FFX_FSR3UPSCALER_GET_LANCZOS_SAMPLER1D(x) FFX_FSR3UPSCALER_CONCAT(FFX_FSR3UPSCALER_SAMPLER_1D_, x)
+
+#endif //!defined( FFX_FSR3UPSCALER_SAMPLE_H )
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_shading_change.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_shading_change.h
new file mode 100644
index 0000000000..2eb23aacdf
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_shading_change.h
@@ -0,0 +1,68 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+FFX_STATIC const FfxInt32 s_MipLevelsToUse = 3;
+
+struct ShadingChangeLumaInfo
+{
+ FfxFloat32 fSamples[s_MipLevelsToUse];
+};
+
+ShadingChangeLumaInfo ComputeShadingChangeLuma(FfxInt32x2 iPxPos, FfxFloat32x2 fUv, const FfxInt32x2 iCurrentSize)
+{
+ ShadingChangeLumaInfo info;
+
+ const FfxFloat32x2 fMipUv = ClampUv(fUv, ShadingChangeRenderSize(), GetSPDMipDimensions(0));
+
+ FFX_UNROLL
+ for (FfxInt32 iMipLevel = iShadingChangeMipStart; iMipLevel < s_MipLevelsToUse; iMipLevel++) {
+
+ const FfxFloat32x2 fSample = SampleSPDMipLevel(fMipUv, iMipLevel);
+
+ info.fSamples[iMipLevel] = abs(fSample.x * fSample.y);
+ }
+
+ return info;
+}
+
+void ShadingChange(FfxInt32x2 iPxPos)
+{
+ if (IsOnScreen(FfxInt32x2(iPxPos), ShadingChangeRenderSize())) {
+
+ const FfxFloat32x2 fUv = (iPxPos + 0.5f) / ShadingChangeRenderSize();
+ const FfxFloat32x2 fUvJittered = fUv + Jitter() / RenderSize();
+
+ const ShadingChangeLumaInfo info = ComputeShadingChangeLuma(iPxPos, fUvJittered, ShadingChangeRenderSize());
+
+ const FfxFloat32 fScale = 1.0f + iShadingChangeMipStart / s_MipLevelsToUse;
+ FfxFloat32 fShadingChange = 0.0f;
+ FFX_UNROLL
+ for (int iMipLevel = iShadingChangeMipStart; iMipLevel < s_MipLevelsToUse; iMipLevel++)
+ {
+ if (info.fSamples[iMipLevel] > 0) {
+ fShadingChange = ffxMax(fShadingChange, info.fSamples[iMipLevel]) * fScale;
+ }
+ }
+
+ StoreShadingChange(iPxPos, ffxSaturate(fShadingChange));
+ }
+}
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_shading_change_pyramid.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_shading_change_pyramid.h
new file mode 100644
index 0000000000..63ca5d6d5f
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_shading_change_pyramid.h
@@ -0,0 +1,297 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+FFX_GROUPSHARED FfxUInt32 spdCounter;
+
+void SpdIncreaseAtomicCounter(FfxUInt32 slice)
+{
+ SPD_IncreaseAtomicCounter(spdCounter);
+}
+
+FfxUInt32 SpdGetAtomicCounter()
+{
+ return spdCounter;
+}
+
+void SpdResetAtomicCounter(FfxUInt32 slice)
+{
+ SPD_ResetAtomicCounter();
+}
+
+#ifndef SPD_PACKED_ONLY
+FFX_GROUPSHARED FfxFloat32 spdIntermediateR[16][16];
+FFX_GROUPSHARED FfxFloat32 spdIntermediateG[16][16];
+FFX_GROUPSHARED FfxFloat32 spdIntermediateB[16][16];
+FFX_GROUPSHARED FfxFloat32 spdIntermediateA[16][16];
+
+FFX_STATIC const FfxInt32 DIFFERENCE = 0;
+FFX_STATIC const FfxInt32 SIGN_SUM = 1;
+FFX_STATIC const FfxInt32 MIP0_INDICATOR = 2;
+
+FfxFloat32x2 Sort2(FfxFloat32x2 v)
+{
+ return FfxFloat32x2(ffxMin(v.x, v.y), ffxMax(v.x, v.y));
+}
+
+struct SampleSet
+{
+ FfxFloat32 fSamples[SHADING_CHANGE_SET_SIZE];
+};
+
+#define CompareSwap(i, j) \
+{ \
+FfxFloat32 fTmp = ffxMin(fSet.fSamples[i], fSet.fSamples[j]);\
+fSet.fSamples[j] = ffxMax(fSet.fSamples[i], fSet.fSamples[j]);\
+fSet.fSamples[i] = fTmp;\
+}
+
+#if SHADING_CHANGE_SET_SIZE == 5
+FFX_STATIC const FfxInt32x2 iSampleOffsets[5] = {FfxInt32x2(+0, +0), FfxInt32x2(-1, +0), FfxInt32x2(+1, +0), FfxInt32x2(+0, -1), FfxInt32x2(+0, +1)};
+
+void SortSet(FFX_PARAMETER_INOUT SampleSet fSet)
+{
+ CompareSwap(0, 3);
+ CompareSwap(1, 4);
+ CompareSwap(0, 2);
+ CompareSwap(1, 3);
+ CompareSwap(0, 1);
+ CompareSwap(2, 4);
+ CompareSwap(1, 2);
+ CompareSwap(3, 4);
+ CompareSwap(2, 3);
+}
+#endif
+
+FfxFloat32 ComputeMinimumDifference(FfxInt32x2 iPxPos, SampleSet fSet0, SampleSet fSet1)
+{
+ FfxFloat32 fMinDiff = FSR3UPSCALER_FP16_MAX - 1;
+ FfxInt32 a = 0;
+ FfxInt32 b = 0;
+
+ SortSet(fSet0);
+ SortSet(fSet1);
+
+ const FfxFloat32 fMax = ffxMin(fSet0.fSamples[SHADING_CHANGE_SET_SIZE-1], fSet1.fSamples[SHADING_CHANGE_SET_SIZE-1]);
+
+ if (fMax > FSR3UPSCALER_FP32_MIN) {
+
+ FFX_UNROLL
+ for (FfxInt32 i = 0; i < SHADING_CHANGE_SET_SIZE && (fMinDiff < FSR3UPSCALER_FP16_MAX); i++) {
+
+ FfxFloat32 fDiff = fSet0.fSamples[a] - fSet1.fSamples[b];
+
+ if (abs(fDiff) > FSR3UPSCALER_FP16_MIN) {
+
+ fDiff = sign(fDiff) * (1.0f - MinDividedByMax(fSet0.fSamples[a], fSet1.fSamples[b]));
+
+ fMinDiff = (abs(fDiff) < abs(fMinDiff)) ? fDiff : fMinDiff;
+
+ a += FfxInt32(fSet0.fSamples[a] < fSet1.fSamples[b]);
+ b += FfxInt32(fSet0.fSamples[a] >= fSet1.fSamples[b]);
+ }
+ else
+ {
+ fMinDiff = FSR3UPSCALER_FP16_MAX;
+ }
+ }
+ }
+
+ return fMinDiff * FfxFloat32(fMinDiff < (FSR3UPSCALER_FP16_MAX - 1));
+}
+
+SampleSet GetCurrentLumaBilinearSamples(FfxFloat32x2 fUv)
+{
+ const FfxFloat32x2 fUvJittered = fUv + Jitter() / RenderSize();
+ const FfxInt32x2 iBasePos = FfxInt32x2(floor(fUvJittered * RenderSize()));
+
+ SampleSet fSet;
+
+ for (FfxInt32 iSampleIndex = 0; iSampleIndex < SHADING_CHANGE_SET_SIZE; iSampleIndex++) {
+ const FfxInt32x2 iSamplePos = ClampLoad(iBasePos, iSampleOffsets[iSampleIndex], RenderSize());
+ fSet.fSamples[iSampleIndex] = LoadCurrentLuma(iSamplePos) * Exposure();
+ fSet.fSamples[iSampleIndex] = ffxPow(fSet.fSamples[iSampleIndex], fShadingChangeSamplePow);
+ fSet.fSamples[iSampleIndex] = ffxMax(fSet.fSamples[iSampleIndex], FSR3UPSCALER_EPSILON);
+ }
+
+ return fSet;
+}
+
+struct PreviousLumaBilinearSamplesData
+{
+ SampleSet fSet;
+ FfxBoolean bIsExistingSample;
+};
+
+PreviousLumaBilinearSamplesData GetPreviousLumaBilinearSamples(FfxFloat32x2 fUv, FfxFloat32x2 fMotionVector)
+{
+ PreviousLumaBilinearSamplesData data;
+
+ const FfxFloat32x2 fUvJittered = fUv + PreviousFrameJitter() / PreviousFrameRenderSize();
+ const FfxFloat32x2 fReprojectedUv = fUvJittered + fMotionVector;
+
+ data.bIsExistingSample = IsUvInside(fReprojectedUv);
+
+ if (data.bIsExistingSample) {
+
+ const FfxInt32x2 iBasePos = FfxInt32x2(floor(fReprojectedUv * PreviousFrameRenderSize()));
+
+ for (FfxInt32 iSampleIndex = 0; iSampleIndex < SHADING_CHANGE_SET_SIZE; iSampleIndex++) {
+
+ const FfxInt32x2 iSamplePos = ClampLoad(iBasePos, iSampleOffsets[iSampleIndex], PreviousFrameRenderSize());
+ data.fSet.fSamples[iSampleIndex] = LoadPreviousLuma(iSamplePos) * DeltaPreExposure() * Exposure();
+ data.fSet.fSamples[iSampleIndex] = ffxPow(data.fSet.fSamples[iSampleIndex], fShadingChangeSamplePow);
+ data.fSet.fSamples[iSampleIndex] = ffxMax(data.fSet.fSamples[iSampleIndex], FSR3UPSCALER_EPSILON);
+ }
+ }
+
+ return data;
+}
+
+FfxFloat32 ComputeDiff(FfxInt32x2 iPxPos, FfxFloat32x2 fUv, FfxFloat32x2 fMotionVector)
+{
+ FfxFloat32 fMinDiff = 0.0f;
+
+ const SampleSet fCurrentSamples = GetCurrentLumaBilinearSamples(fUv);
+ const PreviousLumaBilinearSamplesData previousData = GetPreviousLumaBilinearSamples(fUv, fMotionVector);
+
+ if (previousData.bIsExistingSample) {
+ fMinDiff = ComputeMinimumDifference(iPxPos, fCurrentSamples, previousData.fSet);
+ }
+
+ return fMinDiff;
+}
+
+FfxFloat32x4 SpdLoadSourceImage(FfxFloat32x2 iPxPos, FfxUInt32 slice)
+{
+ const FfxInt32x2 iPxSamplePos = ClampLoad(FfxInt32x2(iPxPos), FfxInt32x2(0, 0), FfxInt32x2(RenderSize()));
+ const FfxFloat32x2 fDilatedMotionVector = LoadDilatedMotionVector(iPxSamplePos);
+ const FfxFloat32x2 fUv = (iPxSamplePos + 0.5f) / RenderSize();
+
+ const FfxFloat32 fScaledAndSignedLumaDiff = ComputeDiff(iPxSamplePos, fUv, fDilatedMotionVector);
+
+ FfxFloat32x4 fOutput = FfxFloat32x4(0.0f, 0.0f, 0.0f, 0.0f);
+ fOutput[DIFFERENCE] = fScaledAndSignedLumaDiff;
+ fOutput[SIGN_SUM] = (fScaledAndSignedLumaDiff != 0.0f) ? sign(fScaledAndSignedLumaDiff) : 0.0f;
+ fOutput[MIP0_INDICATOR] = 1.0f;
+
+ return fOutput;
+}
+
+FfxFloat32x4 SpdLoad(FfxInt32x2 tex, FfxUInt32 slice)
+{
+ return FfxFloat32x4(RWLoadPyramid(tex, 5), 0, 0);
+}
+
+FfxFloat32x4 SpdReduce4(FfxFloat32x4 v0, FfxFloat32x4 v1, FfxFloat32x4 v2, FfxFloat32x4 v3)
+{
+ return (v0 + v1 + v2 + v3) * 0.25f;
+}
+
+void SpdStore(FfxInt32x2 pix, FfxFloat32x4 outValue, FfxUInt32 index, FfxUInt32 slice)
+{
+ if (index >= iShadingChangeMipStart)
+ {
+ StorePyramid(pix, outValue.xy, index);
+ }
+}
+
+FfxFloat32x4 SpdLoadIntermediate(FfxUInt32 x, FfxUInt32 y)
+{
+ return FfxFloat32x4(
+ spdIntermediateR[x][y],
+ spdIntermediateG[x][y],
+ spdIntermediateB[x][y],
+ spdIntermediateA[x][y]);
+}
+void SpdStoreIntermediate(FfxUInt32 x, FfxUInt32 y, FfxFloat32x4 value)
+{
+ spdIntermediateR[x][y] = value.x;
+ spdIntermediateG[x][y] = value.y;
+ spdIntermediateB[x][y] = value.z;
+ spdIntermediateA[x][y] = value.w;
+}
+
+#endif
+
+// define fetch and store functions Packed
+#if FFX_HALF
+
+FFX_GROUPSHARED FfxFloat16x2 spdIntermediateRG[16][16];
+FFX_GROUPSHARED FfxFloat16x2 spdIntermediateBA[16][16];
+
+FfxFloat16x4 SpdLoadSourceImageH(FfxFloat32x2 tex, FfxUInt32 slice)
+{
+ return FfxFloat16x4(0, 0, 0, 0);
+}
+
+FfxFloat16x4 SpdLoadH(FfxInt32x2 p, FfxUInt32 slice)
+{
+ return FfxFloat16x4(0, 0, 0, 0);
+}
+
+void SpdStoreH(FfxInt32x2 p, FfxFloat16x4 value, FfxUInt32 mip, FfxUInt32 slice)
+{
+}
+
+FfxFloat16x4 SpdLoadIntermediateH(FfxUInt32 x, FfxUInt32 y)
+{
+ return FfxFloat16x4(
+ spdIntermediateRG[x][y].x,
+ spdIntermediateRG[x][y].y,
+ spdIntermediateBA[x][y].x,
+ spdIntermediateBA[x][y].y);
+}
+
+void SpdStoreIntermediateH(FfxUInt32 x, FfxUInt32 y, FfxFloat16x4 value)
+{
+ spdIntermediateRG[x][y] = value.xy;
+ spdIntermediateBA[x][y] = value.zw;
+}
+
+FfxFloat16x4 SpdReduce4H(FfxFloat16x4 v0, FfxFloat16x4 v1, FfxFloat16x4 v2, FfxFloat16x4 v3)
+{
+ return (v0 + v1 + v2 + v3) * FfxFloat16(0.25);
+}
+#endif
+
+#include "spd/ffx_spd.h"
+
+void ComputeShadingChangePyramid(FfxUInt32x3 WorkGroupId, FfxUInt32 LocalThreadIndex)
+{
+#if FFX_HALF
+ SpdDownsampleH(
+ FfxUInt32x2(WorkGroupId.xy),
+ FfxUInt32(LocalThreadIndex),
+ FfxUInt32(MipCount()),
+ FfxUInt32(NumWorkGroups()),
+ FfxUInt32(WorkGroupId.z),
+ FfxUInt32x2(WorkGroupOffset()));
+#else
+ SpdDownsample(
+ FfxUInt32x2(WorkGroupId.xy),
+ FfxUInt32(LocalThreadIndex),
+ FfxUInt32(MipCount()),
+ FfxUInt32(NumWorkGroups()),
+ FfxUInt32(WorkGroupId.z),
+ FfxUInt32x2(WorkGroupOffset()));
+#endif
+}
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_upsample.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_upsample.h
new file mode 100644
index 0000000000..801a0a907e
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/fsr3upscaler/ffx_fsr3upscaler_upsample.h
@@ -0,0 +1,644 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+void Deringing(RectificationBox clippingBox, FFX_PARAMETER_INOUT FfxFloat32x3 fColor)
+{
+ fColor = clamp(fColor, clippingBox.aabbMin, clippingBox.aabbMax);
+}
+
+#ifndef FFX_FSR3UPSCALER_OPTION_UPSAMPLE_USE_LANCZOS_TYPE
+#define FFX_FSR3UPSCALER_OPTION_UPSAMPLE_USE_LANCZOS_TYPE 2 // Approximate
+#endif
+
+FfxFloat32 GetUpsampleLanczosWeight(FfxFloat32x2 fSrcSampleOffset, FfxFloat32 fKernelWeight)
+{
+ FfxFloat32x2 fSrcSampleOffsetBiased = fSrcSampleOffset * fKernelWeight.xx;
+#if FFX_FSR3UPSCALER_OPTION_UPSAMPLE_USE_LANCZOS_TYPE == 0 // LANCZOS_TYPE_REFERENCE
+ FfxFloat32 fSampleWeight = Lanczos2(length(fSrcSampleOffsetBiased));
+#elif FFX_FSR3UPSCALER_OPTION_UPSAMPLE_USE_LANCZOS_TYPE == 1 // LANCZOS_TYPE_LUT
+ FfxFloat32 fSampleWeight = Lanczos2_UseLUT(length(fSrcSampleOffsetBiased));
+#elif FFX_FSR3UPSCALER_OPTION_UPSAMPLE_USE_LANCZOS_TYPE == 2 // LANCZOS_TYPE_APPROXIMATE
+ FfxFloat32 fSampleWeight = Lanczos2ApproxSq(dot(fSrcSampleOffsetBiased, fSrcSampleOffsetBiased));
+#else
+#error "Invalid Lanczos type"
+#endif
+ return fSampleWeight;
+}
+
+#if FFX_HALF
+FFX_MIN16_F GetUpsampleLanczosWeight(FFX_MIN16_F2 fSrcSampleOffset, FFX_MIN16_F fKernelWeight)
+{
+ FFX_MIN16_F2 fSrcSampleOffsetBiased = fSrcSampleOffset * fKernelWeight.xx;
+#if FFX_FSR3UPSCALER_OPTION_UPSAMPLE_USE_LANCZOS_TYPE == 0 // LANCZOS_TYPE_REFERENCE
+ FFX_MIN16_F fSampleWeight = Lanczos2(length(fSrcSampleOffsetBiased));
+#elif FFX_FSR3UPSCALER_OPTION_UPSAMPLE_USE_LANCZOS_TYPE == 1 // LANCZOS_TYPE_LUT
+ FFX_MIN16_F fSampleWeight = Lanczos2_UseLUT(length(fSrcSampleOffsetBiased));
+#elif FFX_FSR3UPSCALER_OPTION_UPSAMPLE_USE_LANCZOS_TYPE == 2 // LANCZOS_TYPE_APPROXIMATE
+ FFX_MIN16_F fSampleWeight = Lanczos2ApproxSq(dot(fSrcSampleOffsetBiased, fSrcSampleOffsetBiased));
+
+ // To Test: Save reciproqual sqrt compute
+ // FfxFloat32 fSampleWeight = Lanczos2Sq_UseLUT(dot(fSrcSampleOffsetBiased, fSrcSampleOffsetBiased));
+#else
+#error "Invalid Lanczos type"
+#endif
+ return fSampleWeight;
+}
+#endif
+
+FfxFloat32 ComputeMaxKernelWeight(const AccumulationPassCommonParams params, FFX_PARAMETER_INOUT AccumulationPassData data) {
+
+ const FfxFloat32 fKernelSizeBias = 1.0f + (1.0f / FfxFloat32x2(DownscaleFactor()) - 1.0f).x;
+
+ return ffxMin(FfxFloat32(1.99f), fKernelSizeBias);
+}
+
+FfxFloat32x3 LoadPreparedColor(FfxInt32x2 iSamplePos)
+{
+ const FfxFloat32x3 fRgb = ffxMax(FfxFloat32x3(0, 0, 0), LoadInputColor(iSamplePos)) * Exposure();
+ const FfxFloat32x3 fPreparedYCoCg = RGBToYCoCg(fRgb);
+
+ return fPreparedYCoCg;
+}
+
+#if FFX_HALF && (FFX_FSR3UPSCALER_OPTION_UPSAMPLE_USE_LANCZOS_TYPE == 2) && defined(__XBOX_SCARLETT) && defined(__XBATG_EXTRA_16_BIT_OPTIMISATION) && (__XBATG_EXTRA_16_BIT_OPTIMISATION == 1)
+#define FFX_FSR3UPSCALER_USE_XBOX_PAIRED_16BIT_MATH_OPTIMIZATIONS 1
+#else
+#define FFX_FSR3UPSCALER_USE_XBOX_PAIRED_16BIT_MATH_OPTIMIZATIONS 0
+#endif
+
+#if FFX_FSR3UPSCALER_USE_XBOX_PAIRED_16BIT_MATH_OPTIMIZATIONS
+
+void LoadPreparedColorPairedRgb(FFX_PARAMETER_OUT FFX_MIN16_F2 r,
+ FFX_PARAMETER_OUT FFX_MIN16_F2 g,
+ FFX_PARAMETER_OUT FFX_MIN16_F2 b,
+ FfxInt32x2 iSamplePos0,
+ FfxInt32x2 iSamplePos1)
+{
+ const FFX_MIN16_F3 sample0 = FFX_MIN16_F3(LoadInputColor(iSamplePos0));
+ const FFX_MIN16_F3 sample1 = FFX_MIN16_F3(LoadInputColor(iSamplePos1));
+
+ r = ffxMax(FFX_MIN16_F2(0, 0), FFX_MIN16_F2(sample0.r, sample1.r));
+ g = ffxMax(FFX_MIN16_F2(0, 0), FFX_MIN16_F2(sample0.g, sample1.g));
+ b = ffxMax(FFX_MIN16_F2(0, 0), FFX_MIN16_F2(sample0.b, sample1.b));
+
+ r = FFX_MIN16_F2(r * Exposure());
+ g = FFX_MIN16_F2(g * Exposure());
+ b = FFX_MIN16_F2(b * Exposure());
+}
+
+void TonemapPaired(FFX_PARAMETER_INOUT FFX_MIN16_F2 r, FFX_PARAMETER_INOUT FFX_MIN16_F2 g, FFX_PARAMETER_INOUT FFX_MIN16_F2 b)
+{
+ FFX_MIN16_F2 denomF16 = ffxMax(ffxMax(ffxMax(0.0, r), g), b) + FFX_MIN16_F2(1.0, 1.0);
+
+ // NOTE: expect 2 x v_cvt_f32_f16
+ FfxFloat32x2 denomF32 = FfxFloat32x2(denomF16);
+ // NOTE: expect 2 x v_rcp_f32
+ FfxFloat32x2 normF32 = FfxFloat32x2(1.0, 1.0) / denomF32;
+ // NOTE: expect 2 x v_cvt_f16_f32
+ FFX_MIN16_F2 normF16 = FFX_MIN16_F2(normF32);
+
+ r *= normF16;
+ g *= normF16;
+ b *= normF16;
+}
+
+void RGBToYCoCgPaired(FFX_PARAMETER_INOUT FFX_MIN16_F2 r, FFX_PARAMETER_INOUT FFX_MIN16_F2 g, FFX_PARAMETER_INOUT FFX_MIN16_F2 b)
+{
+ /**
+ * NOTE: given the following conversion
+ *
+ * fYCoCg = FfxFloat32x3(
+ * 0.25f * fRgb.r + 0.5f * fRgb.g + 0.25f * fRgb.b,
+ * 0.5f * fRgb.r - 0.5f * fRgb.b,
+ * -0.25f * fRgb.r + 0.5f * fRgb.g - 0.25f * fRgb.b);
+ *
+ * it's possible to notice that we can compute:
+ * RplusBdiv4 = 0.25 * (R + B)
+ *
+ * so everything else is computed in 3 instructions
+ * Y = G * 0.5 + RplusBdiv4
+ * Co = 2 * RplusBdiv4 - G
+ * Cg = G * 0.5 - RplusBdiv4
+ */
+
+ // NOTE: expect v_pk_add_f32 + v_pk_mul_f32
+ FFX_MIN16_F2 RplusBdiv4 = (r + b) * 0.25;
+ FFX_MIN16_F2 G = g;
+ FFX_MIN16_F2 B = b;
+
+ // NOTE: expect 3x v_pk_fma_f32
+ r = G * 0.5 + RplusBdiv4;
+ g = RplusBdiv4 * 2.0 - B;
+ b = G * 0.5 - RplusBdiv4;
+}
+
+FFX_MIN16_F2 Compute3x3SamplesMinMaxPaired(FFX_PARAMETER_IN FFX_MIN16_F2 sampleCenter,
+ FFX_PARAMETER_IN FFX_MIN16_F2 sample0,
+ FFX_PARAMETER_IN FFX_MIN16_F2 sample1,
+ FFX_PARAMETER_IN FFX_MIN16_F2 sample2,
+ FFX_PARAMETER_IN FFX_MIN16_F2 sample3)
+{
+ FFX_MIN16_F2 twoMinValues = ffxMin(ffxMin(sample0, sample1), ffxMin(sample2, sample3));
+ FFX_MIN16_F2 twoMaxValues = ffxMax(ffxMax(sample0, sample1), ffxMax(sample2, sample3));
+
+ return FFX_MIN16_F2(
+ ffxMin3Half(twoMinValues.x, twoMinValues.y, sampleCenter.x),
+ ffxMax3Half(twoMaxValues.x, twoMaxValues.y, sampleCenter.x)
+ );
+}
+
+
+FFX_MIN16_F2 Bool2ToFloat16x2(bool x, bool y)
+{
+ uint lo = x ? 0x00003c00 : 0x00000000;
+ uint hi = y ? 0x3c000000 : 0x00000000;
+ return FFX_MIN16_F2(__XB_AsHalf(lo).x, __XB_AsHalf(hi).y);
+}
+
+struct PairedRectificationBoxAndAccumulatedColorAndWeight
+{
+ FFX_MIN16_F2 boxCenterR;
+ FFX_MIN16_F2 boxCenterG;
+ FFX_MIN16_F2 boxCenterB;
+
+ FFX_MIN16_F2 boxVecR;
+ FFX_MIN16_F2 boxVecG;
+ FFX_MIN16_F2 boxVecB;
+
+ FFX_MIN16_F2 fBoxCenterWeight;
+
+ FFX_MIN16_F2 fColorR;
+ FFX_MIN16_F2 fColorG;
+ FFX_MIN16_F2 fColorB;
+ FFX_MIN16_F2 fWeight;
+
+ FFX_MIN16_F fKernelBiasSq;
+ FFX_MIN16_F fRectificationCurveBias;
+
+ void setKernelBiasAndRectificationCurveBias(FfxFloat32 kernelBias, FfxFloat32 rectificationCurveBias)
+ {
+ fKernelBiasSq = FFX_MIN16_F(kernelBias * kernelBias);
+ fRectificationCurveBias = FFX_MIN16_F(rectificationCurveBias);
+ }
+
+ void initUpscaledColor(FFX_MIN16_F fSrcSampleOffsetSq, FFX_MIN16_F fOnScreenWeight, FFX_MIN16_F2 sampleR, FFX_MIN16_F2 sampleG, FFX_MIN16_F2 sampleB)
+ {
+ #if FFX_FSR3UPSCALER_OPTION_UPSAMPLE_USE_LANCZOS_TYPE == 2 // LANCZOS_TYPE_APPROXIMATE
+ const FFX_MIN16_F2 LanczosUpsampleWeight = FFX_MIN16_F2(
+ PairedLanczos2ApproxSq(fSrcSampleOffsetSq * fKernelBiasSq).x,
+ 0.0
+ );
+ #else
+ #error "Only LANCZOS_TYPE_APPROXIMATE is supported in paired version so far"
+ #endif
+ const FFX_MIN16_F2 fSampleWeight = fOnScreenWeight * LanczosUpsampleWeight;
+
+ fColorR = sampleR * fSampleWeight;
+ fColorG = sampleG * fSampleWeight;
+ fColorB = sampleB * fSampleWeight;
+ fWeight = fSampleWeight;
+ }
+
+ void initBox(FFX_MIN16_F fSrcSampleOffsetSq, FFX_MIN16_F fOnScreenWeight, FFX_MIN16_F2 sampleR, FFX_MIN16_F2 sampleG, FFX_MIN16_F2 sampleB)
+ {
+ const FFX_MIN16_F2 fBoxSampleWeight = FFX_MIN16_F2(
+ exp(fRectificationCurveBias * fSrcSampleOffsetSq) * fOnScreenWeight,
+ 0.0
+ );
+
+ FFX_MIN16_F2 weightedSampleR = sampleR * fBoxSampleWeight;
+ FFX_MIN16_F2 weightedSampleG = sampleG * fBoxSampleWeight;
+ FFX_MIN16_F2 weightedSampleB = sampleB * fBoxSampleWeight;
+
+ boxCenterR = weightedSampleR;
+ boxCenterG = weightedSampleG;
+ boxCenterB = weightedSampleB;
+
+ boxVecR = sampleR * weightedSampleR;
+ boxVecG = sampleG * weightedSampleG;
+ boxVecB = sampleB * weightedSampleB;
+
+ fBoxCenterWeight = fBoxSampleWeight;
+ }
+
+ void addUpscaledColorSample(FFX_MIN16_F2 fSrcSampleOffsetSq, FFX_MIN16_F2 fOnScreenWeight, FFX_MIN16_F2 sampleR, FFX_MIN16_F2 sampleG, FFX_MIN16_F2 sampleB)
+ {
+ #if FFX_FSR3UPSCALER_OPTION_UPSAMPLE_USE_LANCZOS_TYPE == 2 // LANCZOS_TYPE_APPROXIMATE
+ const FFX_MIN16_F2 LanczosUpsampleWeight = PairedLanczos2ApproxSq(fSrcSampleOffsetSq * fKernelBiasSq);
+ #else
+ #error "Only LANCZOS_TYPE_APPROXIMATE is supported in paired version so far"
+ #endif
+ const FFX_MIN16_F2 fSampleWeight = fOnScreenWeight * LanczosUpsampleWeight;
+
+ fColorR += sampleR * fSampleWeight;
+ fColorG += sampleG * fSampleWeight;
+ fColorB += sampleB * fSampleWeight;
+ fWeight += fSampleWeight;
+ }
+
+ void addBoxSample(FFX_MIN16_F2 fSrcSampleOffsetSq, FFX_MIN16_F2 fOnScreenWeight, FFX_MIN16_F2 sampleR, FFX_MIN16_F2 sampleG, FFX_MIN16_F2 sampleB)
+ {
+ // NOTE: ideally expect here 2x v_fma_mix + 2x v_exp_f32 + 2x v_fma_mix
+ const FFX_MIN16_F2 fBoxSampleWeight = exp(fRectificationCurveBias * fSrcSampleOffsetSq) * fOnScreenWeight;
+
+ FFX_MIN16_F2 weightedSampleR = sampleR * fBoxSampleWeight;
+ FFX_MIN16_F2 weightedSampleG = sampleG * fBoxSampleWeight;
+ FFX_MIN16_F2 weightedSampleB = sampleB * fBoxSampleWeight;
+
+ boxCenterR += weightedSampleR;
+ boxCenterG += weightedSampleG;
+ boxCenterB += weightedSampleB;
+
+ boxVecR += sampleR * weightedSampleR;
+ boxVecG += sampleG * weightedSampleG;
+ boxVecB += sampleB * weightedSampleB;
+
+ fBoxCenterWeight += fBoxSampleWeight;
+ }
+
+ void finalizeUpscaledColor(FFX_PARAMETER_OUT FfxFloat32x4 upscaledColorAndWeight)
+ {
+ upscaledColorAndWeight.r = fColorR.x + fColorR.y;
+ upscaledColorAndWeight.g = fColorG.x + fColorG.y;
+ upscaledColorAndWeight.b = fColorB.x + fColorB.y;
+
+ upscaledColorAndWeight.a = fWeight.x + fWeight.y;
+ }
+
+ void finalizeBox(FFX_PARAMETER_OUT FfxFloat32x2 boxCenterAndVecR,
+ FFX_PARAMETER_OUT FfxFloat32x2 boxCenterAndVecG,
+ FFX_PARAMETER_OUT FfxFloat32x2 boxCenterAndVecB,
+ FFX_PARAMETER_OUT FfxFloat32 boxCenterWeight)
+ {
+ boxCenterAndVecR = FfxFloat32x2(boxCenterR.x + boxCenterR.y, boxVecR.x + boxVecR.y);
+ boxCenterAndVecG = FfxFloat32x2(boxCenterG.x + boxCenterG.y, boxVecG.x + boxVecG.y);
+ boxCenterAndVecB = FfxFloat32x2(boxCenterB.x + boxCenterB.y, boxVecB.x + boxVecB.y);
+
+ boxCenterWeight = fBoxCenterWeight.x + fBoxCenterWeight.y;
+ }
+};
+#endif // #if FFX_FSR3UPSCALER_USE_XBOX_PAIRED_16BIT_MATH_OPTIMIZATIONS
+
+void ComputeUpsampledColorAndWeight(const AccumulationPassCommonParams params, FFX_PARAMETER_INOUT AccumulationPassData data)
+{
+ // We compute a sliced lanczos filter with 2 lobes (other slices are accumulated temporaly)
+ const FfxFloat32x2 fDstOutputPos = FfxFloat32x2(params.iPxHrPos) + FFX_BROADCAST_FLOAT32X2(0.5f);
+ const FfxFloat32x2 fSrcOutputPos = fDstOutputPos * DownscaleFactor();
+ const FfxInt32x2 iSrcInputPos = FfxInt32x2(floor(fSrcOutputPos));
+ const FfxFloat32x2 fSrcUnjitteredPos = (FfxFloat32x2(iSrcInputPos) + FfxFloat32x2(0.5f, 0.5f)) - Jitter(); // This is the un-jittered position of the sample at offset 0,0
+ const FfxFloat32x2 fBaseSampleOffset = FfxFloat32x2(fSrcUnjitteredPos - fSrcOutputPos);
+
+ FfxInt32x2 offsetTL;
+ offsetTL.x = (fSrcUnjitteredPos.x > fSrcOutputPos.x) ? FfxInt32(-2) : FfxInt32(-1);
+ offsetTL.y = (fSrcUnjitteredPos.y > fSrcOutputPos.y) ? FfxInt32(-2) : FfxInt32(-1);
+
+ //Load samples
+ // If fSrcUnjitteredPos.y > fSrcOutputPos.y, indicates offsetTL.y = -2, sample offset Y will be [-2, 1], clipbox will be rows [1, 3].
+ // Flip row# for sampling offset in this case, so first 0~2 rows in the sampled array can always be used for computing the clipbox.
+ // This reduces branch or cmove on sampled colors, but moving this overhead to sample position / weight calculation time which apply to less values.
+ const FfxBoolean bFlipRow = fSrcUnjitteredPos.y > fSrcOutputPos.y;
+ const FfxBoolean bFlipCol = fSrcUnjitteredPos.x > fSrcOutputPos.x;
+ const FfxFloat32x2 fOffsetTL = FfxFloat32x2(offsetTL);
+
+ const FfxBoolean bIsInitialSample = (params.fAccumulation == 0.0f);
+
+#if FFX_FSR3UPSCALER_USE_XBOX_PAIRED_16BIT_MATH_OPTIMIZATIONS
+ // Unroll the loop to load samples on Scarlett to help the shader compiler
+ const FFX_MIN16_F2 fSampleOffsetX02 = __XB_AsHalf(bFlipCol ? __XB_AsUInt(FFX_MIN16_F2( 1, -1)) : __XB_AsUInt(FFX_MIN16_F2(-1, 1)));
+ const FFX_MIN16_F2 fSampleOffsetY02 = __XB_AsHalf(bFlipRow ? __XB_AsUInt(FFX_MIN16_F2( 1, -1)) : __XB_AsUInt(FFX_MIN16_F2(-1, 1)));
+
+ typedef FfxInt32 FfxTexCoordI;
+ typedef FfxInt32x2 FfxTexCoordI2;
+
+ const FfxTexCoordI2 iSrcSamplePosX01 = FfxTexCoordI2(iSrcInputPos.xx) + (bFlipCol ? FfxTexCoordI2( 1, 0) : FfxTexCoordI2(-1, 0));
+ const FfxTexCoordI2 iSrcSamplePosX23 = FfxTexCoordI2(iSrcInputPos.xx) + (bFlipCol ? FfxTexCoordI2(-1, -2) : FfxTexCoordI2( 1, 2));
+
+ const FfxTexCoordI2 iSrcSamplePosY01 = FfxTexCoordI2(iSrcInputPos.yy) + (bFlipRow ? FfxTexCoordI2( 1, 0) : FfxTexCoordI2(-1, 0));
+ const FfxTexCoordI2 iSrcSamplePosY23 = FfxTexCoordI2(iSrcInputPos.yy) + (bFlipRow ? FfxTexCoordI2(-1, -2) : FfxTexCoordI2( 1, 2));
+
+ const FfxTexCoordI2 renderSizeLastTexelCoord = FfxTexCoordI2(RenderSize()) - FfxTexCoordI2(1, 1);
+
+ const FfxTexCoordI2 iSrcSamplePosX01Clamped = FfxTexCoordI2(
+ __XB_Med3_I32(iSrcSamplePosX01.x, 0, renderSizeLastTexelCoord.x),
+ __XB_Med3_I32(iSrcSamplePosX01.y, 0, renderSizeLastTexelCoord.x)
+ );
+
+ const FfxTexCoordI2 iSrcSamplePosX23Clamped = FfxTexCoordI2(
+ __XB_Med3_I32(iSrcSamplePosX23.x, 0, renderSizeLastTexelCoord.x),
+ __XB_Med3_I32(iSrcSamplePosX23.y, 0, renderSizeLastTexelCoord.x)
+ );
+
+ const FfxTexCoordI2 iSrcSamplePosY01Clamped = FfxTexCoordI2(
+ __XB_Med3_I32(iSrcSamplePosY01.x, 0, renderSizeLastTexelCoord.y),
+ __XB_Med3_I32(iSrcSamplePosY01.y, 0, renderSizeLastTexelCoord.y)
+ );
+
+ const FfxTexCoordI2 iSrcSamplePosY23Clamped = FfxTexCoordI2(
+ __XB_Med3_I32(iSrcSamplePosY23.x, 0, renderSizeLastTexelCoord.y),
+ __XB_Med3_I32(iSrcSamplePosY23.y, 0, renderSizeLastTexelCoord.y)
+ );
+
+ FFX_MIN16_F2 TopCornerR, BotCornerR, HorzR, VertR, CenterR;
+ FFX_MIN16_F2 TopCornerG, BotCornerG, HorzG, VertG, CenterG;
+ FFX_MIN16_F2 TopCornerB, BotCornerB, HorzB, VertB, CenterB;
+
+ LoadPreparedColorPairedRgb(TopCornerR, TopCornerG, TopCornerB,
+ FfxTexCoordI2(iSrcSamplePosX01Clamped.x, iSrcSamplePosY01Clamped.x),
+ FfxTexCoordI2(iSrcSamplePosX23Clamped.x, iSrcSamplePosY01Clamped.x)
+ );
+
+ LoadPreparedColorPairedRgb(BotCornerR, BotCornerG, BotCornerB,
+ FfxTexCoordI2(iSrcSamplePosX01Clamped.x, iSrcSamplePosY23Clamped.x),
+ FfxTexCoordI2(iSrcSamplePosX23Clamped.x, iSrcSamplePosY23Clamped.x)
+ );
+
+ LoadPreparedColorPairedRgb(HorzR, HorzG, HorzB,
+ FfxTexCoordI2(iSrcSamplePosX01Clamped.x, iSrcSamplePosY01Clamped.y),
+ FfxTexCoordI2(iSrcSamplePosX23Clamped.x, iSrcSamplePosY01Clamped.y)
+ );
+
+ LoadPreparedColorPairedRgb(VertR, VertG, VertB,
+ FfxTexCoordI2(iSrcSamplePosX01Clamped.y, iSrcSamplePosY01Clamped.x),
+ FfxTexCoordI2(iSrcSamplePosX01Clamped.y, iSrcSamplePosY23Clamped.x)
+ );
+
+ // NOTE: duplicated data
+ LoadPreparedColorPairedRgb(CenterR, CenterG, CenterB,
+ FfxTexCoordI2(iSrcSamplePosX01Clamped.y, iSrcSamplePosY01Clamped.y),
+ FfxTexCoordI2(iSrcSamplePosX01Clamped.y, iSrcSamplePosY01Clamped.y)
+ );
+
+ #if FFX_FSR3UPSCALER_OPTION_HDR_COLOR_INPUT
+ if (bIsInitialSample)
+ {
+ TonemapPaired(TopCornerR, TopCornerG, TopCornerB);
+ TonemapPaired(BotCornerR, BotCornerG, BotCornerB);
+ TonemapPaired(HorzR, HorzG, HorzB);
+ TonemapPaired(VertR, VertG, VertB);
+ TonemapPaired(CenterR, CenterG, CenterB);
+ }
+ #endif
+
+ RGBToYCoCgPaired(TopCornerR, TopCornerG, TopCornerB);
+ RGBToYCoCgPaired(BotCornerR, BotCornerG, BotCornerB);
+ RGBToYCoCgPaired(HorzR, HorzG, HorzB);
+ RGBToYCoCgPaired(VertR, VertG, VertB);
+ RGBToYCoCgPaired(CenterR, CenterG, CenterB);
+
+#else
+ FfxFloat32x3 fSamples[9];
+ FfxInt32 iSampleIndex = 0;
+
+ FFX_UNROLL
+ for (FfxInt32 row = 0; row < 3; row++) {
+ FFX_UNROLL
+ for (FfxInt32 col = 0; col < 3; col++) {
+ const FfxInt32x2 iSampleColRow = FfxInt32x2(bFlipCol ? (3 - col) : col, bFlipRow ? (3 - row) : row);
+ const FfxInt32x2 iSrcSamplePos = FfxInt32x2(iSrcInputPos) + offsetTL + iSampleColRow;
+ const FfxInt32x2 iSampleCoord = ClampLoad(iSrcSamplePos, FfxInt32x2(0, 0), FfxInt32x2(RenderSize()));
+
+ fSamples[iSampleIndex] = LoadPreparedColor(iSampleCoord);
+
+ ++iSampleIndex;
+ }
+ }
+
+#if FFX_FSR3UPSCALER_OPTION_HDR_COLOR_INPUT
+ if (bIsInitialSample)
+ {
+ for (iSampleIndex = 0; iSampleIndex < 9; ++iSampleIndex)
+ {
+ //YCoCg -> RGB -> Tonemap -> YCoCg (Use RGB tonemapper to avoid color desaturation)
+ fSamples[iSampleIndex] = RGBToYCoCg(Tonemap(YCoCgToRGB(fSamples[iSampleIndex])));
+ }
+ }
+#endif
+
+#endif // #if FFX_FSR3UPSCALER_USE_XBOX_PAIRED_16BIT_MATH_OPTIMIZATIONS
+
+ // Identify how much of each upsampled color to be used for this frame
+ const FfxFloat32 fKernelBiasMax = ComputeMaxKernelWeight(params, data);
+ const FfxFloat32 fKernelBiasMin = ffxMax(1.0f, ((1.0f + fKernelBiasMax) * 0.3f));
+
+ const FfxFloat32 fKernelBiasWeight =
+ ffxMin(1.0f - params.fDisocclusion * 0.5f,
+ ffxMin(1.0f - params.fShadingChange,
+ ffxSaturate(data.fHistoryWeight * 5.0f)
+ ));
+
+ const FfxFloat32 fKernelBias = ffxLerp(fKernelBiasMin, fKernelBiasMax, fKernelBiasWeight);
+
+#if FFX_FSR3UPSCALER_USE_XBOX_PAIRED_16BIT_MATH_OPTIMIZATIONS
+ // Unroll the loop to load samples on Scarlett to help the shader compiler
+ const bool coordX0OnScreen = iSrcSamplePosX01.x == iSrcSamplePosX01Clamped.x;
+ const bool coordX1OnScreen = iSrcSamplePosX01.y == iSrcSamplePosX01Clamped.y;
+ const bool coordX2OnScreen = iSrcSamplePosX23.x == iSrcSamplePosX23Clamped.x;
+
+ const bool coordY0OnScreen = iSrcSamplePosY01.x == iSrcSamplePosY01Clamped.x;
+ const bool coordY1OnScreen = iSrcSamplePosY01.y == iSrcSamplePosY01Clamped.y;
+ const bool coordY2OnScreen = iSrcSamplePosY23.x == iSrcSamplePosY23Clamped.x;
+
+ const FFX_MIN16_F2 fBaseSampleOffsetHalf = FFX_MIN16_F2(fBaseSampleOffset);
+
+ const FFX_MIN16_F2 fSrcSampleOffsetX_02 = fBaseSampleOffsetHalf.xx + fSampleOffsetX02;
+ const FFX_MIN16_F2 fSrcSampleOffsetY_02 = fBaseSampleOffsetHalf.yy + fSampleOffsetY02;
+
+ const FFX_MIN16_F2 fSrcSampleOffsetXSq_02 = fSrcSampleOffsetX_02 * fSrcSampleOffsetX_02;
+ const FFX_MIN16_F2 fSrcSampleOffsetYSq_02 = fSrcSampleOffsetY_02 * fSrcSampleOffsetY_02;
+ const FFX_MIN16_F2 fSrcSampleOffsetXYSq_11 = fBaseSampleOffsetHalf * fBaseSampleOffsetHalf;
+
+ const FfxFloat32 fRectificationCurveBias = -2.3f;
+ PairedRectificationBoxAndAccumulatedColorAndWeight pairedBox;
+ pairedBox.setKernelBiasAndRectificationCurveBias(fKernelBias, fRectificationCurveBias);
+
+ // init by o o o
+ // o x o
+ // o o o
+ pairedBox.initBox(
+ fSrcSampleOffsetXYSq_11.x + fSrcSampleOffsetXYSq_11.y,
+ Bool2ToFloat16x2(coordX1OnScreen && coordY1OnScreen, false).x,
+ CenterR, CenterG, CenterB
+ );
+
+ // add remaining two samples from 1st row x o x
+ // o * o
+ // o o o
+ pairedBox.addBoxSample(
+ fSrcSampleOffsetXSq_02 + fSrcSampleOffsetYSq_02.xx,
+ Bool2ToFloat16x2(coordX0OnScreen && coordY0OnScreen, coordX2OnScreen && coordY0OnScreen),
+ TopCornerR, TopCornerG, TopCornerB
+ );
+
+ // add two samples from 2nd row * o *
+ // o * o
+ // x o x
+ pairedBox.addBoxSample(
+ fSrcSampleOffsetXSq_02 + fSrcSampleOffsetYSq_02.yy,
+ Bool2ToFloat16x2(coordX0OnScreen && coordY2OnScreen, coordX2OnScreen && coordY2OnScreen),
+ BotCornerR, BotCornerG, BotCornerB
+ );
+
+ // add two samples from 3rd row * o *
+ // x * x
+ // * o *
+ pairedBox.addBoxSample(
+ fSrcSampleOffsetXSq_02 + fSrcSampleOffsetXYSq_11.yy,
+ Bool2ToFloat16x2(coordX0OnScreen && coordY1OnScreen, coordX2OnScreen && coordY1OnScreen),
+ HorzR, HorzG, HorzB
+ );
+
+ // add remaining samples * x *
+ // * * *
+ // * x *
+ pairedBox.addBoxSample(
+ fSrcSampleOffsetXYSq_11.xx + fSrcSampleOffsetYSq_02,
+ Bool2ToFloat16x2(coordX1OnScreen && coordY0OnScreen, coordX1OnScreen && coordY2OnScreen),
+ VertR, VertG, VertB
+ );
+
+ FfxFloat32x2 boxCenterAndVecR, boxCenterAndVecG, boxCenterAndVecB;
+ FfxFloat32 boxCenterWeight;
+ pairedBox.finalizeBox(boxCenterAndVecR, boxCenterAndVecG, boxCenterAndVecB, boxCenterWeight);
+
+ if (!bIsInitialSample)
+ {
+ pairedBox.initUpscaledColor(
+ fSrcSampleOffsetXYSq_11.x + fSrcSampleOffsetXYSq_11.y,
+ Bool2ToFloat16x2(coordX1OnScreen && coordY1OnScreen, false).x,
+ CenterR, CenterG, CenterB
+ );
+
+ // add remaining two samples from 1st row x o x
+ // o * o
+ // o o o
+ pairedBox.addUpscaledColorSample(
+ fSrcSampleOffsetXSq_02 + fSrcSampleOffsetYSq_02.xx,
+ Bool2ToFloat16x2(coordX0OnScreen && coordY0OnScreen, coordX2OnScreen && coordY0OnScreen),
+ TopCornerR, TopCornerG, TopCornerB
+ );
+
+ // add two samples from 2nd row * o *
+ // o * o
+ // x o x
+ pairedBox.addUpscaledColorSample(
+ fSrcSampleOffsetXSq_02 + fSrcSampleOffsetYSq_02.yy,
+ Bool2ToFloat16x2(coordX0OnScreen && coordY2OnScreen, coordX2OnScreen && coordY2OnScreen),
+ BotCornerR, BotCornerG, BotCornerB
+ );
+
+ // add two samples from 3rd row * o *
+ // x * x
+ // * o *
+ pairedBox.addUpscaledColorSample(
+ fSrcSampleOffsetXSq_02 + fSrcSampleOffsetXYSq_11.yy,
+ Bool2ToFloat16x2(coordX0OnScreen && coordY1OnScreen, coordX2OnScreen && coordY1OnScreen),
+ HorzR, HorzG, HorzB
+ );
+
+ // add remaining samples * x *
+ // * * *
+ // * x *
+ pairedBox.addUpscaledColorSample(
+ fSrcSampleOffsetXYSq_11.xx + fSrcSampleOffsetYSq_02,
+ Bool2ToFloat16x2(coordX1OnScreen && coordY0OnScreen, coordX1OnScreen && coordY2OnScreen),
+ VertR, VertG, VertB
+ );
+
+ FfxFloat32x4 upscaledColorAndWeight = 0.0;
+ pairedBox.finalizeUpscaledColor(upscaledColorAndWeight);
+
+ data.fUpsampledColor = FfxFloat32x3(upscaledColorAndWeight.rgb);
+ data.fUpsampledWeight = FfxFloat32(upscaledColorAndWeight.w);
+ }
+
+ FFX_MIN16_F2 aabbMinMaxR = Compute3x3SamplesMinMaxPaired(CenterR, TopCornerR, BotCornerR, HorzR, VertR);
+ FFX_MIN16_F2 aabbMinMaxG = Compute3x3SamplesMinMaxPaired(CenterG, TopCornerG, BotCornerG, HorzG, VertG);
+ FFX_MIN16_F2 aabbMinMaxB = Compute3x3SamplesMinMaxPaired(CenterB, TopCornerB, BotCornerB, HorzB, VertB);
+
+ data.clippingBox.boxCenter = FfxFloat32x3(boxCenterAndVecR.x, boxCenterAndVecG.x, boxCenterAndVecB.x);
+ data.clippingBox.boxVec = FfxFloat32x3(boxCenterAndVecR.y, boxCenterAndVecG.y, boxCenterAndVecB.y);
+ data.clippingBox.aabbMin = FfxFloat32x3(aabbMinMaxR.x, aabbMinMaxG.x, aabbMinMaxB.x);
+ data.clippingBox.aabbMax = FfxFloat32x3(aabbMinMaxR.y, aabbMinMaxG.y, aabbMinMaxB.y);
+ data.clippingBox.fBoxCenterWeight = FfxFloat32(boxCenterWeight);
+#else
+
+ iSampleIndex = 0;
+
+ FFX_UNROLL
+ for (FfxInt32 row = 0; row < 3; row++)
+ {
+ FFX_UNROLL
+ for (FfxInt32 col = 0; col < 3; col++)
+ {
+ const FfxInt32x2 sampleColRow = FfxInt32x2(bFlipCol ? (3 - col) : col, bFlipRow ? (3 - row) : row);
+ const FfxFloat32x2 fOffset = fOffsetTL + FfxFloat32x2(sampleColRow);
+ const FfxFloat32x2 fSrcSampleOffset = fBaseSampleOffset + fOffset;
+
+ const FfxInt32x2 iSrcSamplePos = FfxInt32x2(iSrcInputPos) + FfxInt32x2(offsetTL) + sampleColRow;
+ const FfxFloat32 fOnScreenFactor = FfxFloat32(IsOnScreen(FfxInt32x2(iSrcSamplePos), FfxInt32x2(RenderSize())));
+
+ if (!bIsInitialSample)
+ {
+ const FfxFloat32 fSampleWeight = fOnScreenFactor * FfxFloat32(GetUpsampleLanczosWeight(fSrcSampleOffset, fKernelBias));
+
+ data.fUpsampledColor += fSamples[iSampleIndex] * fSampleWeight;
+ data.fUpsampledWeight += fSampleWeight;
+ }
+
+ // Update rectification box
+ {
+ const FfxFloat32 fRectificationCurveBias = -2.3f;
+ const FfxFloat32 fSrcSampleOffsetSq = dot(fSrcSampleOffset, fSrcSampleOffset);
+ const FfxFloat32 fBoxSampleWeight = exp(fRectificationCurveBias * fSrcSampleOffsetSq) * fOnScreenFactor;
+
+ const FfxBoolean bInitialSample = (row == 0) && (col == 0);
+ RectificationBoxAddSample(bInitialSample, data.clippingBox, fSamples[iSampleIndex], fBoxSampleWeight);
+ }
+ ++iSampleIndex;
+ }
+ }
+
+#endif // #if FFX_FSR3UPSCALER_USE_XBOX_PAIRED_16BIT_MATH_OPTIMIZATIONS
+
+ RectificationBoxComputeVarianceBoxData(data.clippingBox);
+
+ data.fUpsampledWeight *= FfxFloat32(data.fUpsampledWeight > FSR3UPSCALER_EPSILON);
+
+ if (data.fUpsampledWeight > FSR3UPSCALER_EPSILON) {
+ // Normalize for deringing (we need to compare colors)
+ data.fUpsampledColor = data.fUpsampledColor / data.fUpsampledWeight;
+ data.fUpsampledWeight *= fAverageLanczosWeightPerFrame;
+
+ Deringing(data.clippingBox, data.fUpsampledColor);
+ }
+
+ // Initial samples using tonemapped upsampling
+ if (bIsInitialSample) {
+#if FFX_FSR3UPSCALER_OPTION_HDR_COLOR_INPUT
+ data.fUpsampledColor = RGBToYCoCg(InverseTonemap(YCoCgToRGB(data.clippingBox.boxCenter)));
+#else
+ data.fUpsampledColor = data.clippingBox.boxCenter;
+#endif
+ data.fUpsampledWeight = 1.0f;
+ data.fHistoryWeight = 0.0f;
+ }
+}
diff --git a/externals/FidelityFX-SDK/include/FidelityFX/gpu/spd/ffx_spd.h b/externals/FidelityFX-SDK/include/FidelityFX/gpu/spd/ffx_spd.h
new file mode 100644
index 0000000000..c3ee50f027
--- /dev/null
+++ b/externals/FidelityFX-SDK/include/FidelityFX/gpu/spd/ffx_spd.h
@@ -0,0 +1,1014 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+/// @defgroup FfxGPUSpd FidelityFX SPD
+/// FidelityFX Single Pass Downsampler 2.0 GPU documentation
+///
+/// @ingroup FfxGPUEffects
+
+/// Setup required constant values for SPD (CPU).
+///
+/// @param [out] dispatchThreadGroupCountXY CPU side: dispatch thread group count xy. z is number of slices of the input texture
+/// @param [out] workGroupOffset GPU side: pass in as constant
+/// @param [out] numWorkGroupsAndMips GPU side: pass in as constant
+/// @param [in] rectInfo left, top, width, height
+/// @param [in] mips optional: if -1, calculate based on rect width and height
+///
+/// @ingroup FfxGPUSpd
+#if defined(FFX_CPU)
+FFX_STATIC void ffxSpdSetup(FfxUInt32x2 dispatchThreadGroupCountXY,
+ FfxUInt32x2 workGroupOffset,
+ FfxUInt32x2 numWorkGroupsAndMips,
+ FfxUInt32x4 rectInfo,
+ FfxInt32 mips)
+{
+ // determines the offset of the first tile to downsample based on
+ // left (rectInfo[0]) and top (rectInfo[1]) of the subregion.
+ workGroupOffset[0] = rectInfo[0] / 64;
+ workGroupOffset[1] = rectInfo[1] / 64;
+
+ FfxUInt32 endIndexX = (rectInfo[0] + rectInfo[2] - 1) / 64; // rectInfo[0] = left, rectInfo[2] = width
+ FfxUInt32 endIndexY = (rectInfo[1] + rectInfo[3] - 1) / 64; // rectInfo[1] = top, rectInfo[3] = height
+
+ // we only need to dispatch as many thread groups as tiles we need to downsample
+ // number of tiles per slice depends on the subregion to downsample
+ dispatchThreadGroupCountXY[0] = endIndexX + 1 - workGroupOffset[0];
+ dispatchThreadGroupCountXY[1] = endIndexY + 1 - workGroupOffset[1];
+
+ // number of thread groups per slice
+ numWorkGroupsAndMips[0] = (dispatchThreadGroupCountXY[0]) * (dispatchThreadGroupCountXY[1]);
+
+ if (mips >= 0)
+ {
+ numWorkGroupsAndMips[1] = FfxUInt32(mips);
+ }
+ else
+ {
+ // calculate based on rect width and height
+ FfxUInt32 resolution = ffxMax(rectInfo[2], rectInfo[3]);
+ numWorkGroupsAndMips[1] = FfxUInt32((ffxMin(floor(log2(FfxFloat32(resolution))), FfxFloat32(12))));
+ }
+}
+
+/// Setup required constant values for SPD (CPU).
+///
+/// @param [out] dispatchThreadGroupCountXY CPU side: dispatch thread group count xy. z is number of slices of the input texture
+/// @param [out] workGroupOffset GPU side: pass in as constant
+/// @param [out] numWorkGroupsAndMips GPU side: pass in as constant
+/// @param [in] rectInfo left, top, width, height
+///
+/// @ingroup FfxGPUSpd
+FFX_STATIC void ffxSpdSetup(FfxUInt32x2 dispatchThreadGroupCountXY,
+ FfxUInt32x2 workGroupOffset,
+ FfxUInt32x2 numWorkGroupsAndMips,
+ FfxUInt32x4 rectInfo)
+{
+ ffxSpdSetup(dispatchThreadGroupCountXY, workGroupOffset, numWorkGroupsAndMips, rectInfo, -1);
+}
+#endif // #if defined(FFX_CPU)
+
+
+//==============================================================================================================================
+// NON-PACKED VERSION
+//==============================================================================================================================
+#if defined(FFX_GPU)
+#if defined(FFX_SPD_PACKED_ONLY)
+// Avoid compiler errors by including default implementations of these callbacks.
+FfxFloat32x4 SpdLoadSourceImage(FfxInt32x2 p, FfxUInt32 slice)
+{
+ return FfxFloat32x4(0.0, 0.0, 0.0, 0.0);
+}
+
+FfxFloat32x4 SpdLoad(FfxInt32x2 p, FfxUInt32 slice)
+{
+ return FfxFloat32x4(0.0, 0.0, 0.0, 0.0);
+}
+void SpdStore(FfxInt32x2 p, FfxFloat32x4 value, FfxUInt32 mip, FfxUInt32 slice)
+{
+}
+FfxFloat32x4 SpdLoadIntermediate(FfxUInt32 x, FfxUInt32 y)
+{
+ return FfxFloat32x4(0.0, 0.0, 0.0, 0.0);
+}
+void SpdStoreIntermediate(FfxUInt32 x, FfxUInt32 y, FfxFloat32x4 value)
+{
+}
+FfxFloat32x4 SpdReduce4(FfxFloat32x4 v0, FfxFloat32x4 v1, FfxFloat32x4 v2, FfxFloat32x4 v3)
+{
+ return FfxFloat32x4(0.0, 0.0, 0.0, 0.0);
+}
+#endif // #if FFX_SPD_PACKED_ONLY
+
+//_____________________________________________________________/\_______________________________________________________________
+#if defined(FFX_GLSL) && !defined(FFX_SPD_NO_WAVE_OPERATIONS)
+#extension GL_KHR_shader_subgroup_quad:require
+#endif
+
+void ffxSpdWorkgroupShuffleBarrier()
+{
+ FFX_GROUP_MEMORY_BARRIER;
+}
+
+// Only last active workgroup should proceed
+bool SpdExitWorkgroup(FfxUInt32 numWorkGroups, FfxUInt32 localInvocationIndex, FfxUInt32 slice)
+{
+ // global atomic counter
+ if (localInvocationIndex == 0)
+ {
+ SpdIncreaseAtomicCounter(slice);
+ }
+
+ ffxSpdWorkgroupShuffleBarrier();
+ return (SpdGetAtomicCounter() != (numWorkGroups - 1));
+}
+
+// User defined: FfxFloat32x4 SpdReduce4(FfxFloat32x4 v0, FfxFloat32x4 v1, FfxFloat32x4 v2, FfxFloat32x4 v3);
+FfxFloat32x4 SpdReduceQuad(FfxFloat32x4 v)
+{
+#if defined(FFX_GLSL) && !defined(FFX_SPD_NO_WAVE_OPERATIONS)
+
+ FfxFloat32x4 v0 = v;
+ FfxFloat32x4 v1 = subgroupQuadSwapHorizontal(v);
+ FfxFloat32x4 v2 = subgroupQuadSwapVertical(v);
+ FfxFloat32x4 v3 = subgroupQuadSwapDiagonal(v);
+ return SpdReduce4(v0, v1, v2, v3);
+
+#elif defined(FFX_HLSL) && !defined(FFX_SPD_NO_WAVE_OPERATIONS)
+
+ // requires SM6.0
+ FfxFloat32x4 v0 = v;
+ FfxFloat32x4 v1 = QuadReadAcrossX(v);
+ FfxFloat32x4 v2 = QuadReadAcrossY(v);
+ FfxFloat32x4 v3 = QuadReadAcrossDiagonal(v);
+ return SpdReduce4(v0, v1, v2, v3);
+/*
+ // if SM6.0 is not available, you can use the AMD shader intrinsics
+ // the AMD shader intrinsics are available in AMD GPU Services (AGS) library:
+ // https://gpuopen.com/amd-gpu-services-ags-library/
+ // works for DX11
+ FfxFloat32x4 v0 = v;
+ FfxFloat32x4 v1;
+ v1.x = AmdExtD3DShaderIntrinsics_SwizzleF(v.x, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1);
+ v1.y = AmdExtD3DShaderIntrinsics_SwizzleF(v.y, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1);
+ v1.z = AmdExtD3DShaderIntrinsics_SwizzleF(v.z, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1);
+ v1.w = AmdExtD3DShaderIntrinsics_SwizzleF(v.w, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1);
+ FfxFloat32x4 v2;
+ v2.x = AmdExtD3DShaderIntrinsics_SwizzleF(v.x, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2);
+ v2.y = AmdExtD3DShaderIntrinsics_SwizzleF(v.y, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2);
+ v2.z = AmdExtD3DShaderIntrinsics_SwizzleF(v.z, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2);
+ v2.w = AmdExtD3DShaderIntrinsics_SwizzleF(v.w, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2);
+ FfxFloat32x4 v3;
+ v3.x = AmdExtD3DShaderIntrinsics_SwizzleF(v.x, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4);
+ v3.y = AmdExtD3DShaderIntrinsics_SwizzleF(v.y, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4);
+ v3.z = AmdExtD3DShaderIntrinsics_SwizzleF(v.z, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4);
+ v3.w = AmdExtD3DShaderIntrinsics_SwizzleF(v.w, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4);
+ return SpdReduce4(v0, v1, v2, v3);
+ */
+#endif
+ return v;
+}
+
+FfxFloat32x4 SpdReduceIntermediate(FfxUInt32x2 i0, FfxUInt32x2 i1, FfxUInt32x2 i2, FfxUInt32x2 i3)
+{
+ FfxFloat32x4 v0 = SpdLoadIntermediate(i0.x, i0.y);
+ FfxFloat32x4 v1 = SpdLoadIntermediate(i1.x, i1.y);
+ FfxFloat32x4 v2 = SpdLoadIntermediate(i2.x, i2.y);
+ FfxFloat32x4 v3 = SpdLoadIntermediate(i3.x, i3.y);
+ return SpdReduce4(v0, v1, v2, v3);
+}
+
+FfxFloat32x4 SpdReduceLoad4(FfxUInt32x2 i0, FfxUInt32x2 i1, FfxUInt32x2 i2, FfxUInt32x2 i3, FfxUInt32 slice)
+{
+ FfxFloat32x4 v0 = SpdLoad(FfxInt32x2(i0), slice);
+ FfxFloat32x4 v1 = SpdLoad(FfxInt32x2(i1), slice);
+ FfxFloat32x4 v2 = SpdLoad(FfxInt32x2(i2), slice);
+ FfxFloat32x4 v3 = SpdLoad(FfxInt32x2(i3), slice);
+ return SpdReduce4(v0, v1, v2, v3);
+}
+
+FfxFloat32x4 SpdReduceLoad4(FfxUInt32x2 base, FfxUInt32 slice)
+{
+ return SpdReduceLoad4(FfxUInt32x2(base + FfxUInt32x2(0, 0)), FfxUInt32x2(base + FfxUInt32x2(0, 1)), FfxUInt32x2(base + FfxUInt32x2(1, 0)), FfxUInt32x2(base + FfxUInt32x2(1, 1)), slice);
+}
+
+FfxFloat32x4 SpdReduceLoadSourceImage4(FfxUInt32x2 i0, FfxUInt32x2 i1, FfxUInt32x2 i2, FfxUInt32x2 i3, FfxUInt32 slice)
+{
+ FfxFloat32x4 v0 = SpdLoadSourceImage(FfxInt32x2(i0), slice);
+ FfxFloat32x4 v1 = SpdLoadSourceImage(FfxInt32x2(i1), slice);
+ FfxFloat32x4 v2 = SpdLoadSourceImage(FfxInt32x2(i2), slice);
+ FfxFloat32x4 v3 = SpdLoadSourceImage(FfxInt32x2(i3), slice);
+ return SpdReduce4(v0, v1, v2, v3);
+}
+
+FfxFloat32x4 SpdReduceLoadSourceImage(FfxUInt32x2 base, FfxUInt32 slice)
+{
+#if defined(SPD_LINEAR_SAMPLER)
+ return SpdLoadSourceImage(FfxInt32x2(base), slice);
+#else
+ return SpdReduceLoadSourceImage4(FfxUInt32x2(base + FfxUInt32x2(0, 0)), FfxUInt32x2(base + FfxUInt32x2(0, 1)), FfxUInt32x2(base + FfxUInt32x2(1, 0)), FfxUInt32x2(base + FfxUInt32x2(1, 1)), slice);
+#endif
+}
+
+void SpdDownsampleMips_0_1_Intrinsics(FfxUInt32 x, FfxUInt32 y, FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mip, FfxUInt32 slice)
+{
+ FfxFloat32x4 v[4];
+
+ FfxInt32x2 tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2, y * 2);
+ FfxInt32x2 pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x, y);
+ v[0] = SpdReduceLoadSourceImage(tex, slice);
+ SpdStore(pix, v[0], 0, slice);
+
+ tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2 + 32, y * 2);
+ pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x + 16, y);
+ v[1] = SpdReduceLoadSourceImage(tex, slice);
+ SpdStore(pix, v[1], 0, slice);
+
+ tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2, y * 2 + 32);
+ pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x, y + 16);
+ v[2] = SpdReduceLoadSourceImage(tex, slice);
+ SpdStore(pix, v[2], 0, slice);
+
+ tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2 + 32, y * 2 + 32);
+ pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x + 16, y + 16);
+ v[3] = SpdReduceLoadSourceImage(tex, slice);
+ SpdStore(pix, v[3], 0, slice);
+
+ if (mip <= 1)
+ return;
+
+ v[0] = SpdReduceQuad(v[0]);
+ v[1] = SpdReduceQuad(v[1]);
+ v[2] = SpdReduceQuad(v[2]);
+ v[3] = SpdReduceQuad(v[3]);
+
+ if ((localInvocationIndex % 4) == 0)
+ {
+ SpdStore(FfxInt32x2(workGroupID.xy * 16) + FfxInt32x2(x / 2, y / 2), v[0], 1, slice);
+ SpdStoreIntermediate(x / 2, y / 2, v[0]);
+
+ SpdStore(FfxInt32x2(workGroupID.xy * 16) + FfxInt32x2(x / 2 + 8, y / 2), v[1], 1, slice);
+ SpdStoreIntermediate(x / 2 + 8, y / 2, v[1]);
+
+ SpdStore(FfxInt32x2(workGroupID.xy * 16) + FfxInt32x2(x / 2, y / 2 + 8), v[2], 1, slice);
+ SpdStoreIntermediate(x / 2, y / 2 + 8, v[2]);
+
+ SpdStore(FfxInt32x2(workGroupID.xy * 16) + FfxInt32x2(x / 2 + 8, y / 2 + 8), v[3], 1, slice);
+ SpdStoreIntermediate(x / 2 + 8, y / 2 + 8, v[3]);
+ }
+}
+
+void SpdDownsampleMips_0_1_LDS(FfxUInt32 x, FfxUInt32 y, FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mip, FfxUInt32 slice)
+{
+ FfxFloat32x4 v[4];
+
+ FfxInt32x2 tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2, y * 2);
+ FfxInt32x2 pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x, y);
+ v[0] = SpdReduceLoadSourceImage(tex, slice);
+ SpdStore(pix, v[0], 0, slice);
+
+ tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2 + 32, y * 2);
+ pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x + 16, y);
+ v[1] = SpdReduceLoadSourceImage(tex, slice);
+ SpdStore(pix, v[1], 0, slice);
+
+ tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2, y * 2 + 32);
+ pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x, y + 16);
+ v[2] = SpdReduceLoadSourceImage(tex, slice);
+ SpdStore(pix, v[2], 0, slice);
+
+ tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2 + 32, y * 2 + 32);
+ pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x + 16, y + 16);
+ v[3] = SpdReduceLoadSourceImage(tex, slice);
+ SpdStore(pix, v[3], 0, slice);
+
+ if (mip <= 1)
+ return;
+
+ for (FfxUInt32 i = 0; i < 4; i++)
+ {
+ SpdStoreIntermediate(x, y, v[i]);
+ ffxSpdWorkgroupShuffleBarrier();
+ if (localInvocationIndex < 64)
+ {
+ v[i] = SpdReduceIntermediate(FfxUInt32x2(x * 2 + 0, y * 2 + 0), FfxUInt32x2(x * 2 + 1, y * 2 + 0), FfxUInt32x2(x * 2 + 0, y * 2 + 1), FfxUInt32x2(x * 2 + 1, y * 2 + 1));
+ SpdStore(FfxInt32x2(workGroupID.xy * 16) + FfxInt32x2(x + (i % 2) * 8, y + (i / 2) * 8), v[i], 1, slice);
+ }
+ ffxSpdWorkgroupShuffleBarrier();
+ }
+
+ if (localInvocationIndex < 64)
+ {
+ SpdStoreIntermediate(x + 0, y + 0, v[0]);
+ SpdStoreIntermediate(x + 8, y + 0, v[1]);
+ SpdStoreIntermediate(x + 0, y + 8, v[2]);
+ SpdStoreIntermediate(x + 8, y + 8, v[3]);
+ }
+}
+
+void SpdDownsampleMips_0_1(FfxUInt32 x, FfxUInt32 y, FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mip, FfxUInt32 slice)
+{
+#if defined(FFX_SPD_NO_WAVE_OPERATIONS)
+ SpdDownsampleMips_0_1_LDS(x, y, workGroupID, localInvocationIndex, mip, slice);
+#else
+ SpdDownsampleMips_0_1_Intrinsics(x, y, workGroupID, localInvocationIndex, mip, slice);
+#endif
+}
+
+
+void SpdDownsampleMip_2(FfxUInt32 x, FfxUInt32 y, FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mip, FfxUInt32 slice)
+{
+#if defined(FFX_SPD_NO_WAVE_OPERATIONS)
+ if (localInvocationIndex < 64)
+ {
+ FfxFloat32x4 v = SpdReduceIntermediate(FfxUInt32x2(x * 2 + 0, y * 2 + 0), FfxUInt32x2(x * 2 + 1, y * 2 + 0), FfxUInt32x2(x * 2 + 0, y * 2 + 1), FfxUInt32x2(x * 2 + 1, y * 2 + 1));
+ SpdStore(FfxInt32x2(workGroupID.xy * 8) + FfxInt32x2(x, y), v, mip, slice);
+ // store to LDS, try to reduce bank conflicts
+ // x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
+ // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ // 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 x
+ // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ // x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
+ // ...
+ // x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
+ SpdStoreIntermediate(x * 2 + y % 2, y * 2, v);
+ }
+#else
+ FfxFloat32x4 v = SpdLoadIntermediate(x, y);
+ v = SpdReduceQuad(v);
+ // quad index 0 stores result
+ if (localInvocationIndex % 4 == 0)
+ {
+ SpdStore(FfxInt32x2(workGroupID.xy * 8) + FfxInt32x2(x / 2, y / 2), v, mip, slice);
+ SpdStoreIntermediate(x + (y / 2) % 2, y, v);
+ }
+#endif
+}
+
+void SpdDownsampleMip_3(FfxUInt32 x, FfxUInt32 y, FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mip, FfxUInt32 slice)
+{
+#if defined(FFX_SPD_NO_WAVE_OPERATIONS)
+ if (localInvocationIndex < 16)
+ {
+ // x 0 x 0
+ // 0 0 0 0
+ // 0 x 0 x
+ // 0 0 0 0
+ FfxFloat32x4 v =
+ SpdReduceIntermediate(FfxUInt32x2(x * 4 + 0 + 0, y * 4 + 0), FfxUInt32x2(x * 4 + 2 + 0, y * 4 + 0), FfxUInt32x2(x * 4 + 0 + 1, y * 4 + 2), FfxUInt32x2(x * 4 + 2 + 1, y * 4 + 2));
+ SpdStore(FfxInt32x2(workGroupID.xy * 4) + FfxInt32x2(x, y), v, mip, slice);
+ // store to LDS
+ // x 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0
+ // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ // 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0 0
+ // ...
+ // 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0
+ // ...
+ // 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x
+ // ...
+ SpdStoreIntermediate(x * 4 + y, y * 4, v);
+ }
+#else
+ if (localInvocationIndex < 64)
+ {
+ FfxFloat32x4 v = SpdLoadIntermediate(x * 2 + y % 2, y * 2);
+ v = SpdReduceQuad(v);
+ // quad index 0 stores result
+ if (localInvocationIndex % 4 == 0)
+ {
+ SpdStore(FfxInt32x2(workGroupID.xy * 4) + FfxInt32x2(x / 2, y / 2), v, mip, slice);
+ SpdStoreIntermediate(x * 2 + y / 2, y * 2, v);
+ }
+ }
+#endif
+}
+
+void SpdDownsampleMip_4(FfxUInt32 x, FfxUInt32 y, FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mip, FfxUInt32 slice)
+{
+#if defined(FFX_SPD_NO_WAVE_OPERATIONS)
+ if (localInvocationIndex < 4)
+ {
+ // x 0 0 0 x 0 0 0
+ // ...
+ // 0 x 0 0 0 x 0 0
+ FfxFloat32x4 v = SpdReduceIntermediate(FfxUInt32x2(x * 8 + 0 + 0 + y * 2, y * 8 + 0),
+ FfxUInt32x2(x * 8 + 4 + 0 + y * 2, y * 8 + 0),
+ FfxUInt32x2(x * 8 + 0 + 1 + y * 2, y * 8 + 4),
+ FfxUInt32x2(x * 8 + 4 + 1 + y * 2, y * 8 + 4));
+ SpdStore(FfxInt32x2(workGroupID.xy * 2) + FfxInt32x2(x, y), v, mip, slice);
+ // store to LDS
+ // x x x x 0 ...
+ // 0 ...
+ SpdStoreIntermediate(x + y * 2, 0, v);
+ }
+#else
+ if (localInvocationIndex < 16)
+ {
+ FfxFloat32x4 v = SpdLoadIntermediate(x * 4 + y, y * 4);
+ v = SpdReduceQuad(v);
+ // quad index 0 stores result
+ if (localInvocationIndex % 4 == 0)
+ {
+ SpdStore(FfxInt32x2(workGroupID.xy * 2) + FfxInt32x2(x / 2, y / 2), v, mip, slice);
+ SpdStoreIntermediate(x / 2 + y, 0, v);
+ }
+ }
+#endif
+}
+
+void SpdDownsampleMip_5(FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mip, FfxUInt32 slice)
+{
+#if defined(FFX_SPD_NO_WAVE_OPERATIONS)
+ if (localInvocationIndex < 1)
+ {
+ // x x x x 0 ...
+ // 0 ...
+ FfxFloat32x4 v = SpdReduceIntermediate(FfxUInt32x2(0, 0), FfxUInt32x2(1, 0), FfxUInt32x2(2, 0), FfxUInt32x2(3, 0));
+ SpdStore(FfxInt32x2(workGroupID.xy), v, mip, slice);
+ }
+#else
+ if (localInvocationIndex < 4)
+ {
+ FfxFloat32x4 v = SpdLoadIntermediate(localInvocationIndex, 0);
+ v = SpdReduceQuad(v);
+ // quad index 0 stores result
+ if (localInvocationIndex % 4 == 0)
+ {
+ SpdStore(FfxInt32x2(workGroupID.xy), v, mip, slice);
+ }
+ }
+#endif
+}
+
+void SpdDownsampleMips_6_7(FfxUInt32 x, FfxUInt32 y, FfxUInt32 mips, FfxUInt32 slice)
+{
+ FfxInt32x2 tex = FfxInt32x2(x * 4 + 0, y * 4 + 0);
+ FfxInt32x2 pix = FfxInt32x2(x * 2 + 0, y * 2 + 0);
+ FfxFloat32x4 v0 = SpdReduceLoad4(tex, slice);
+ SpdStore(pix, v0, 6, slice);
+
+ tex = FfxInt32x2(x * 4 + 2, y * 4 + 0);
+ pix = FfxInt32x2(x * 2 + 1, y * 2 + 0);
+ FfxFloat32x4 v1 = SpdReduceLoad4(tex, slice);
+ SpdStore(pix, v1, 6, slice);
+
+ tex = FfxInt32x2(x * 4 + 0, y * 4 + 2);
+ pix = FfxInt32x2(x * 2 + 0, y * 2 + 1);
+ FfxFloat32x4 v2 = SpdReduceLoad4(tex, slice);
+ SpdStore(pix, v2, 6, slice);
+
+ tex = FfxInt32x2(x * 4 + 2, y * 4 + 2);
+ pix = FfxInt32x2(x * 2 + 1, y * 2 + 1);
+ FfxFloat32x4 v3 = SpdReduceLoad4(tex, slice);
+ SpdStore(pix, v3, 6, slice);
+
+ if (mips <= 7)
+ return;
+ // no barrier needed, working on values only from the same thread
+
+ FfxFloat32x4 v = SpdReduce4(v0, v1, v2, v3);
+ SpdStore(FfxInt32x2(x, y), v, 7, slice);
+ SpdStoreIntermediate(x, y, v);
+}
+
+void SpdDownsampleNextFour(FfxUInt32 x, FfxUInt32 y, FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 baseMip, FfxUInt32 mips, FfxUInt32 slice)
+{
+ if (mips <= baseMip)
+ return;
+ ffxSpdWorkgroupShuffleBarrier();
+ SpdDownsampleMip_2(x, y, workGroupID, localInvocationIndex, baseMip, slice);
+
+ if (mips <= baseMip + 1)
+ return;
+ ffxSpdWorkgroupShuffleBarrier();
+ SpdDownsampleMip_3(x, y, workGroupID, localInvocationIndex, baseMip + 1, slice);
+
+ if (mips <= baseMip + 2)
+ return;
+ ffxSpdWorkgroupShuffleBarrier();
+ SpdDownsampleMip_4(x, y, workGroupID, localInvocationIndex, baseMip + 2, slice);
+
+ if (mips <= baseMip + 3)
+ return;
+ ffxSpdWorkgroupShuffleBarrier();
+ SpdDownsampleMip_5(workGroupID, localInvocationIndex, baseMip + 3, slice);
+}
+
+/// Downsamples a 64x64 tile based on the work group id.
+/// If after downsampling it's the last active thread group, computes the remaining MIP levels.
+///
+/// @param [in] workGroupID index of the work group / thread group
+/// @param [in] localInvocationIndex index of the thread within the thread group in 1D
+/// @param [in] mips the number of total MIP levels to compute for the input texture
+/// @param [in] numWorkGroups the total number of dispatched work groups / thread groups for this slice
+/// @param [in] slice the slice of the input texture
+///
+/// @ingroup FfxGPUSpd
+void SpdDownsample(FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mips, FfxUInt32 numWorkGroups, FfxUInt32 slice)
+{
+ // compute MIP level 0 and 1
+ FfxUInt32x2 sub_xy = ffxRemapForWaveReduction(localInvocationIndex % 64);
+ FfxUInt32 x = sub_xy.x + 8 * ((localInvocationIndex >> 6) % 2);
+ FfxUInt32 y = sub_xy.y + 8 * ((localInvocationIndex >> 7));
+ SpdDownsampleMips_0_1(x, y, workGroupID, localInvocationIndex, mips, slice);
+
+ // compute MIP level 2, 3, 4, 5
+ SpdDownsampleNextFour(x, y, workGroupID, localInvocationIndex, 2, mips, slice);
+
+ if (mips <= 6)
+ return;
+
+ // increase the global atomic counter for the given slice and check if it's the last remaining thread group:
+ // terminate if not, continue if yes.
+ if (SpdExitWorkgroup(numWorkGroups, localInvocationIndex, slice))
+ return;
+
+ // reset the global atomic counter back to 0 for the next spd dispatch
+ SpdResetAtomicCounter(slice);
+
+ // After mip 5 there is only a single workgroup left that downsamples the remaining up to 64x64 texels.
+ // compute MIP level 6 and 7
+ SpdDownsampleMips_6_7(x, y, mips, slice);
+
+ // compute MIP level 8, 9, 10, 11
+ SpdDownsampleNextFour(x, y, FfxUInt32x2(0, 0), localInvocationIndex, 8, mips, slice);
+}
+/// Downsamples a 64x64 tile based on the work group id and work group offset.
+/// If after downsampling it's the last active thread group, computes the remaining MIP levels.
+///
+/// @param [in] workGroupID index of the work group / thread group
+/// @param [in] localInvocationIndex index of the thread within the thread group in 1D
+/// @param [in] mips the number of total MIP levels to compute for the input texture
+/// @param [in] numWorkGroups the total number of dispatched work groups / thread groups for this slice
+/// @param [in] slice the slice of the input texture
+/// @param [in] workGroupOffset the work group offset. it's (0,0) in case the entire input texture is downsampled.
+///
+/// @ingroup FfxGPUSpd
+void SpdDownsample(FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mips, FfxUInt32 numWorkGroups, FfxUInt32 slice, FfxUInt32x2 workGroupOffset)
+{
+ SpdDownsample(workGroupID + workGroupOffset, localInvocationIndex, mips, numWorkGroups, slice);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+//==============================================================================================================================
+// PACKED VERSION
+//==============================================================================================================================
+
+#if FFX_HALF
+
+#if defined(FFX_GLSL)
+#extension GL_EXT_shader_subgroup_extended_types_float16:require
+#endif
+
+FfxFloat16x4 SpdReduceQuadH(FfxFloat16x4 v)
+{
+#if defined(FFX_GLSL) && !defined(FFX_SPD_NO_WAVE_OPERATIONS)
+ FfxFloat16x4 v0 = v;
+ FfxFloat16x4 v1 = subgroupQuadSwapHorizontal(v);
+ FfxFloat16x4 v2 = subgroupQuadSwapVertical(v);
+ FfxFloat16x4 v3 = subgroupQuadSwapDiagonal(v);
+ return SpdReduce4H(v0, v1, v2, v3);
+#elif defined(FFX_HLSL) && !defined(FFX_SPD_NO_WAVE_OPERATIONS)
+ // requires SM6.0
+ FfxFloat16x4 v0 = v;
+ FfxFloat16x4 v1 = QuadReadAcrossX(v);
+ FfxFloat16x4 v2 = QuadReadAcrossY(v);
+ FfxFloat16x4 v3 = QuadReadAcrossDiagonal(v);
+ return SpdReduce4H(v0, v1, v2, v3);
+/*
+ // if SM6.0 is not available, you can use the AMD shader intrinsics
+ // the AMD shader intrinsics are available in AMD GPU Services (AGS) library:
+ // https://gpuopen.com/amd-gpu-services-ags-library/
+ // works for DX11
+ FfxFloat16x4 v0 = v;
+ FfxFloat16x4 v1;
+ v1.x = AmdExtD3DShaderIntrinsics_SwizzleF(v.x, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1);
+ v1.y = AmdExtD3DShaderIntrinsics_SwizzleF(v.y, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1);
+ v1.z = AmdExtD3DShaderIntrinsics_SwizzleF(v.z, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1);
+ v1.w = AmdExtD3DShaderIntrinsics_SwizzleF(v.w, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1);
+ FfxFloat16x4 v2;
+ v2.x = AmdExtD3DShaderIntrinsics_SwizzleF(v.x, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2);
+ v2.y = AmdExtD3DShaderIntrinsics_SwizzleF(v.y, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2);
+ v2.z = AmdExtD3DShaderIntrinsics_SwizzleF(v.z, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2);
+ v2.w = AmdExtD3DShaderIntrinsics_SwizzleF(v.w, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2);
+ FfxFloat16x4 v3;
+ v3.x = AmdExtD3DShaderIntrinsics_SwizzleF(v.x, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4);
+ v3.y = AmdExtD3DShaderIntrinsics_SwizzleF(v.y, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4);
+ v3.z = AmdExtD3DShaderIntrinsics_SwizzleF(v.z, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4);
+ v3.w = AmdExtD3DShaderIntrinsics_SwizzleF(v.w, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4);
+ return SpdReduce4H(v0, v1, v2, v3);
+ */
+#endif
+ return FfxFloat16x4(0.0, 0.0, 0.0, 0.0);
+}
+
+FfxFloat16x4 SpdReduceIntermediateH(FfxUInt32x2 i0, FfxUInt32x2 i1, FfxUInt32x2 i2, FfxUInt32x2 i3)
+{
+ FfxFloat16x4 v0 = SpdLoadIntermediateH(i0.x, i0.y);
+ FfxFloat16x4 v1 = SpdLoadIntermediateH(i1.x, i1.y);
+ FfxFloat16x4 v2 = SpdLoadIntermediateH(i2.x, i2.y);
+ FfxFloat16x4 v3 = SpdLoadIntermediateH(i3.x, i3.y);
+ return SpdReduce4H(v0, v1, v2, v3);
+}
+
+FfxFloat16x4 SpdReduceLoad4H(FfxUInt32x2 i0, FfxUInt32x2 i1, FfxUInt32x2 i2, FfxUInt32x2 i3, FfxUInt32 slice)
+{
+ FfxFloat16x4 v0 = SpdLoadH(FfxInt32x2(i0), slice);
+ FfxFloat16x4 v1 = SpdLoadH(FfxInt32x2(i1), slice);
+ FfxFloat16x4 v2 = SpdLoadH(FfxInt32x2(i2), slice);
+ FfxFloat16x4 v3 = SpdLoadH(FfxInt32x2(i3), slice);
+ return SpdReduce4H(v0, v1, v2, v3);
+}
+
+FfxFloat16x4 SpdReduceLoad4H(FfxUInt32x2 base, FfxUInt32 slice)
+{
+ return SpdReduceLoad4H(FfxUInt32x2(base + FfxUInt32x2(0, 0)), FfxUInt32x2(base + FfxUInt32x2(0, 1)), FfxUInt32x2(base + FfxUInt32x2(1, 0)), FfxUInt32x2(base + FfxUInt32x2(1, 1)), slice);
+}
+
+FfxFloat16x4 SpdReduceLoadSourceImage4H(FfxUInt32x2 i0, FfxUInt32x2 i1, FfxUInt32x2 i2, FfxUInt32x2 i3, FfxUInt32 slice)
+{
+ FfxFloat16x4 v0 = SpdLoadSourceImageH(FfxInt32x2(i0), slice);
+ FfxFloat16x4 v1 = SpdLoadSourceImageH(FfxInt32x2(i1), slice);
+ FfxFloat16x4 v2 = SpdLoadSourceImageH(FfxInt32x2(i2), slice);
+ FfxFloat16x4 v3 = SpdLoadSourceImageH(FfxInt32x2(i3), slice);
+ return SpdReduce4H(v0, v1, v2, v3);
+}
+
+FfxFloat16x4 SpdReduceLoadSourceImageH(FfxUInt32x2 base, FfxUInt32 slice)
+{
+#if defined(SPD_LINEAR_SAMPLER)
+ return SpdLoadSourceImageH(FfxInt32x2(base), slice);
+#else
+ return SpdReduceLoadSourceImage4H(FfxUInt32x2(base + FfxUInt32x2(0, 0)), FfxUInt32x2(base + FfxUInt32x2(0, 1)), FfxUInt32x2(base + FfxUInt32x2(1, 0)), FfxUInt32x2(base + FfxUInt32x2(1, 1)), slice);
+#endif
+}
+
+void SpdDownsampleMips_0_1_IntrinsicsH(FfxUInt32 x, FfxUInt32 y, FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mips, FfxUInt32 slice)
+{
+ FfxFloat16x4 v[4];
+
+ FfxInt32x2 tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2, y * 2);
+ FfxInt32x2 pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x, y);
+ v[0] = SpdReduceLoadSourceImageH(tex, slice);
+ SpdStoreH(pix, v[0], 0, slice);
+
+ tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2 + 32, y * 2);
+ pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x + 16, y);
+ v[1] = SpdReduceLoadSourceImageH(tex, slice);
+ SpdStoreH(pix, v[1], 0, slice);
+
+ tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2, y * 2 + 32);
+ pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x, y + 16);
+ v[2] = SpdReduceLoadSourceImageH(tex, slice);
+ SpdStoreH(pix, v[2], 0, slice);
+
+ tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2 + 32, y * 2 + 32);
+ pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x + 16, y + 16);
+ v[3] = SpdReduceLoadSourceImageH(tex, slice);
+ SpdStoreH(pix, v[3], 0, slice);
+
+ if (mips <= 1)
+ return;
+
+ v[0] = SpdReduceQuadH(v[0]);
+ v[1] = SpdReduceQuadH(v[1]);
+ v[2] = SpdReduceQuadH(v[2]);
+ v[3] = SpdReduceQuadH(v[3]);
+
+ if ((localInvocationIndex % 4) == 0)
+ {
+ SpdStoreH(FfxInt32x2(workGroupID.xy * 16) + FfxInt32x2(x / 2, y / 2), v[0], 1, slice);
+ SpdStoreIntermediateH(x / 2, y / 2, v[0]);
+
+ SpdStoreH(FfxInt32x2(workGroupID.xy * 16) + FfxInt32x2(x / 2 + 8, y / 2), v[1], 1, slice);
+ SpdStoreIntermediateH(x / 2 + 8, y / 2, v[1]);
+
+ SpdStoreH(FfxInt32x2(workGroupID.xy * 16) + FfxInt32x2(x / 2, y / 2 + 8), v[2], 1, slice);
+ SpdStoreIntermediateH(x / 2, y / 2 + 8, v[2]);
+
+ SpdStoreH(FfxInt32x2(workGroupID.xy * 16) + FfxInt32x2(x / 2 + 8, y / 2 + 8), v[3], 1, slice);
+ SpdStoreIntermediateH(x / 2 + 8, y / 2 + 8, v[3]);
+ }
+}
+
+void SpdDownsampleMips_0_1_LDSH(FfxUInt32 x, FfxUInt32 y, FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mips, FfxUInt32 slice)
+{
+ FfxFloat16x4 v[4];
+
+ FfxInt32x2 tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2, y * 2);
+ FfxInt32x2 pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x, y);
+ v[0] = SpdReduceLoadSourceImageH(tex, slice);
+ SpdStoreH(pix, v[0], 0, slice);
+
+ tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2 + 32, y * 2);
+ pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x + 16, y);
+ v[1] = SpdReduceLoadSourceImageH(tex, slice);
+ SpdStoreH(pix, v[1], 0, slice);
+
+ tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2, y * 2 + 32);
+ pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x, y + 16);
+ v[2] = SpdReduceLoadSourceImageH(tex, slice);
+ SpdStoreH(pix, v[2], 0, slice);
+
+ tex = FfxInt32x2(workGroupID.xy * 64) + FfxInt32x2(x * 2 + 32, y * 2 + 32);
+ pix = FfxInt32x2(workGroupID.xy * 32) + FfxInt32x2(x + 16, y + 16);
+ v[3] = SpdReduceLoadSourceImageH(tex, slice);
+ SpdStoreH(pix, v[3], 0, slice);
+
+ if (mips <= 1)
+ return;
+
+ for (FfxInt32 i = 0; i < 4; i++)
+ {
+ SpdStoreIntermediateH(x, y, v[i]);
+ ffxSpdWorkgroupShuffleBarrier();
+ if (localInvocationIndex < 64)
+ {
+ v[i] = SpdReduceIntermediateH(FfxUInt32x2(x * 2 + 0, y * 2 + 0), FfxUInt32x2(x * 2 + 1, y * 2 + 0), FfxUInt32x2(x * 2 + 0, y * 2 + 1), FfxUInt32x2(x * 2 + 1, y * 2 + 1));
+ SpdStoreH(FfxInt32x2(workGroupID.xy * 16) + FfxInt32x2(x + (i % 2) * 8, y + (i / 2) * 8), v[i], 1, slice);
+ }
+ ffxSpdWorkgroupShuffleBarrier();
+ }
+
+ if (localInvocationIndex < 64)
+ {
+ SpdStoreIntermediateH(x + 0, y + 0, v[0]);
+ SpdStoreIntermediateH(x + 8, y + 0, v[1]);
+ SpdStoreIntermediateH(x + 0, y + 8, v[2]);
+ SpdStoreIntermediateH(x + 8, y + 8, v[3]);
+ }
+}
+
+void SpdDownsampleMips_0_1H(FfxUInt32 x, FfxUInt32 y, FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mips, FfxUInt32 slice)
+{
+#if defined(FFX_SPD_NO_WAVE_OPERATIONS)
+ SpdDownsampleMips_0_1_LDSH(x, y, workGroupID, localInvocationIndex, mips, slice);
+#else
+ SpdDownsampleMips_0_1_IntrinsicsH(x, y, workGroupID, localInvocationIndex, mips, slice);
+#endif
+}
+
+
+void SpdDownsampleMip_2H(FfxUInt32 x, FfxUInt32 y, FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mip, FfxUInt32 slice)
+{
+#if defined(FFX_SPD_NO_WAVE_OPERATIONS)
+ if (localInvocationIndex < 64)
+ {
+ FfxFloat16x4 v = SpdReduceIntermediateH(FfxUInt32x2(x * 2 + 0, y * 2 + 0), FfxUInt32x2(x * 2 + 1, y * 2 + 0), FfxUInt32x2(x * 2 + 0, y * 2 + 1), FfxUInt32x2(x * 2 + 1, y * 2 + 1));
+ SpdStoreH(FfxInt32x2(workGroupID.xy * 8) + FfxInt32x2(x, y), v, mip, slice);
+ // store to LDS, try to reduce bank conflicts
+ // x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
+ // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ // 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 x
+ // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ // x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
+ // ...
+ // x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
+ SpdStoreIntermediateH(x * 2 + y % 2, y * 2, v);
+ }
+#else
+ FfxFloat16x4 v = SpdLoadIntermediateH(x, y);
+ v = SpdReduceQuadH(v);
+ // quad index 0 stores result
+ if (localInvocationIndex % 4 == 0)
+ {
+ SpdStoreH(FfxInt32x2(workGroupID.xy * 8) + FfxInt32x2(x / 2, y / 2), v, mip, slice);
+ SpdStoreIntermediateH(x + (y / 2) % 2, y, v);
+ }
+#endif
+}
+
+void SpdDownsampleMip_3H(FfxUInt32 x, FfxUInt32 y, FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mip, FfxUInt32 slice)
+{
+#if defined(FFX_SPD_NO_WAVE_OPERATIONS)
+ if (localInvocationIndex < 16)
+ {
+ // x 0 x 0
+ // 0 0 0 0
+ // 0 x 0 x
+ // 0 0 0 0
+ FfxFloat16x4 v =
+ SpdReduceIntermediateH(FfxUInt32x2(x * 4 + 0 + 0, y * 4 + 0), FfxUInt32x2(x * 4 + 2 + 0, y * 4 + 0), FfxUInt32x2(x * 4 + 0 + 1, y * 4 + 2), FfxUInt32x2(x * 4 + 2 + 1, y * 4 + 2));
+ SpdStoreH(FfxInt32x2(workGroupID.xy * 4) + FfxInt32x2(x, y), v, mip, slice);
+ // store to LDS
+ // x 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0
+ // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ // 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0 0
+ // ...
+ // 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0
+ // ...
+ // 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x
+ // ...
+ SpdStoreIntermediateH(x * 4 + y, y * 4, v);
+ }
+#else
+ if (localInvocationIndex < 64)
+ {
+ FfxFloat16x4 v = SpdLoadIntermediateH(x * 2 + y % 2, y * 2);
+ v = SpdReduceQuadH(v);
+ // quad index 0 stores result
+ if (localInvocationIndex % 4 == 0)
+ {
+ SpdStoreH(FfxInt32x2(workGroupID.xy * 4) + FfxInt32x2(x / 2, y / 2), v, mip, slice);
+ SpdStoreIntermediateH(x * 2 + y / 2, y * 2, v);
+ }
+ }
+#endif
+}
+
+void SpdDownsampleMip_4H(FfxUInt32 x, FfxUInt32 y, FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mip, FfxUInt32 slice)
+{
+#if defined(FFX_SPD_NO_WAVE_OPERATIONS)
+ if (localInvocationIndex < 4)
+ {
+ // x 0 0 0 x 0 0 0
+ // ...
+ // 0 x 0 0 0 x 0 0
+ FfxFloat16x4 v = SpdReduceIntermediateH(FfxUInt32x2(x * 8 + 0 + 0 + y * 2, y * 8 + 0),
+ FfxUInt32x2(x * 8 + 4 + 0 + y * 2, y * 8 + 0),
+ FfxUInt32x2(x * 8 + 0 + 1 + y * 2, y * 8 + 4),
+ FfxUInt32x2(x * 8 + 4 + 1 + y * 2, y * 8 + 4));
+ SpdStoreH(FfxInt32x2(workGroupID.xy * 2) + FfxInt32x2(x, y), v, mip, slice);
+ // store to LDS
+ // x x x x 0 ...
+ // 0 ...
+ SpdStoreIntermediateH(x + y * 2, 0, v);
+ }
+#else
+ if (localInvocationIndex < 16)
+ {
+ FfxFloat16x4 v = SpdLoadIntermediateH(x * 4 + y, y * 4);
+ v = SpdReduceQuadH(v);
+ // quad index 0 stores result
+ if (localInvocationIndex % 4 == 0)
+ {
+ SpdStoreH(FfxInt32x2(workGroupID.xy * 2) + FfxInt32x2(x / 2, y / 2), v, mip, slice);
+ SpdStoreIntermediateH(x / 2 + y, 0, v);
+ }
+ }
+#endif
+}
+
+void SpdDownsampleMip_5H(FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mip, FfxUInt32 slice)
+{
+#if defined(FFX_SPD_NO_WAVE_OPERATIONS)
+ if (localInvocationIndex < 1)
+ {
+ // x x x x 0 ...
+ // 0 ...
+ FfxFloat16x4 v = SpdReduceIntermediateH(FfxUInt32x2(0, 0), FfxUInt32x2(1, 0), FfxUInt32x2(2, 0), FfxUInt32x2(3, 0));
+ SpdStoreH(FfxInt32x2(workGroupID.xy), v, mip, slice);
+ }
+#else
+ if (localInvocationIndex < 4)
+ {
+ FfxFloat16x4 v = SpdLoadIntermediateH(localInvocationIndex, 0);
+ v = SpdReduceQuadH(v);
+ // quad index 0 stores result
+ if (localInvocationIndex % 4 == 0)
+ {
+ SpdStoreH(FfxInt32x2(workGroupID.xy), v, mip, slice);
+ }
+ }
+#endif
+}
+
+void SpdDownsampleMips_6_7H(FfxUInt32 x, FfxUInt32 y, FfxUInt32 mips, FfxUInt32 slice)
+{
+ FfxInt32x2 tex = FfxInt32x2(x * 4 + 0, y * 4 + 0);
+ FfxInt32x2 pix = FfxInt32x2(x * 2 + 0, y * 2 + 0);
+ FfxFloat16x4 v0 = SpdReduceLoad4H(tex, slice);
+ SpdStoreH(pix, v0, 6, slice);
+
+ tex = FfxInt32x2(x * 4 + 2, y * 4 + 0);
+ pix = FfxInt32x2(x * 2 + 1, y * 2 + 0);
+ FfxFloat16x4 v1 = SpdReduceLoad4H(tex, slice);
+ SpdStoreH(pix, v1, 6, slice);
+
+ tex = FfxInt32x2(x * 4 + 0, y * 4 + 2);
+ pix = FfxInt32x2(x * 2 + 0, y * 2 + 1);
+ FfxFloat16x4 v2 = SpdReduceLoad4H(tex, slice);
+ SpdStoreH(pix, v2, 6, slice);
+
+ tex = FfxInt32x2(x * 4 + 2, y * 4 + 2);
+ pix = FfxInt32x2(x * 2 + 1, y * 2 + 1);
+ FfxFloat16x4 v3 = SpdReduceLoad4H(tex, slice);
+ SpdStoreH(pix, v3, 6, slice);
+
+ if (mips < 8)
+ return;
+ // no barrier needed, working on values only from the same thread
+
+ FfxFloat16x4 v = SpdReduce4H(v0, v1, v2, v3);
+ SpdStoreH(FfxInt32x2(x, y), v, 7, slice);
+ SpdStoreIntermediateH(x, y, v);
+}
+
+void SpdDownsampleNextFourH(FfxUInt32 x, FfxUInt32 y, FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 baseMip, FfxUInt32 mips, FfxUInt32 slice)
+{
+ if (mips <= baseMip)
+ return;
+ ffxSpdWorkgroupShuffleBarrier();
+ SpdDownsampleMip_2H(x, y, workGroupID, localInvocationIndex, baseMip, slice);
+
+ if (mips <= baseMip + 1)
+ return;
+ ffxSpdWorkgroupShuffleBarrier();
+ SpdDownsampleMip_3H(x, y, workGroupID, localInvocationIndex, baseMip + 1, slice);
+
+ if (mips <= baseMip + 2)
+ return;
+ ffxSpdWorkgroupShuffleBarrier();
+ SpdDownsampleMip_4H(x, y, workGroupID, localInvocationIndex, baseMip + 2, slice);
+
+ if (mips <= baseMip + 3)
+ return;
+ ffxSpdWorkgroupShuffleBarrier();
+ SpdDownsampleMip_5H(workGroupID, localInvocationIndex, baseMip + 3, slice);
+}
+
+/// Downsamples a 64x64 tile based on the work group id and work group offset.
+/// If after downsampling it's the last active thread group, computes the remaining MIP levels.
+/// Uses half types.
+///
+/// @param [in] workGroupID index of the work group / thread group
+/// @param [in] localInvocationIndex index of the thread within the thread group in 1D
+/// @param [in] mips the number of total MIP levels to compute for the input texture
+/// @param [in] numWorkGroups the total number of dispatched work groups / thread groups for this slice
+/// @param [in] slice the slice of the input texture
+///
+/// @ingroup FfxGPUSpd
+void SpdDownsampleH(FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mips, FfxUInt32 numWorkGroups, FfxUInt32 slice)
+{
+ FfxUInt32x2 sub_xy = ffxRemapForWaveReduction(localInvocationIndex % 64);
+ FfxUInt32 x = sub_xy.x + 8 * ((localInvocationIndex >> 6) % 2);
+ FfxUInt32 y = sub_xy.y + 8 * ((localInvocationIndex >> 7));
+
+ // compute MIP level 0 and 1
+ SpdDownsampleMips_0_1H(x, y, workGroupID, localInvocationIndex, mips, slice);
+
+ // compute MIP level 2, 3, 4, 5
+ SpdDownsampleNextFourH(x, y, workGroupID, localInvocationIndex, 2, mips, slice);
+
+ if (mips < 7)
+ return;
+
+ // increase the global atomic counter for the given slice and check if it's the last remaining thread group:
+ // terminate if not, continue if yes.
+ if (SpdExitWorkgroup(numWorkGroups, localInvocationIndex, slice))
+ return;
+
+ // reset the global atomic counter back to 0 for the next spd dispatch
+ SpdResetAtomicCounter(slice);
+
+ // After mip 5 there is only a single workgroup left that downsamples the remaining up to 64x64 texels.
+ // compute MIP level 6 and 7
+ SpdDownsampleMips_6_7H(x, y, mips, slice);
+
+ // compute MIP level 8, 9, 10, 11
+ SpdDownsampleNextFourH(x, y, FfxUInt32x2(0, 0), localInvocationIndex, 8, mips, slice);
+}
+
+/// Downsamples a 64x64 tile based on the work group id and work group offset.
+/// If after downsampling it's the last active thread group, computes the remaining MIP levels.
+/// Uses half types.
+///
+/// @param [in] workGroupID index of the work group / thread group
+/// @param [in] localInvocationIndex index of the thread within the thread group in 1D
+/// @param [in] mips the number of total MIP levels to compute for the input texture
+/// @param [in] numWorkGroups the total number of dispatched work groups / thread groups for this slice
+/// @param [in] slice the slice of the input texture
+/// @param [in] workGroupOffset the work group offset. it's (0,0) in case the entire input texture is downsampled.
+///
+/// @ingroup FfxGPUSpd
+void SpdDownsampleH(FfxUInt32x2 workGroupID, FfxUInt32 localInvocationIndex, FfxUInt32 mips, FfxUInt32 numWorkGroups, FfxUInt32 slice, FfxUInt32x2 workGroupOffset)
+{
+ SpdDownsampleH(workGroupID + workGroupOffset, localInvocationIndex, mips, numWorkGroups, slice);
+}
+
+#endif // #if FFX_HALF
+#endif // #if defined(FFX_GPU)
diff --git a/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_accumulate_pass.glsl b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_accumulate_pass.glsl
new file mode 100644
index 0000000000..3b93575536
--- /dev/null
+++ b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_accumulate_pass.glsl
@@ -0,0 +1,75 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#version 450
+
+#extension GL_GOOGLE_include_directive : require
+#extension GL_EXT_samplerless_texture_functions : require
+// Needed for rw_upscaled_output declaration
+#extension GL_EXT_shader_image_load_formatted : require
+
+#define FSR3UPSCALER_BIND_SRV_INPUT_EXPOSURE 0
+#define FSR3UPSCALER_BIND_SRV_DILATED_REACTIVE_MASKS 1
+#if FFX_FSR3UPSCALER_OPTION_LOW_RESOLUTION_MOTION_VECTORS
+#define FSR3UPSCALER_BIND_SRV_DILATED_MOTION_VECTORS 2
+#else
+#define FSR3UPSCALER_BIND_SRV_INPUT_MOTION_VECTORS 2
+#endif
+#define FSR3UPSCALER_BIND_SRV_INTERNAL_UPSCALED 3
+#define FSR3UPSCALER_BIND_SRV_LANCZOS_LUT 4
+#define FSR3UPSCALER_BIND_SRV_FARTHEST_DEPTH_MIP1 5
+
+#define FSR3UPSCALER_BIND_SRV_CURRENT_LUMA 6
+#define FSR3UPSCALER_BIND_SRV_LUMA_INSTABILITY 7
+#define FSR3UPSCALER_BIND_SRV_INPUT_COLOR 8
+
+#define FSR3UPSCALER_BIND_UAV_INTERNAL_UPSCALED 9
+#define FSR3UPSCALER_BIND_UAV_UPSCALED_OUTPUT 10
+#define FSR3UPSCALER_BIND_UAV_NEW_LOCKS 11
+
+#define FSR3UPSCALER_BIND_CB_FSR3UPSCALER 12
+
+#include "fsr3upscaler/ffx_fsr3upscaler_callbacks_glsl.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_common.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_sample.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_upsample.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_reproject.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_accumulate.h"
+
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH 8
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#define FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT 8
+#endif // FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH 1
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#ifndef FFX_FSR3UPSCALER_NUM_THREADS
+#define FFX_FSR3UPSCALER_NUM_THREADS layout (local_size_x = FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH, local_size_y = FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT, local_size_z = FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH) in;
+#endif // #ifndef FFX_FSR3UPSCALER_NUM_THREADS
+
+FFX_FSR3UPSCALER_NUM_THREADS
+void main()
+{
+ Accumulate(FfxInt32x2(gl_GlobalInvocationID.xy));
+}
diff --git a/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_luma_instability_pass.glsl b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_luma_instability_pass.glsl
new file mode 100644
index 0000000000..6d5db88e1c
--- /dev/null
+++ b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_luma_instability_pass.glsl
@@ -0,0 +1,62 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#version 450
+
+#extension GL_GOOGLE_include_directive : require
+#extension GL_EXT_samplerless_texture_functions : require
+
+#define FSR3UPSCALER_BIND_SRV_INPUT_EXPOSURE 0
+#define FSR3UPSCALER_BIND_SRV_DILATED_REACTIVE_MASKS 1
+#define FSR3UPSCALER_BIND_SRV_DILATED_MOTION_VECTORS 2
+#define FSR3UPSCALER_BIND_SRV_FRAME_INFO 3
+#define FSR3UPSCALER_BIND_SRV_LUMA_HISTORY 4
+#define FSR3UPSCALER_BIND_SRV_FARTHEST_DEPTH_MIP1 5
+#define FSR3UPSCALER_BIND_SRV_CURRENT_LUMA 6
+
+#define FSR3UPSCALER_BIND_UAV_LUMA_HISTORY 7
+#define FSR3UPSCALER_BIND_UAV_LUMA_INSTABILITY 8
+
+#define FSR3UPSCALER_BIND_CB_FSR3UPSCALER 9
+
+#include "fsr3upscaler/ffx_fsr3upscaler_callbacks_glsl.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_common.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_luma_instability.h"
+
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH 8
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#define FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT 8
+#endif // FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH 1
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#ifndef FFX_FSR3UPSCALER_NUM_THREADS
+#define FFX_FSR3UPSCALER_NUM_THREADS layout (local_size_x = FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH, local_size_y = FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT, local_size_z = FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH) in;
+#endif // #ifndef FFX_FSR3UPSCALER_NUM_THREADS
+
+FFX_FSR3UPSCALER_NUM_THREADS
+void main()
+{
+ LumaInstability(FfxInt32x2(gl_GlobalInvocationID.xy));
+}
diff --git a/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_luma_pyramid_pass.glsl b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_luma_pyramid_pass.glsl
new file mode 100644
index 0000000000..29f4c8f8dd
--- /dev/null
+++ b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_luma_pyramid_pass.glsl
@@ -0,0 +1,65 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#version 450
+
+#extension GL_GOOGLE_include_directive : require
+#extension GL_EXT_samplerless_texture_functions : require
+
+#define FSR3UPSCALER_BIND_SRV_CURRENT_LUMA 0
+#define FSR3UPSCALER_BIND_SRV_FARTHEST_DEPTH 1
+
+#define FSR3UPSCALER_BIND_UAV_SPD_GLOBAL_ATOMIC 2
+#define FSR3UPSCALER_BIND_UAV_FRAME_INFO 3
+#define FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_0 4
+#define FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_1 5
+#define FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_2 6
+#define FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_3 7
+#define FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_4 8
+#define FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_5 9
+#define FSR3UPSCALER_BIND_UAV_FARTHEST_DEPTH_MIP1 10
+
+#define FSR3UPSCALER_BIND_CB_FSR3UPSCALER 11
+#define FSR3UPSCALER_BIND_CB_SPD 12
+
+#include "fsr3upscaler/ffx_fsr3upscaler_callbacks_glsl.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_common.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_luma_pyramid.h"
+
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH 256
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#define FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT 1
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH 1
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#ifndef FFX_FSR3UPSCALER_NUM_THREADS
+#define FFX_FSR3UPSCALER_NUM_THREADS layout (local_size_x = FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH, local_size_y = FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT, local_size_z = FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH) in;
+#endif // #ifndef FFX_FSR3UPSCALER_NUM_THREADS
+
+FFX_FSR3UPSCALER_NUM_THREADS
+void main()
+{
+ ComputeAutoExposure(gl_WorkGroupID, gl_LocalInvocationIndex);
+}
diff --git a/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_prepare_inputs_pass.glsl b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_prepare_inputs_pass.glsl
new file mode 100644
index 0000000000..6e3c572233
--- /dev/null
+++ b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_prepare_inputs_pass.glsl
@@ -0,0 +1,61 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#version 450
+
+#extension GL_GOOGLE_include_directive : require
+#extension GL_EXT_samplerless_texture_functions : require
+
+#define FSR3UPSCALER_BIND_SRV_INPUT_MOTION_VECTORS 0
+#define FSR3UPSCALER_BIND_SRV_INPUT_DEPTH 1
+#define FSR3UPSCALER_BIND_SRV_INPUT_COLOR 2
+
+#define FSR3UPSCALER_BIND_UAV_DILATED_MOTION_VECTORS 3
+#define FSR3UPSCALER_BIND_UAV_DILATED_DEPTH 4
+#define FSR3UPSCALER_BIND_UAV_RECONSTRUCTED_PREV_NEAREST_DEPTH 5
+#define FSR3UPSCALER_BIND_UAV_FARTHEST_DEPTH 6
+#define FSR3UPSCALER_BIND_UAV_CURRENT_LUMA 7
+
+#define FSR3UPSCALER_BIND_CB_FSR3UPSCALER 8
+
+#include "fsr3upscaler/ffx_fsr3upscaler_callbacks_glsl.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_common.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_prepare_inputs.h"
+
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH 8
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#define FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT 8
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH 1
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#ifndef FFX_FSR3UPSCALER_NUM_THREADS
+#define FFX_FSR3UPSCALER_NUM_THREADS layout (local_size_x = FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH, local_size_y = FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT, local_size_z = FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH) in;
+#endif // #ifndef FFX_FSR3UPSCALER_NUM_THREADS
+
+FFX_FSR3UPSCALER_NUM_THREADS
+void main()
+{
+ PrepareInputs(FfxInt32x2(gl_GlobalInvocationID.xy));
+}
diff --git a/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_prepare_reactivity_pass.glsl b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_prepare_reactivity_pass.glsl
new file mode 100644
index 0000000000..e09d5d36df
--- /dev/null
+++ b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_prepare_reactivity_pass.glsl
@@ -0,0 +1,65 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#version 450
+
+#extension GL_GOOGLE_include_directive : require
+#extension GL_EXT_samplerless_texture_functions : require
+
+#define FSR3UPSCALER_BIND_SRV_RECONSTRUCTED_PREV_NEAREST_DEPTH 0
+#define FSR3UPSCALER_BIND_SRV_DILATED_MOTION_VECTORS 1
+#define FSR3UPSCALER_BIND_SRV_DILATED_DEPTH 2
+#define FSR3UPSCALER_BIND_SRV_REACTIVE_MASK 3
+#define FSR3UPSCALER_BIND_SRV_TRANSPARENCY_AND_COMPOSITION_MASK 4
+#define FSR3UPSCALER_BIND_SRV_ACCUMULATION 5
+#define FSR3UPSCALER_BIND_SRV_SHADING_CHANGE 6
+#define FSR3UPSCALER_BIND_SRV_CURRENT_LUMA 7
+#define FSR3UPSCALER_BIND_SRV_INPUT_EXPOSURE 8
+
+#define FSR3UPSCALER_BIND_UAV_DILATED_REACTIVE_MASKS 9
+#define FSR3UPSCALER_BIND_UAV_NEW_LOCKS 10
+#define FSR3UPSCALER_BIND_UAV_ACCUMULATION 11
+
+#define FSR3UPSCALER_BIND_CB_FSR3UPSCALER 12
+
+#include "fsr3upscaler/ffx_fsr3upscaler_callbacks_glsl.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_common.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_prepare_reactivity.h"
+
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH 8
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#define FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT 8
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH 1
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#ifndef FFX_FSR3UPSCALER_NUM_THREADS
+#define FFX_FSR3UPSCALER_NUM_THREADS layout (local_size_x = FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH, local_size_y = FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT, local_size_z = FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH) in;
+#endif // #ifndef FFX_FSR3UPSCALER_NUM_THREADS
+
+FFX_FSR3UPSCALER_NUM_THREADS
+void main()
+{
+ PrepareReactivity(FfxInt32x2(gl_GlobalInvocationID.xy));
+}
diff --git a/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_rcas_pass.glsl b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_rcas_pass.glsl
new file mode 100644
index 0000000000..112c2e5fbc
--- /dev/null
+++ b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_rcas_pass.glsl
@@ -0,0 +1,59 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#version 450
+
+#extension GL_GOOGLE_include_directive : require
+#extension GL_EXT_samplerless_texture_functions : require
+// Needed for rw_upscaled_output declaration
+#extension GL_EXT_shader_image_load_formatted : require
+
+#define FSR3UPSCALER_BIND_SRV_INPUT_EXPOSURE 0
+#define FSR3UPSCALER_BIND_SRV_RCAS_INPUT 1
+
+#define FSR3UPSCALER_BIND_UAV_UPSCALED_OUTPUT 2
+
+#define FSR3UPSCALER_BIND_CB_FSR3UPSCALER 3
+#define FSR3UPSCALER_BIND_CB_RCAS 4
+
+#include "fsr3upscaler/ffx_fsr3upscaler_callbacks_glsl.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_common.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_rcas.h"
+
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH 64
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#define FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT 1
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH 1
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#ifndef FFX_FSR3UPSCALER_NUM_THREADS
+#define FFX_FSR3UPSCALER_NUM_THREADS layout (local_size_x = FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH, local_size_y = FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT, local_size_z = FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH) in;
+#endif // #ifndef FFX_FSR3UPSCALER_NUM_THREADS
+
+FFX_FSR3UPSCALER_NUM_THREADS
+void main()
+{
+ RCAS(gl_LocalInvocationID.xyz, gl_WorkGroupID.xyz, gl_GlobalInvocationID.xyz);
+}
diff --git a/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_shading_change_pass.glsl b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_shading_change_pass.glsl
new file mode 100644
index 0000000000..539728f0a1
--- /dev/null
+++ b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_shading_change_pass.glsl
@@ -0,0 +1,55 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#version 450
+
+#extension GL_GOOGLE_include_directive : require
+#extension GL_EXT_samplerless_texture_functions : require
+
+#define FSR3UPSCALER_BIND_SRV_SPD_MIPS 0
+
+#define FSR3UPSCALER_BIND_UAV_SHADING_CHANGE 1
+
+#define FSR3UPSCALER_BIND_CB_FSR3UPSCALER 2
+
+#include "fsr3upscaler/ffx_fsr3upscaler_callbacks_glsl.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_common.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_shading_change.h"
+
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH 8
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#define FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT 8
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH 1
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#ifndef FFX_FSR3UPSCALER_NUM_THREADS
+#define FFX_FSR3UPSCALER_NUM_THREADS layout (local_size_x = FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH, local_size_y = FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT, local_size_z = FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH) in;
+#endif // #ifndef FFX_FSR3UPSCALER_NUM_THREADS
+
+FFX_FSR3UPSCALER_NUM_THREADS
+void main()
+{
+ ShadingChange(FfxInt32x2(gl_GlobalInvocationID.xy));
+}
diff --git a/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_shading_change_pyramid_pass.glsl b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_shading_change_pyramid_pass.glsl
new file mode 100644
index 0000000000..5e02d3da91
--- /dev/null
+++ b/externals/FidelityFX-SDK/shaders/fsr3upscaler/ffx_fsr3upscaler_shading_change_pyramid_pass.glsl
@@ -0,0 +1,67 @@
+// This file is part of the FidelityFX SDK.
+//
+// Copyright (C) 2024 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.
+
+#version 450
+
+#extension GL_GOOGLE_include_directive : require
+#extension GL_EXT_samplerless_texture_functions : require
+
+#define FSR3UPSCALER_BIND_SRV_CURRENT_LUMA 0
+#define FSR3UPSCALER_BIND_SRV_PREVIOUS_LUMA 1
+#define FSR3UPSCALER_BIND_SRV_DILATED_MOTION_VECTORS 2
+#define FSR3UPSCALER_BIND_SRV_INPUT_EXPOSURE 3
+
+
+#define FSR3UPSCALER_BIND_UAV_SPD_GLOBAL_ATOMIC 4
+#define FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_0 5
+#define FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_1 6
+#define FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_2 7
+#define FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_3 8
+#define FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_4 9
+#define FSR3UPSCALER_BIND_UAV_SPD_MIPS_LEVEL_5 10
+
+
+#define FSR3UPSCALER_BIND_CB_FSR3UPSCALER 11
+#define FSR3UPSCALER_BIND_CB_SPD 12
+
+#include "fsr3upscaler/ffx_fsr3upscaler_callbacks_glsl.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_common.h"
+#include "fsr3upscaler/ffx_fsr3upscaler_shading_change_pyramid.h"
+
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH 256
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#define FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT 1
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT
+#ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#define FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH 1
+#endif // #ifndef FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH
+#ifndef FFX_FSR3UPSCALER_NUM_THREADS
+#define FFX_FSR3UPSCALER_NUM_THREADS layout (local_size_x = FFX_FSR3UPSCALER_THREAD_GROUP_WIDTH, local_size_y = FFX_FSR3UPSCALER_THREAD_GROUP_HEIGHT, local_size_z = FFX_FSR3UPSCALER_THREAD_GROUP_DEPTH) in;
+#endif // #ifndef FFX_FSR3UPSCALER_NUM_THREADS
+
+FFX_FSR3UPSCALER_NUM_THREADS
+void main()
+{
+ ComputeShadingChangePyramid(gl_WorkGroupID, gl_LocalInvocationIndex);
+}
diff --git a/src/core/file_sys/nup_writer.cpp b/src/core/file_sys/nup_writer.cpp
index 4161969f98..54bb4c77d3 100644
--- a/src/core/file_sys/nup_writer.cpp
+++ b/src/core/file_sys/nup_writer.cpp
@@ -638,10 +638,13 @@ private:
std::ranges::find(skip, lower) != skip.end()) {
continue;
}
+ // Keep the dump's own layout for anything that is not exefs/romfs, the
+ // same way the reference packer does: a root-level data/ folder stays
+ // data/, and other extras keep their name instead of moving under data/.
if (entry.is_directory(ec)) {
- CollectTree(index, entry.path(), "data/" + name);
+ CollectTree(index, entry.path(), name);
} else {
- AddFile(index, entry, "data/" + name);
+ AddFile(index, entry, name);
}
}
return plan.packages.back().title_id;
diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h
index 259681a2a2..290d82746d 100644
--- a/src/core/hle/service/nx2_gsv.h
+++ b/src/core/hle/service/nx2_gsv.h
@@ -2347,7 +2347,7 @@ public:
{8, &INx2VideoSession::Flush, "Flush"},
{101, &INx2VideoSession::Configure, "Configure"},
{109, &INx2VideoSession::SubmitInput, "SubmitInputBuffer"},
- {115, &INx2VideoSession::Succeed, "Finalize"},
+ {115, &INx2VideoSession::Finalize, "Finalize"},
{200, &INx2VideoSession::Succeed, "Initialize"},
{201, &INx2VideoSession::Succeed, "InitializeWithFlag"},
{202, &INx2VideoSession::Succeed, "InitializeWithFlagAndValue"},
@@ -2419,6 +2419,10 @@ private:
if (Nx2FirstVideoCommand(GetServiceName(), ctx.GetCommand())) {
LOG_INFO(Service, "{} TIPC {} {} (first use): words {:08x}", GetServiceName(),
ctx.GetCommand(), what, fmt::join(std::span<const u32>{w, 12}, " "));
+ } else if (static const bool trace = std::getenv("EDEN_NX2_VDEC_TRACE") != nullptr; trace) {
+ // EDEN_NX2_VDEC_TRACE=1: every video decoder call at Info level.
+ LOG_INFO(Service, "{} TIPC {} {}: [2]={:#x} [3]={:#x} [4]={:#x} [5]={:#x}",
+ GetServiceName(), ctx.GetCommand(), what, w[2], w[3], w[4], w[5]);
} else {
LOG_DEBUG(Service, "{} TIPC {} {}: [2]={:#x} [3]={:#x} [4]={:#x} [5]={:#x}",
GetServiceName(), ctx.GetCommand(), what, w[2], w[3], w[4], w[5]);
@@ -2447,10 +2451,31 @@ private:
void Configure(HLERequestContext& ctx) {
const auto* w = ctx.CommandBuffer();
std::string codec;
+ std::span<const u8> name_buf;
if (!ctx.BufferDescriptorA().empty()) {
- const auto name = ctx.ReadBufferA(0);
- codec.assign(reinterpret_cast<const char*>(name.data()),
- strnlen(reinterpret_cast<const char*>(name.data()), name.size()));
+ name_buf = ctx.ReadBufferA(0);
+ codec.assign(reinterpret_cast<const char*>(name_buf.data()),
+ strnlen(reinterpret_cast<const char*>(name_buf.data()), name_buf.size()));
+ }
+ if (name_buf.empty() && !ctx.BufferDescriptorX().empty()) {
+ name_buf = ctx.ReadBufferX(0);
+ codec.assign(reinterpret_cast<const char*>(name_buf.data()),
+ strnlen(reinterpret_cast<const char*>(name_buf.data()), name_buf.size()));
+ }
+ if (!name_buf.empty()) {
+ if (codec.empty()) {
+ const std::string_view sv{reinterpret_cast<const char*>(name_buf.data()), name_buf.size()};
+ for (const auto& candidate : {"video/avc", "video/vp9", "video/vp8", "video/hevc"}) {
+ if (sv.find(candidate) != std::string_view::npos) {
+ codec = candidate;
+ break;
+ }
+ }
+ }
+ if (codec.empty()) {
+ LOG_INFO(Service, "{} configure: no codec name in {:02x}", GetServiceName(),
+ fmt::join(name_buf, " "));
+ }
}
Log(ctx, "configure");
LOG_INFO(Service, "{} configure codec \"{}\" {}x{} rate {}", GetServiceName(), codec, w[5],
@@ -2513,7 +2538,24 @@ private:
if (release_fence) {
m_release_fences[image] = *release_fence;
}
- value = m_sync_base + ++m_handed;
+ // After the end of a stream nothing fills an image again, so a release completes at
+ // once: its fence is the syncpoint's current value. Mario Kart World releases its
+ // images after a movie ends and waits for the returned fences; a fence for a frame
+ // that never comes hung the game on a black screen after its title movie.
+ static const bool release_now = [] {
+ const char* value = std::getenv("EDEN_NX2_VDEC_EOS_RELEASE_NOW");
+ return value != nullptr && value[0] != '0';
+ }();
+ if (release_now && release_fence && m_stream_ended && m_inputs.empty() &&
+ m_decoded.empty()) {
+ const u32 reached = static_cast<u32>(
+ system.Host1x().GetSyncpointManager().GetGuestSyncpointValue(m_syncpoint));
+ m_sync_base = reached;
+ m_handed = 0;
+ value = reached;
+ } else {
+ value = m_sync_base + ++m_handed;
+ }
}
m_cv.notify_one();
return {~m_syncpoint, 0, value, 0};
@@ -2559,7 +2601,8 @@ private:
{
std::scoped_lock lk{m_mutex};
m_inputs.push_back(std::move(input));
- m_ready->room.store(m_inputs.size() < MaxQueuedInputs, std::memory_order_relaxed);
+ m_stream_ended = false;
+ m_ready->room.store(!end && m_inputs.size() < MaxQueuedInputs, std::memory_order_relaxed);
}
m_cv.notify_one();
const u64 id = ++m_submitted;
@@ -2574,12 +2617,41 @@ private:
std::scoped_lock lk{m_mutex};
m_inputs.clear();
m_flush = true;
+ m_stream_ended = false;
m_ready->room.store(true, std::memory_order_relaxed);
+ // Only Stop: Bananza flushes (8) after its movies end and treats every completed
+ // fence as a frame to acquire.
+ if (ctx.GetCommand() == 207) {
+ CompleteHandedFences();
+ }
}
m_cv.notify_one();
TipcReplyWords(ctx, {});
}
+ void Finalize(HLERequestContext& ctx) {
+ Log(ctx, "finalize");
+ {
+ std::scoped_lock lk{m_mutex};
+ CompleteHandedFences();
+ }
+ TipcReplyWords(ctx, {});
+ }
+
+ // Advances the decoder's syncpoint to the last value handed out (m_mutex held).
+ void CompleteHandedFences() {
+ if (m_syncpoint == NoSyncpoint) {
+ return;
+ }
+ auto& syncpoints = system.Host1x().GetSyncpointManager();
+ const u32 target = m_sync_base + m_handed;
+ u32 current = static_cast<u32>(syncpoints.GetGuestSyncpointValue(m_syncpoint));
+ while (static_cast<s32>(target - current) > 0) {
+ syncpoints.IncrementGuest(m_syncpoint);
+ ++current;
+ }
+ }
+
void GetOutputInfo(HLERequestContext& ctx) {
Log(ctx, "output info");
std::optional<Output> output;
@@ -2647,6 +2719,10 @@ private:
lk.lock();
// Even an image written during a flush counts: the client waits for its value.
m_outputs.push_back({image, width, height, frame.pts, frame.end});
+ if (frame.end) {
+ m_stream_ended = true;
+ m_ready->room.store(false, std::memory_order_relaxed);
+ }
system.Host1x().GetSyncpointManager().IncrementGuest(m_syncpoint);
++m_frames_out;
if (m_frames_out == 1 || (m_frames_out % 600) == 0 || frame.end) {
@@ -2658,8 +2734,16 @@ private:
}
Input input = std::move(m_inputs.front());
m_inputs.pop_front();
- m_ready->room.store(m_inputs.size() < MaxQueuedInputs, std::memory_order_relaxed);
- const std::string codec = m_codec;
+ if (!m_stream_ended) {
+ m_ready->room.store(m_inputs.size() < MaxQueuedInputs, std::memory_order_relaxed);
+ }
+ std::string codec = m_codec;
+ if (codec.empty() && Tegra::Host1x::IsH264Stream(input.data)) {
+ codec = "video/avc";
+ m_codec = codec;
+ LOG_INFO(Service, "{} empty codec, detected H.264 stream: using \"{}\"",
+ GetServiceName(), codec);
+ }
lk.unlock();
if (!decoder || decoder->Name() != codec) {
decoder = std::make_unique<Tegra::Host1x::Nx2VideoDecoder>(codec);
@@ -2754,6 +2838,10 @@ private:
memory.WriteBlock(address + layout.chroma_offset, bytes.data() + layout.chroma_offset,
layout.pitch * ((height + 1) / 2));
}
+ // The game samples the image on the GPU: drop the texture cache's copy of the old frame.
+ if (const auto device = Nvidia::Devices::Nx2GpuMemoryRegistry::Get().DeviceAddressOf(address)) {
+ system.GPU().InvalidateRegion(*device, layout.size);
+ }
return {width, height};
}
@@ -2809,6 +2897,7 @@ private:
std::deque<u32> m_free; ///< Output images, in the order the client acquires them
std::map<u32, Fence> m_release_fences;
std::deque<Output> m_outputs; ///< Finished, for 213
+ bool m_stream_ended{}; ///< End-of-stream frame handed out, nothing submitted since
bool m_flush{};
bool m_quit{};
std::thread m_worker;
diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp
index 37f5a89f26..c5907e1b65 100644
--- a/src/qt_common/config/shared_translation.cpp
+++ b/src/qt_common/config/shared_translation.cpp
@@ -145,8 +145,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
"Higher resolutions require more VRAM and bandwidth.\n"
"Options lower than 1X can cause artifacts."));
INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QString());
- INSERT(Settings, fsr_sharpening_slider, tr("FSR Sharpness:"),
- tr("Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast."));
+ INSERT(Settings, fsr_sharpening_slider, tr("RCAS Sharpness:"),
+ tr("Strength of RCAS (AMD's contrast-adaptive sharpening) on the upscaler used in place "
+ "of a game's DLSS (AMD FSR 3.1); 0% turns it off.\nAlso sets the sharpening "
+ "of the AMD FSR 1 and SGSR window adapting filters."));
INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"),
tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA "
"can produce a more stable picture in lower resolutions."));
@@ -487,7 +489,7 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
translations->insert({Settings::EnumMetadata<Settings::FrameGenType>::Index(),
{
PAIR(FrameGenType, Lsfg, tr("Lossless Scaling (LSFG)")),
- PAIR(FrameGenType, Fsr3, tr("AMD FidelityFX Super Resolution 3 (FSR 3)")),
+ PAIR(FrameGenType, Fsr3, tr("Optical Flow Interpolation (built-in)")),
}});
translations->insert({Settings::EnumMetadata<Settings::ScalingFilter>::Index(),
{
@@ -497,7 +499,7 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
PAIR(ScalingFilter, Gaussian, tr("Gaussian")),
PAIR(ScalingFilter, Lanczos, tr("Lanczos")),
PAIR(ScalingFilter, ScaleForce, tr("ScaleForce")),
- PAIR(ScalingFilter, Fsr, tr("AMD FidelityFX Super Resolution")),
+ PAIR(ScalingFilter, Fsr, tr("AMD FidelityFX Super Resolution 1 (FSR 1)")),
PAIR(ScalingFilter, Area, tr("Area")),
PAIR(ScalingFilter, Mmpx, tr("MMPX")),
PAIR(ScalingFilter, ZeroTangent, tr("Zero-Tangent")),
@@ -506,7 +508,7 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
PAIR(ScalingFilter, Spline1, tr("Spline-1")),
PAIR(ScalingFilter, Sgsr, tr("Snapdragon Game Super Resolution")),
PAIR(ScalingFilter, SgsrEdge, tr("Snapdragon Game Super Resolution EdgeDir")),
- PAIR(ScalingFilter, Fsr3, tr("AMD FidelityFX Super Resolution 3")),
+ PAIR(ScalingFilter, Fsr3, tr("AMD FidelityFX Super Resolution 1, softer sharpening")),
}});
translations->insert({Settings::EnumMetadata<Settings::AntiAliasing>::Index(),
{
diff --git a/src/qt_common/config/shared_translation.h b/src/qt_common/config/shared_translation.h
index 15d427d4aa..7f29071acc 100644
--- a/src/qt_common/config/shared_translation.h
+++ b/src/qt_common/config/shared_translation.h
@@ -35,7 +35,7 @@ static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map =
static const std::map<Settings::FrameGenType, QString> frame_gen_type_texts_map = {
{Settings::FrameGenType::Lsfg, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "LSFG"))},
- {Settings::FrameGenType::Fsr3, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "FSR 3"))},
+ {Settings::FrameGenType::Fsr3, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Optical Flow"))},
};
static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map = {
@@ -55,12 +55,12 @@ static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map
{Settings::ScalingFilter::Lanczos, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Lanczos"))},
{Settings::ScalingFilter::ScaleForce,
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "ScaleForce"))},
- {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "FSR"))},
+ {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "FSR 1"))},
{Settings::ScalingFilter::Area, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Area"))},
{Settings::ScalingFilter::Mmpx, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "MMPX"))},
{Settings::ScalingFilter::Sgsr, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "SGSR"))},
{Settings::ScalingFilter::SgsrEdge, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "SGSR EdgeDir"))},
- {Settings::ScalingFilter::Fsr3, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "FSR 3"))},
+ {Settings::ScalingFilter::Fsr3, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "FSR 1 Soft"))},
};
static const std::map<Settings::ConsoleMode, QString> use_docked_mode_texts_map = {
diff --git a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h
index 024ed27ea3..81bd4b9e74 100644
--- a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h
+++ b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h
@@ -88,6 +88,7 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32
result.source_instruction_offsets.push_back(static_cast<u32>(result.instructions.size()));
u32 opcode = insn.Bits(0, 12);
if (insn.Bits(12, 4) == 15) continue; // @!PT: never executes (compiler padding)
+ u32 f16_dest = 255, f16_second = 255, f16_components = 0;
std::optional<u32> active_link;
u32 active_entry = 0;
if (main_exited) {
@@ -113,6 +114,19 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32
const u32 reg = insn.Bits(bit, 8);
if (reg >= (uses_uniform_bank ? high_uniform_base : 246U) && reg < 255) return fail(index, "native program uses lowering scratch registers");
}
+ // TLD/TLD4 .F16 (bit 79): the components come packed in pairs, (0, 1) in Rd and (2, 3) in
+ // the second destination. Lowered as the F32 form into R248/R249 and R246/R247, then packed.
+ if ((opcode == 0xb66 || opcode == 0xb63) && insn.Bits(79, 2) == 1) {
+ f16_dest = insn.Bits(16, 8);
+ f16_second = insn.Bits(64, 8);
+ f16_components = static_cast<u32>(std::popcount(insn.Bits(72, 4)));
+ if (f16_dest >= 246 || (f16_second != 255 && f16_second >= 246) || insn.Bits(61, 3) == 5 ||
+ (f16_second == 255 && f16_components > 2))
+ return fail(index, "native half-precision texture result registers");
+ insn.words[2] &= ~(1U << 15);
+ insn.words[0] = (insn.words[0] & ~0x00ff0000U) | (f16_dest == 255 ? 0x00ff0000U : (248U << 16));
+ if (f16_second != 255) insn.words[2] = (insn.words[2] & ~0xffU) | 246U;
+ }
// UR0-UR21 live in R216-R237 and UR22-UR27 in R240-R245 (R239 holds the uniform
// predicates), so even/odd pairs stay adjacent; UR28 and up below R216. Sites that
// still check reg < 16 rely on UR0-UR15 being R216-R231.
@@ -899,19 +913,22 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32
// (x, y[, layer]) and the four gradients sit in the same registers for both.
const u32 dimension = insn.Bits(61, 3);
const u32 high = insn.Bits(64, 8); // destination of the third and fourth channels
+ // Bit 90 is forwarded below (Maxwell's NODEP); Splatoon Raiders sets it. Bits 72-75 are
+ // the channel mask: the first two enabled channels go to Rd, Rd+1, the others to the
+ // second destination (RZ drops them; a third channel alone may be in an odd register).
const u32 mask = insn.Bits(72, 4), channels = static_cast<u32>(std::popcount(mask));
- // Bit 90 is forwarded below (Maxwell's NODEP); Splatoon Raiders sets it.
if ((insn.words[2] & ~(0xfffU | (1U << 26))) != 0x001e0000 || mask == 0 || insn.Bits(59, 2) ||
(dimension != 1 && dimension != 5) || binding >= 18 || offset > 0x1fff ||
- (channels > 1 && (dest & 1)) || dest >= 248 ||
- (channels > 2 ? high != 255 && ((high & 1) || high >= 248) : high != 255) ||
+ (channels > 1 && (dest & 1)) || dest >= 248 || (channels <= 2 && high != 255) ||
+ (high != 255 && ((channels > 3 && (high & 1)) || high >= 248)) ||
coordinates >= 248 || derivatives >= 247)
return fail(index, "native gradient texture mode");
if (result.required_texture_buffer != ~0U && result.required_texture_buffer != binding)
return fail(index, "native texture descriptor buffer conflict");
result.required_texture_buffer = binding;
- // Maxwell writes consecutive registers: when the second pair is elsewhere (Splatoon
- // Raiders: R12/R18, R18/R44), write them to scratch and move each pair into place.
+ // Maxwell writes the channels to consecutive registers: when the second destination
+ // doesn't follow Rd (Splatoon Raiders: R12/R18, R18/R44), write them to scratch and move
+ // each into place.
const bool consecutive = channels <= 2 || high == dest + 2;
const u64 target = consecutive ? dest : 248;
emit(0xde00000000000000ULL | predicate | target | (u64{coordinates} << 8) |
@@ -920,8 +937,8 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32
if (!consecutive) {
for (u32 i = 0; i < channels; ++i) {
if (i >= 2 && high == 255) break;
- emit(0x5c98000000000000ULL | predicate | (i < 2 ? dest + i : high + i - 2) |
- (u64{15} << 39) | (u64{248 + i} << 20));
+ const u32 to = i < 2 ? static_cast<u32>(dest) + i : high + (i - 2);
+ emit(0x5c98000000000000ULL | predicate | to | (u64{15} << 39) | (u64{248 + i} << 20));
}
}
break;
@@ -1573,12 +1590,13 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32
}
case 0x305: case 0xb05: case 0xd05: { // F2I NTZ, register/constant-buffer/uniform source
const u32 mode = insn.words[2] & ~(opcode == 0xd05 ? 0x08000000U : 0U);
- const bool half = opcode == 0x305 && mode == 0x0010f000;
+ // F16 source: bit 72 signs the result as in the other forms, bits 60-61 pick H0/H1.
+ const bool half = opcode == 0x305 && (mode & ~0x100U) == 0x0010f000;
const bool small = mode == 0x00212800;
const bool floor = (mode & ~0x10100U) == 0x00207000;
const bool nearest = (mode & ~0x10100U) == 0x00203000;
if ((!half && !small && !floor && !nearest && (mode & ~0x10100U) != 0x0020f000) ||
- (opcode != 0xb05 && (insn.words[1] & 0xffffff00U)) ||
+ (opcode != 0xb05 && (insn.words[1] & (half ? 0xefffff00U : 0xffffff00U))) ||
(opcode == 0xd05 && uniform_register(insn.Bits(32, 8)) == ~0U)) {
return fail(index, "native float-to-integer conversion mode");
}
@@ -1586,7 +1604,7 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32
u32 source = opcode == 0xb05 ? materialize(2, 0, 252, false, false)
: opcode == 0xd05 ? uniform_register(insn.Bits(32, 8)) : insn.Bits(32, 8);
if (half) {
- extract_half({source, 2, false, false}, 0, 252);
+ extract_half({source, insn.Bits(60, 1) ? 3U : 2U, false, false}, 0, 252);
source = 252;
}
if (!is_signed) {
@@ -2870,6 +2888,21 @@ inline LoweredProgram LowerToMaxwellWith(std::span<const Instruction> code, u32
return fail(index, "unsupported native opcode");
}
if (!operand_error.empty()) return fail(index, operand_error);
+ if (f16_components != 0) {
+ // Each pair as F2FP.PACK_AB: the second value in the high half.
+ const u64 pred = u64{insn.Bits(12, 4)} << 16;
+ const auto pack = [&](u32 target, u32 low, u32 high) {
+ result.instructions.push_back(0x5ca8000000000000ULL | pred | 250 | (u64{1} << 8) |
+ (u64{2} << 10) | (u64{high} << 20));
+ result.instructions.push_back(0x5ca8000000000000ULL | pred | 251 | (u64{1} << 8) |
+ (u64{2} << 10) | (u64{low} << 20));
+ result.instructions.push_back(0x5c18000000000000ULL | pred | target | (u64{250} << 8) |
+ (u64{251} << 20) | (u64{16} << 39));
+ };
+ if (f16_dest != 255) pack(f16_dest, 248, f16_components > 1 ? 249U : 255U);
+ if (f16_second != 255 && f16_components > 2)
+ pack(f16_second, 246, f16_components > 3 ? 247U : 255U);
+ }
if (output_mask) {
emit(0x38e8000000070000ULL | 239 | (u64{239} << 8) | (u64{output_mask} << 20));
emit(0x38f0000000070000ULL | (u64{254} << 8) | (u64{output_mask} << 20));
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index b3ef88c5f1..d0e84c324c 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -169,6 +169,8 @@ add_library(video_core STATIC
renderer_vulkan/vk_command_pool.h
renderer_vulkan/vk_compute_pass.cpp
renderer_vulkan/vk_compute_pass.h
+ renderer_vulkan/vk_fsr3_upscaler.cpp
+ renderer_vulkan/vk_fsr3_upscaler.h
renderer_vulkan/vk_compute_pipeline.cpp
renderer_vulkan/vk_compute_pipeline.h
renderer_vulkan/vk_descriptor_buffer.cpp
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index 925b1b2ca1..be6a1d5eac 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -591,8 +591,9 @@ void KeplerCompute::DescribeNativeResources(const NativeQmdScheduler::Descriptor
std::atomic<u32> g_nx2_trace_draws_after_upscale{};
// Fire Emblem: Fortune's Weave's first DLSS pass (rejected, before the network) has the input colour
-// at cbuf0+0x278, its motion vectors (R16G16 float, the input's size) at +0x290 and this frame's
-// camera jitter (x, y; input pixels, Halton bases 2 and 3 less 0.5) at +0x1b0.
+// at cbuf0+0x278, its motion vectors (R16G16 float, the input's size) at +0x290, its depth (Z32, the
+// input's size) at +0x298 and this frame's camera jitter (x, y; input pixels, Halton bases 2 and 3
+// less 0.5) at +0x1b0.
void KeplerCompute::RememberDlssMotionVectors() {
const auto& cbuf = launch_description.const_buffer_config[0];
if (cbuf.size.Value() < 0x294) return;
@@ -610,6 +611,14 @@ void KeplerCompute::RememberDlssMotionVectors() {
return;
dlss_motion_vectors = *motion;
dlss_motion_color = color->Address();
+ const auto depth = cbuf.size.Value() >= 0x29c ? image(0x298) : std::nullopt;
+ if (depth && depth->format == Texture::TextureFormat::Z32 && depth->Address() != 0 &&
+ depth->Width() == color->Width() && depth->Height() == color->Height() &&
+ depth->IsBlockLinear()) {
+ dlss_depth = *depth;
+ } else {
+ dlss_depth.reset();
+ }
dlss_jitter = {std::bit_cast<f32>(memory_manager.Read<u32>(cbuf_address + 0x1b0)),
std::bit_cast<f32>(memory_manager.Read<u32>(cbuf_address + 0x1b4))};
}
@@ -794,8 +803,9 @@ bool KeplerCompute::UpscaleInsteadOfNativeProgram() {
return false;
Fermi2D::Surface motion = surface(*dlss_motion_vectors);
motion.format = RTFormat::R16G16_FLOAT;
- return rasterizer->AccelerateTemporalUpscale(source, surface(*output), motion, dlss_jitter,
- {1.0f, 1.0f});
+ return rasterizer->AccelerateTemporalUpscale(source, surface(*output), motion,
+ dlss_depth ? &*dlss_depth : nullptr,
+ dlss_jitter, {1.0f, 1.0f});
};
const bool done = upscale_temporally() ||
rasterizer->AccelerateSurfaceCopy(source, surface(*output), config);
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h
index b5aee3706a..6a06390294 100644
--- a/src/video_core/engines/kepler_compute.h
+++ b/src/video_core/engines/kepler_compute.h
@@ -269,10 +269,11 @@ private:
void RememberDlssMotionVectors();
bool UpscaleInsteadOfNativeProgram();
- /// Motion vectors of Fire Emblem: Fortune's Weave's DLSS evaluation, from its first pass (run
- /// earlier in the frame than the network pass the stand-in replaces), with the input colour
- /// image and camera jitter they go with.
+ /// Motion vectors and depth of Fire Emblem: Fortune's Weave's DLSS evaluation, from its first
+ /// pass (run earlier in the frame than the network pass the stand-in replaces), with the input
+ /// colour image and camera jitter they go with.
std::optional<Texture::TICEntry> dlss_motion_vectors;
+ std::optional<Texture::TICEntry> dlss_depth;
GPUVAddr dlss_motion_color{};
std::array<f32, 2> dlss_jitter{};
bool ProcessNativeMethod(Core::System& system, u32 method, u32 argument, bool is_last_call);
diff --git a/src/video_core/host1x/codec_types.h b/src/video_core/host1x/codec_types.h
index b2f80c822a..da9b713b26 100644
--- a/src/video_core/host1x/codec_types.h
+++ b/src/video_core/host1x/codec_types.h
@@ -312,6 +312,10 @@ struct Vp9PictureInfo {
bool allow_high_precision_mv;
bool segment_enabled;
bool mode_ref_delta_enabled;
+
+ Segmentation segmentation;
+ std::array<u8, 7> seg_tree_probs;
+ std::array<u8, 3> seg_pred_probs;
};
struct Vp9FrameContainer {
@@ -377,6 +381,10 @@ struct PictureInfo {
.allow_high_precision_mv = allow_high_precision_mv != 0,
.segment_enabled = segmentation.enabled != 0,
.mode_ref_delta_enabled = loop_filter.mode_ref_delta_enabled != 0,
+
+ .segmentation = segmentation,
+ .seg_tree_probs = {},
+ .seg_pred_probs = {},
};
}
};
diff --git a/src/video_core/host1x/codecs/vp9.cpp b/src/video_core/host1x/codecs/vp9.cpp
index dcc6261e5c..dd91f7f1b7 100644
--- a/src/video_core/host1x/codecs/vp9.cpp
+++ b/src/video_core/host1x/codecs/vp9.cpp
@@ -331,19 +331,16 @@ void VP9::WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_
}
void VP9::WriteSegmentation(VpxBitStreamWriter& writer) {
- bool enabled = current_picture_info.segmentation.enabled != 0;
+ bool enabled = current_frame_info.segmentation.enabled != 0;
writer.WriteBit(enabled);
if (!enabled) {
return;
}
- auto update_map = current_picture_info.segmentation.update_map != 0;
+ auto update_map = current_frame_info.segmentation.update_map != 0;
writer.WriteBit(update_map);
if (update_map) {
- EntropyProbs entropy_probs{};
- host1x.gmmu_manager.ReadBlock(regs.vp9_prob_tab_buffer_offset.Address(), &entropy_probs, sizeof(entropy_probs));
-
auto WriteProb = [&](u8 prob) {
bool coded = prob != 255;
writer.WriteBit(coded);
@@ -352,55 +349,55 @@ void VP9::WriteSegmentation(VpxBitStreamWriter& writer) {
}
};
- for (size_t i = 0; i < entropy_probs.mb_segment_tree_probs.size(); i++) {
- WriteProb(entropy_probs.mb_segment_tree_probs[i]);
+ for (size_t i = 0; i < current_frame_info.seg_tree_probs.size(); i++) {
+ WriteProb(current_frame_info.seg_tree_probs[i]);
}
- auto temporal_update = current_picture_info.segmentation.temporal_update != 0;
+ auto temporal_update = current_frame_info.segmentation.temporal_update != 0;
writer.WriteBit(temporal_update);
if (temporal_update) {
for (s32 i = 0; i < 3; i++) {
- WriteProb(entropy_probs.segment_pred_probs[i]);
+ WriteProb(current_frame_info.seg_pred_probs[i]);
}
}
}
- if (last_segmentation == current_picture_info.segmentation) {
+ if (last_segmentation == current_frame_info.segmentation) {
writer.WriteBit(false);
return;
}
- last_segmentation = current_picture_info.segmentation;
+ last_segmentation = current_frame_info.segmentation;
writer.WriteBit(true);
- writer.WriteBit(current_picture_info.segmentation.abs_delta != 0);
+ writer.WriteBit(current_frame_info.segmentation.abs_delta != 0);
constexpr s32 MAX_SEGMENTS = 8;
constexpr std::array SegmentationFeatureBits = {8, 6, 2, 0};
for (s32 i = 0; i < MAX_SEGMENTS; i++) {
- auto q_enabled = current_picture_info.segmentation.feature_enabled[i][0] != 0;
+ auto q_enabled = current_frame_info.segmentation.feature_enabled[i][0] != 0;
writer.WriteBit(q_enabled);
if (q_enabled) {
- writer.WriteS(current_picture_info.segmentation.feature_data[i][0],
+ writer.WriteS(current_frame_info.segmentation.feature_data[i][0],
SegmentationFeatureBits[0]);
}
- auto lf_enabled = current_picture_info.segmentation.feature_enabled[i][1] != 0;
+ auto lf_enabled = current_frame_info.segmentation.feature_enabled[i][1] != 0;
writer.WriteBit(lf_enabled);
if (lf_enabled) {
- writer.WriteS(current_picture_info.segmentation.feature_data[i][1],
+ writer.WriteS(current_frame_info.segmentation.feature_data[i][1],
SegmentationFeatureBits[1]);
}
- auto ref_enabled = current_picture_info.segmentation.feature_enabled[i][2] != 0;
+ auto ref_enabled = current_frame_info.segmentation.feature_enabled[i][2] != 0;
writer.WriteBit(ref_enabled);
if (ref_enabled) {
- writer.WriteU(current_picture_info.segmentation.feature_data[i][2],
+ writer.WriteU(current_frame_info.segmentation.feature_data[i][2],
SegmentationFeatureBits[2]);
}
- auto skip_enabled = current_picture_info.segmentation.feature_enabled[i][3] != 0;
+ auto skip_enabled = current_frame_info.segmentation.feature_enabled[i][3] != 0;
writer.WriteBit(skip_enabled);
}
}
@@ -411,6 +408,14 @@ Vp9PictureInfo VP9::GetVp9PictureInfo() {
InsertEntropy(regs.vp9_prob_tab_buffer_offset.Address(), vp9_info.entropy);
+ EntropyProbs entropy_probs{};
+ host1x.gmmu_manager.ReadBlock(regs.vp9_prob_tab_buffer_offset.Address(),
+ &entropy_probs, sizeof(entropy_probs));
+
+ vp9_info.seg_tree_probs = entropy_probs.mb_segment_tree_probs;
+ vp9_info.seg_pred_probs = entropy_probs.segment_pred_probs;
+
+
// surface_luma_offset[0:3] contains the address of the reference frame offsets in the following
// order: last, golden, altref, current.
for (size_t i = 0; i < 4; i++) {
diff --git a/src/video_core/host1x/nx2_video_decoder.cpp b/src/video_core/host1x/nx2_video_decoder.cpp
index 9b34bd84af..86869ad5e2 100644
--- a/src/video_core/host1x/nx2_video_decoder.cpp
+++ b/src/video_core/host1x/nx2_video_decoder.cpp
@@ -47,6 +47,45 @@ std::optional<NvdecCommon::VideoCodec> CodecFromMime(std::string_view mime) {
} // namespace
+bool IsH264Stream(std::span<const u8> data) {
+ if (data.size() >= 4) {
+ // Annex-B 3-byte start code: 00 00 01 <nal_header>
+ if (data[0] == 0 && data[1] == 0 && data[2] == 1) {
+ const u8 nal_type = data[3] & 0x1F;
+ if ((data[3] & 0x80) == 0 && nal_type >= 1 && nal_type <= 23) {
+ return true;
+ }
+ }
+ }
+ if (data.size() >= 5) {
+ // Annex-B 4-byte start code: 00 00 00 01 <nal_header>
+ if (data[0] == 0 && data[1] == 0 && data[2] == 0 && data[3] == 1) {
+ const u8 nal_type = data[4] & 0x1F;
+ if ((data[4] & 0x80) == 0 && nal_type >= 1 && nal_type <= 23) {
+ return true;
+ }
+ }
+ // avcC / MP4 length-prefixed NAL unit: [u32 be length] <nal_header>
+ const u32 nalu_len = (static_cast<u32>(data[0]) << 24) |
+ (static_cast<u32>(data[1]) << 16) |
+ (static_cast<u32>(data[2]) << 8) |
+ static_cast<u32>(data[3]);
+ if (nalu_len > 0 && nalu_len <= data.size() - 4) {
+ const u8 nal_type = data[4] & 0x1F;
+ if ((data[4] & 0x80) == 0 && nal_type >= 1 && nal_type <= 23) {
+ return true;
+ }
+ }
+ }
+ if (data.size() >= 7) {
+ // avcC configuration record: version=1, profile, compat, level, lengthSizeMinusOne | 0xFC, numSPS | 0xE0
+ if (data[0] == 1 && (data[4] & 0xFC) == 0xFC && (data[5] & 0xE0) == 0xE0 && (data[5] & 0x1F) > 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
struct Nx2VideoDecoder::Impl {
std::string name;
std::optional<FFmpeg::Decoder> decoder;
@@ -123,6 +162,31 @@ void Nx2VideoDecoder::Decode(std::span<const u8> data, u64 pts, std::vector<Nx2P
}
impl->packet.assign(data.size() + AV_INPUT_BUFFER_PADDING_SIZE, 0);
std::memcpy(impl->packet.data(), data.data(), data.size());
+
+ // Convert MP4/avcC length-prefixed NALUs to Annex-B start codes for FFmpeg if needed.
+ if (impl->name.find("avc") != std::string::npos || impl->name.find("264") != std::string::npos) {
+ const bool is_annex_b = data.size() >= 4 &&
+ ((data[0] == 0 && data[1] == 0 && data[2] == 1) ||
+ (data.size() >= 5 && data[0] == 0 && data[1] == 0 && data[2] == 0 && data[3] == 1));
+ if (!is_annex_b && data.size() >= 5) {
+ size_t offset = 0;
+ while (offset + 4 < data.size()) {
+ const u32 nalu_len = (static_cast<u32>(impl->packet[offset]) << 24) |
+ (static_cast<u32>(impl->packet[offset + 1]) << 16) |
+ (static_cast<u32>(impl->packet[offset + 2]) << 8) |
+ static_cast<u32>(impl->packet[offset + 3]);
+ if (nalu_len == 0 || offset + 4 + nalu_len > data.size()) {
+ break;
+ }
+ impl->packet[offset] = 0;
+ impl->packet[offset + 1] = 0;
+ impl->packet[offset + 2] = 0;
+ impl->packet[offset + 3] = 1;
+ offset += 4 + nalu_len;
+ }
+ }
+ }
+
FFmpeg::Packet packet{std::span<const u8>{impl->packet.data(), data.size()}};
packet.GetPacket()->pts = static_cast<s64>(pts);
// Hidden VP9 frames never come out: keep the set from growing over a long movie.
@@ -162,6 +226,20 @@ void Nx2VideoDecoder::Render(const Nx2Picture& picture, Nx2PictureFormat format,
const AVPixelFormat target = format == Nx2PictureFormat::Nv12 ? AV_PIX_FMT_NV12
: format == Nx2PictureFormat::Rgba8 ? AV_PIX_FMT_RGBA
: AV_PIX_FMT_BGRA;
+ // Frames copied back from a host GPU decoder are already NV12. Copy them as they are:
+ // FFmpeg 6.1's swscale leaves the chroma plane of an NV12 to NV12 copy at zero, which shows
+ // the movies green.
+ if (target == AV_PIX_FMT_NV12 && f->format == AV_PIX_FMT_NV12 &&
+ static_cast<u32>(f->width) == width && static_cast<u32>(f->height) == height) {
+ for (u32 y = 0; y < height; ++y) {
+ std::memcpy(out.data() + y * pitch, f->data[0] + y * f->linesize[0], width);
+ }
+ for (u32 y = 0; y < (height + 1) / 2; ++y) {
+ std::memcpy(out.data() + chroma_offset + y * pitch, f->data[1] + y * f->linesize[1],
+ width);
+ }
+ return;
+ }
impl->scaler = sws_getCachedContext(impl->scaler, f->width, f->height,
static_cast<AVPixelFormat>(f->format),
static_cast<int>(width), static_cast<int>(height), target,
diff --git a/src/video_core/host1x/nx2_video_decoder.h b/src/video_core/host1x/nx2_video_decoder.h
index c979a2c801..347b0e6525 100644
--- a/src/video_core/host1x/nx2_video_decoder.h
+++ b/src/video_core/host1x/nx2_video_decoder.h
@@ -32,6 +32,9 @@ enum class Nx2PictureFormat : u32 {
Bgra8,
};
+/// Checks if stream data begins with an H.264 Annex-B start code or avcC/MP4 length-prefixed NALUs.
+bool IsH264Stream(std::span<const u8> data);
+
/// The decoder behind the Switch 2 video decoder service (nn::codec::video): FFmpeg decodes the
/// elementary stream the game demuxed, in software or on the host GPU (NVDEC emulation setting).
/// Used from one thread at a time.
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index 3dc26ec3ba..7fbf9a80be 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -73,6 +73,7 @@ set(SHADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/smaa_blending_weight_calculation.frag
${CMAKE_CURRENT_SOURCE_DIR}/smaa_neighborhood_blending.vert
${CMAKE_CURRENT_SOURCE_DIR}/smaa_neighborhood_blending.frag
+ ${CMAKE_CURRENT_SOURCE_DIR}/temporal_sharpen.frag
${CMAKE_CURRENT_SOURCE_DIR}/temporal_upscale.frag
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_blit_depth_stencil.frag
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_color_clear.frag
@@ -212,6 +213,71 @@ foreach(VARIANT IN ITEMS ${SHADER_TYPE_VARIANTS})
set(SHADER_HEADERS ${SHADER_HEADERS} ${VARIANT_HEADER_FILE})
endforeach()
+# AMD FidelityFX Super Resolution 3.1 upscaler (externals/FidelityFX-SDK, unmodified): the SDK's
+# Vulkan pass files, compiled with one set of options (render-size motion vectors, reversed depth,
+# HDR colour, reference Lanczos), plus the sharpening and the no-wave-operation SPD variants.
+set(FSR3_GPU_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-SDK/include/FidelityFX/gpu)
+set(FSR3_PASS_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-SDK/shaders/fsr3upscaler)
+file(GLOB FSR3_GPU_FILES ${FSR3_GPU_DIR}/*.h ${FSR3_GPU_DIR}/fsr1/*.h ${FSR3_GPU_DIR}/spd/*.h
+ ${FSR3_GPU_DIR}/fsr3upscaler/*.h)
+set(FSR3_FLAGS
+ -DFFX_GPU=1
+ -DFFX_GLSL=1
+ -DFFX_FSR3UPSCALER_OPTION_UPSAMPLE_SAMPLERS_USE_DATA_HALF=0
+ -DFFX_FSR3UPSCALER_OPTION_ACCUMULATE_SAMPLERS_USE_DATA_HALF=0
+ -DFFX_FSR3UPSCALER_OPTION_REPROJECT_SAMPLERS_USE_DATA_HALF=1
+ -DFFX_FSR3UPSCALER_OPTION_POSTPROCESSLOCKSTATUS_SAMPLERS_USE_DATA_HALF=0
+ -DFFX_FSR3UPSCALER_OPTION_UPSAMPLE_USE_LANCZOS_TYPE=2
+ -DFFX_FSR3UPSCALER_OPTION_REPROJECT_USE_LANCZOS_TYPE=0
+ -DFFX_FSR3UPSCALER_OPTION_HDR_COLOR_INPUT=1
+ -DFFX_FSR3UPSCALER_OPTION_LOW_RESOLUTION_MOTION_VECTORS=1
+ -DFFX_FSR3UPSCALER_OPTION_JITTERED_MOTION_VECTORS=0
+ -DFFX_FSR3UPSCALER_OPTION_INVERTED_DEPTH=1
+)
+set(FSR3_VARIANTS
+ "prepare_inputs|prepare_inputs|none"
+ "luma_pyramid|luma_pyramid|none"
+ "luma_pyramid_nowave|luma_pyramid|-DFFX_SPD_NO_WAVE_OPERATIONS=1"
+ "shading_change_pyramid|shading_change_pyramid|none"
+ "shading_change_pyramid_nowave|shading_change_pyramid|-DFFX_SPD_NO_WAVE_OPERATIONS=1"
+ "shading_change|shading_change|none"
+ "prepare_reactivity|prepare_reactivity|none"
+ "luma_instability|luma_instability|none"
+ "accumulate|accumulate|none"
+ "accumulate_sharpen|accumulate|-DFFX_FSR3UPSCALER_OPTION_APPLY_SHARPENING=1"
+ "rcas|rcas|none"
+)
+
+foreach(VARIANT IN ITEMS ${FSR3_VARIANTS})
+ string(REPLACE "|" ";" VARIANT_PARTS "${VARIANT}")
+ list(GET VARIANT_PARTS 0 FSR3_NAME)
+ list(GET VARIANT_PARTS 1 FSR3_PASS)
+ list(GET VARIANT_PARTS 2 FSR3_DEFINE)
+ if (FSR3_DEFINE STREQUAL "none")
+ set(FSR3_DEFINE "")
+ endif()
+ if (NOT FSR3_DEFINE MATCHES "APPLY_SHARPENING")
+ list(APPEND FSR3_DEFINE -DFFX_FSR3UPSCALER_OPTION_APPLY_SHARPENING=0)
+ endif()
+ set(FSR3_SOURCE ${FSR3_PASS_DIR}/ffx_fsr3upscaler_${FSR3_PASS}_pass.glsl)
+ string(TOUPPER fsr3_upscaler_${FSR3_NAME}_comp_spv FSR3_VARIABLE_NAME)
+ set(FSR3_HEADER_FILE ${SHADER_DIR}/fsr3_upscaler_${FSR3_NAME}_comp_spv.h)
+ add_custom_command(
+ OUTPUT
+ ${FSR3_HEADER_FILE}
+ COMMAND
+ ${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -S comp -I"${FSR3_GPU_DIR}"
+ -I"${FSR3_GPU_DIR}/fsr3upscaler" ${FSR3_FLAGS} ${FSR3_DEFINE}
+ --variable-name ${FSR3_VARIABLE_NAME} -o ${FSR3_HEADER_FILE} ${FSR3_SOURCE}
+ --target-env ${SPIR_V_VERSION}
+ MAIN_DEPENDENCY
+ ${FSR3_SOURCE}
+ DEPENDS
+ ${FSR3_GPU_FILES}
+ )
+ set(SHADER_HEADERS ${SHADER_HEADERS} ${FSR3_HEADER_FILE})
+endforeach()
+
foreach(FILEPATH IN ITEMS ${FIDELITYFX_FILES})
get_filename_component(FILENAME ${FILEPATH} NAME)
string(REPLACE "." "_" HEADER_NAME ${FILENAME})
diff --git a/src/video_core/host_shaders/temporal_sharpen.frag b/src/video_core/host_shaders/temporal_sharpen.frag
new file mode 100644
index 0000000000..45a13013cb
--- /dev/null
+++ b/src/video_core/host_shaders/temporal_sharpen.frag
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// RCAS (FidelityFX FSR 1's contrast-adaptive sharpening) on the DLSS stand-in's result, before the
+// game post-processes it. The input is linear HDR; RCAS's limits assume 0 to 1, so it sharpens in
+// the stand-in's range-compressed space.
+
+#version 460 core
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_ARB_shading_language_420pack : enable
+#extension GL_GOOGLE_include_directive : enable
+#extension GL_EXT_shader_explicit_arithmetic_types : require
+
+layout(binding = 0) uniform sampler2D InputTexture;
+
+layout(push_constant) uniform PushConstants {
+ // The vertex stage's texture scale and offset come first.
+ layout(offset = 16) uvec4 Const0; // FsrRcasCon
+};
+
+layout(location = 0) in vec2 texcoord;
+layout(location = 0) out vec4 color;
+
+#define A_GPU 1
+#define A_GLSL 1
+#include "ffx_a.h"
+
+vec3 Compress(vec3 c) {
+ return c / (1.0 + max(max(c.r, c.g), c.b));
+}
+
+vec3 Expand(vec3 c) {
+ c = clamp(c, vec3(0.0), vec3(1.0 - 1.0 / 65504.0));
+ return c / (1.0 - max(max(c.r, c.g), c.b));
+}
+
+#define FSR_RCAS_F 1
+AF4 FsrRcasLoadF(ASU2 p) {
+ return AF4(Compress(texelFetch(InputTexture, p, 0).rgb), 1.0);
+}
+void FsrRcasInputF(inout AF1 r, inout AF1 g, inout AF1 b) {}
+
+#include "ffx_fsr1.h"
+
+void main() {
+ AF3 c;
+ FsrRcasF(c.r, c.g, c.b, AU2(gl_FragCoord.xy), Const0);
+ color = vec4(Expand(c), 1.0);
+}
diff --git a/src/video_core/host_shaders/temporal_upscale.frag b/src/video_core/host_shaders/temporal_upscale.frag
index 0e76df2b43..ee31e035b1 100644
--- a/src/video_core/host_shaders/temporal_upscale.frag
+++ b/src/video_core/host_shaders/temporal_upscale.frag
@@ -11,12 +11,17 @@
layout(binding = 0) uniform sampler2D input_color;
layout(binding = 1) uniform sampler2D motion_vectors;
layout(binding = 2) uniform sampler2D history;
+layout(binding = 3, rgba16f) uniform writeonly image2D next_history;
layout(push_constant) uniform PushConstants {
// The vertex stage's texture scale and offset come first.
layout(offset = 16) vec2 jitter; // this frame's sample offset, in input pixels
vec2 motion_scale; // motion vector -> UV offset to the previous frame
float current_weight; // largest weight of this frame; 1 ignores the history
+ float kernel_scale; // accumulation kernel distance scale
+ float clip_width; // neighbourhood clip of still pixels, in deviations
+ float clip_motion; // clip of pixels moving a pixel or more per frame
+ float kernel_motion; // kernel distance scale of moving pixels
};
layout(location = 0) in vec2 texcoord; // 0 to 1 across the output
@@ -83,50 +88,92 @@ void main() {
const vec2 target = texcoord * vec2(input_size);
const ivec2 nearest = ivec2(floor(target + jitter));
+ // The longest motion around the pixel, so that an edge follows the object in front.
+ vec2 motion = vec2(0.0);
+ float longest = -1.0;
+ for (int y = -1; y <= 1; ++y) {
+ for (int x = -1; x <= 1; ++x) {
+ // The motion vectors can be at another scale than the colour (resolution scaling).
+ const ivec2 input_texel = clamp(nearest + ivec2(x, y), ivec2(0), input_size - 1);
+ const ivec2 texel = clamp(ivec2((vec2(input_texel) + 0.5) * vec2(motion_size) /
+ vec2(input_size)),
+ ivec2(0), motion_size - 1);
+ const vec2 candidate = texelFetch(motion_vectors, texel, 0).rg;
+ const float length_squared = dot(candidate, candidate);
+ if (length_squared > longest) {
+ longest = length_squared;
+ motion = candidate;
+ }
+ }
+ }
+ const vec2 previous = texcoord + motion * motion_scale;
+ // Still pixels: a narrow kernel (detail from many jitter positions accumulates)
+ // and a looser clip (keeps detail only some positions sample, e.g. thin bars). Moving pixels
+ // (a quarter pixel or more per frame): the wide kernel and tight clip, as their history is unreliable.
+ // The history's alpha remembers recent motion, decaying over a few frames: background that a
+ // moving object has just uncovered has no motion of its own but a history of that object.
+ const vec2 history_position = previous * vec2(textureSize(history, 0));
+ const float moved = smoothstep(0.02, 0.25, length(motion * motion_scale * vec2(textureSize(history, 0))));
+ const float moving = max(moved, texture(history, history_position).a);
+ const float kernel = mix(kernel_scale, kernel_motion, moving);
+ const float clip = mix(clip_width, clip_motion, moving);
+
vec3 filtered = vec3(0.0);
float total_weight = 0.0;
float nearest_weight = 0.0;
vec3 sum = vec3(0.0);
vec3 squares = vec3(0.0);
- vec2 motion = vec2(0.0);
- float longest = -1.0;
for (int y = -1; y <= 1; ++y) {
for (int x = -1; x <= 1; ++x) {
const ivec2 texel = clamp(nearest + ivec2(x, y), ivec2(0), input_size - 1);
const vec3 sample_color = RgbToYCoCg(Compress(texelFetch(input_color, texel, 0).rgb));
// A Blackman-Harris-like window over the sample's distance to the output pixel.
- const vec2 distance = vec2(texel) + 0.5 - jitter - target;
+ const vec2 distance = (vec2(texel) + 0.5 - jitter - target) * kernel;
const float weight = exp(-2.29 * dot(distance, distance));
filtered += sample_color * weight;
total_weight += weight;
nearest_weight = max(nearest_weight, weight);
sum += sample_color;
squares += sample_color * sample_color;
- // The longest motion around the pixel, so that an edge follows the object in front.
- const vec2 candidate =
- texelFetch(motion_vectors, clamp(texel, ivec2(0), motion_size - 1), 0).rg;
- const float length_squared = dot(candidate, candidate);
- if (length_squared > longest) {
- longest = length_squared;
- motion = candidate;
- }
}
}
- const vec3 current = filtered / total_weight;
+ const vec3 current = filtered / max(total_weight, 1e-6);
const vec3 mean = sum / 9.0;
const vec3 deviation = sqrt(max(squares / 9.0 - mean * mean, vec3(0.0)));
- const vec3 low = mean - deviation;
- const vec3 high = mean + deviation;
+ const vec3 low = mean - clip * deviation;
+ const vec3 high = mean + clip * deviation;
- const vec2 previous = texcoord + motion * motion_scale;
// A frame whose samples land close to this pixel counts more; one whose samples land between
// pixels mostly keeps the history (from other jitter positions) there.
float weight = current_weight * nearest_weight;
+ vec3 result = current;
+ float remembered = max(moved, 0.85 * moving);
if (current_weight >= 1.0 || any(lessThan(previous, vec2(0.0))) ||
any(greaterThan(previous, vec2(1.0)))) {
- weight = 1.0;
+ // No usable history: the wide kernel, as a narrow one would show the input's pixels.
+ vec3 wide = vec3(0.0);
+ float wide_total = 0.0;
+ for (int y = -1; y <= 1; ++y) {
+ for (int x = -1; x <= 1; ++x) {
+ const ivec2 texel = clamp(nearest + ivec2(x, y), ivec2(0), input_size - 1);
+ const vec2 distance = vec2(texel) + 0.5 - jitter - target;
+ const float wide_weight = exp(-2.29 * dot(distance, distance));
+ wide += RgbToYCoCg(Compress(texelFetch(input_color, texel, 0).rgb)) * wide_weight;
+ wide_total += wide_weight;
+ }
+ }
+ result = wide / wide_total;
+ remembered = 1.0;
+ } else {
+ const vec3 unclipped = RgbToYCoCg(Compress(SampleHistory(history_position)));
+ const vec3 past = ClipToBox(unclipped, low, high);
+ result = mix(past, current, weight);
+ // A history far outside the neighbourhood means the content changed without
+ // motion vectors (some animations have none): treat the pixel as moving for a while.
+ const vec3 extent = 0.5 * (high - low) + 1.0 / 65504.0;
+ const vec3 units = abs(unclipped - 0.5 * (low + high)) / extent;
+ remembered = max(remembered, smoothstep(1.25, 2.5, max(units.x, max(units.y, units.z))));
}
- const vec2 history_position = previous * vec2(textureSize(history, 0));
- const vec3 past = ClipToBox(RgbToYCoCg(Compress(SampleHistory(history_position))), low, high);
- color = vec4(Expand(YCoCgToRgb(mix(past, current, weight))), 1.0);
+ color = vec4(Expand(YCoCgToRgb(result)), 1.0);
+ imageStore(next_history, ivec2(gl_FragCoord.xy), vec4(color.rgb, remembered));
}
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index e9eaa2b3c2..363bb2d875 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -28,6 +28,9 @@ class AccelerateDMAInterface;
namespace Control {
struct ChannelState;
}
+namespace Texture {
+struct TICEntry;
+}
} // namespace Tegra
namespace VideoCore {
@@ -179,10 +182,12 @@ public:
/// Stand-in for a DLSS evaluation: temporal upscale of src into dst along motion_vectors
/// (jitter in src pixels; motion_scale turns a motion vector into a UV offset to the previous
- /// frame). False when unsupported; the caller then falls back to AccelerateSurfaceCopy.
+ /// frame), with the frame's depth when known. False when unsupported; the caller then falls
+ /// back to AccelerateSurfaceCopy.
[[nodiscard]] virtual bool AccelerateTemporalUpscale(
const Tegra::Engines::Fermi2D::Surface& src, const Tegra::Engines::Fermi2D::Surface& dst,
- const Tegra::Engines::Fermi2D::Surface& motion_vectors, std::array<f32, 2> jitter,
+ const Tegra::Engines::Fermi2D::Surface& motion_vectors,
+ const Tegra::Texture::TICEntry* depth, std::array<f32, 2> jitter,
std::array<f32, 2> motion_scale) {
return false;
}
diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp
index 506daef11e..0628da1d0f 100644
--- a/src/video_core/renderer_vulkan/blit_image.cpp
+++ b/src/video_core/renderer_vulkan/blit_image.cpp
@@ -7,6 +7,7 @@
#include <algorithm>
#include "video_core/renderer_vulkan/vk_texture_cache.h"
+#include "video_core/fsr.h"
#include "common/div_ceil.h"
#include "common/settings.h"
@@ -34,6 +35,7 @@
#include "video_core/host_shaders/convert_non_msaa_to_msaa_depth_stencil_frag_spv.h"
#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
#include "video_core/host_shaders/full_screen_triangle_vert_spv.h"
+#include "video_core/host_shaders/temporal_sharpen_frag_spv.h"
#include "video_core/host_shaders/temporal_upscale_frag_spv.h"
#include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h"
#include "video_core/host_shaders/vulkan_color_clear_frag_spv.h"
@@ -99,9 +101,19 @@ struct TemporalUpscalePushConstants {
std::array<float, 2> jitter;
std::array<float, 2> motion_scale;
float current_weight;
+ float kernel_scale;
+ float clip_width;
+ float clip_motion;
+ float kernel_motion;
float padding;
};
+// The vertex stage's PushConstants first, then temporal_sharpen.frag's FsrRcasCon.
+struct TemporalSharpenPushConstants {
+ PushConstants vertex;
+ std::array<u32, 4> rcas;
+};
+
struct MSAACopyPushConstants {
std::array<s32, 2> dst_offset;
std::array<s32, 2> src_offset;
@@ -120,10 +132,27 @@ constexpr std::array TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS{
TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<0>,
TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<1>,
};
-constexpr std::array THREE_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS{
+// temporal_upscale.frag: input colour, motion vectors and history, then the next history (storage).
+constexpr std::array TEMPORAL_UPSCALE_DESCRIPTOR_SET_LAYOUT_BINDINGS{
TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<0>,
TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<1>,
TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<2>,
+ VkDescriptorSetLayoutBinding{
+ .binding = 3,
+ .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
+ .pImmutableSamplers = nullptr,
+ },
+};
+constexpr DescriptorBankInfo TEMPORAL_UPSCALE_DESCRIPTOR_BANK_INFO{
+ .uniform_buffers = 0,
+ .storage_buffers = 0,
+ .texture_buffers = 0,
+ .image_buffers = 0,
+ .textures = 3,
+ .images = 1,
+ .score = 2,
};
constexpr VkDescriptorSetLayoutCreateInfo ONE_TEXTURE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
@@ -149,12 +178,12 @@ constexpr VkDescriptorSetLayoutCreateInfo TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_CRE
.bindingCount = static_cast<u32>(TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS.size()),
.pBindings = TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS.data(),
};
-constexpr VkDescriptorSetLayoutCreateInfo THREE_TEXTURES_DESCRIPTOR_SET_LAYOUT_CREATE_INFO{
+constexpr VkDescriptorSetLayoutCreateInfo TEMPORAL_UPSCALE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
- .bindingCount = static_cast<u32>(THREE_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS.size()),
- .pBindings = THREE_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS.data(),
+ .bindingCount = static_cast<u32>(TEMPORAL_UPSCALE_DESCRIPTOR_SET_LAYOUT_BINDINGS.size()),
+ .pBindings = TEMPORAL_UPSCALE_DESCRIPTOR_SET_LAYOUT_BINDINGS.data(),
};
template <VkShaderStageFlags stageFlags, size_t size>
inline constexpr VkPushConstantRange PUSH_CONSTANT_RANGE{
@@ -359,11 +388,12 @@ constexpr std::array<VkPipelineShaderStageCreateInfo, 2> MakeStages(
}
void UpdateOneTextureDescriptorSet(const Device& device, VkDescriptorSet descriptor_set,
- VkSampler sampler, VkImageView image_view) {
+ VkSampler sampler, VkImageView image_view,
+ VkImageLayout image_layout = VK_IMAGE_LAYOUT_GENERAL) {
const VkDescriptorImageInfo image_info{
.sampler = sampler,
.imageView = image_view,
- .imageLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .imageLayout = image_layout,
};
const VkWriteDescriptorSet write_descriptor_set{
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
@@ -380,13 +410,14 @@ void UpdateOneTextureDescriptorSet(const Device& device, VkDescriptorSet descrip
device.GetLogical().UpdateDescriptorSets(write_descriptor_set, nullptr);
}
-void UpdateThreeTexturesDescriptorSet(const Device& device, VkDescriptorSet descriptor_set,
- VkSampler sampler, std::array<VkImageView, 3> image_views) {
- std::array<VkDescriptorImageInfo, 3> image_infos{};
- std::array<VkWriteDescriptorSet, 3> write_descriptor_sets{};
- for (u32 binding = 0; binding < 3; ++binding) {
+void UpdateTemporalUpscaleDescriptorSet(const Device& device, VkDescriptorSet descriptor_set,
+ VkSampler sampler, std::array<VkImageView, 4> image_views) {
+ std::array<VkDescriptorImageInfo, 4> image_infos{};
+ std::array<VkWriteDescriptorSet, 4> write_descriptor_sets{};
+ for (u32 binding = 0; binding < 4; ++binding) {
+ const bool storage = binding == 3;
image_infos[binding] = VkDescriptorImageInfo{
- .sampler = sampler,
+ .sampler = storage ? VK_NULL_HANDLE : sampler,
.imageView = image_views[binding],
.imageLayout = VK_IMAGE_LAYOUT_GENERAL,
};
@@ -397,7 +428,8 @@ void UpdateThreeTexturesDescriptorSet(const Device& device, VkDescriptorSet desc
.dstBinding = binding,
.dstArrayElement = 0,
.descriptorCount = 1,
- .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .descriptorType = storage ? VK_DESCRIPTOR_TYPE_STORAGE_IMAGE
+ : VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = &image_infos[binding],
.pBufferInfo = nullptr,
.pTexelBufferView = nullptr,
@@ -639,14 +671,14 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
ONE_TEXTURE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO)),
two_textures_set_layout(device.GetLogical().CreateDescriptorSetLayout(
TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_CREATE_INFO)),
- three_textures_set_layout(device.GetLogical().CreateDescriptorSetLayout(
- THREE_TEXTURES_DESCRIPTOR_SET_LAYOUT_CREATE_INFO)),
+ temporal_upscale_set_layout(device.GetLogical().CreateDescriptorSetLayout(
+ TEMPORAL_UPSCALE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO)),
one_texture_descriptor_allocator{
descriptor_pool.Allocator(device_, scheduler_, *one_texture_set_layout, TEXTURE_DESCRIPTOR_BANK_INFO<1>)},
two_textures_descriptor_allocator{
descriptor_pool.Allocator(device_, scheduler_, *two_textures_set_layout, TEXTURE_DESCRIPTOR_BANK_INFO<2>)},
- three_textures_descriptor_allocator{
- descriptor_pool.Allocator(device_, scheduler_, *three_textures_set_layout, TEXTURE_DESCRIPTOR_BANK_INFO<3>)},
+ temporal_upscale_descriptor_allocator{descriptor_pool.Allocator(
+ device_, scheduler_, *temporal_upscale_set_layout, TEMPORAL_UPSCALE_DESCRIPTOR_BANK_INFO)},
one_texture_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo(
one_texture_set_layout.address(),
PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstants)>))),
@@ -656,9 +688,14 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstants)>))),
temporal_upscale_pipeline_layout(
device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo(
- three_textures_set_layout.address(),
+ temporal_upscale_set_layout.address(),
PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
sizeof(TemporalUpscalePushConstants)>))),
+ temporal_sharpen_pipeline_layout(
+ device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo(
+ one_texture_set_layout.address(),
+ PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
+ sizeof(TemporalSharpenPushConstants)>))),
clear_color_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo(
nullptr, PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(float) * 4>))),
msaa_copy_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo(
@@ -671,6 +708,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)),
blit_color_to_color_frag(BuildShader(device, BLIT_COLOR_FLOAT_FRAG_SPV)),
temporal_upscale_frag(BuildShader(device, TEMPORAL_UPSCALE_FRAG_SPV)),
+ temporal_sharpen_frag(BuildShader(device, TEMPORAL_SHARPEN_FRAG_SPV)),
blit_color_srgb_to_linear_frag(BuildShader(device, BLIT_COLOR_SRGB_TO_LINEAR_FRAG_SPV)),
blit_color_msaa_frag(BuildShader(device, BLIT_COLOR_MSAA_FRAG_SPV)),
blit_depth_stencil_frag(device.IsExtShaderStencilExportSupported()
@@ -752,7 +790,9 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView
TransitionImageLayout(cmdbuf, src_image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
BeginRenderPass(cmdbuf, dst_framebuffer);
const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
- UpdateOneTextureDescriptorSet(device, descriptor_set, src_sampler, src_image_view);
+ // The source was just transitioned: describe it in that layout.
+ UpdateOneTextureDescriptorSet(device, descriptor_set, src_sampler, src_image_view,
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
nullptr);
@@ -764,20 +804,26 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView
void BlitImageHelper::TemporalUpscale(const Framebuffer* dst_framebuffer,
const ImageView& input_color, const ImageView& motion_vectors,
- VkImageView history, const Region2D& dst_region,
+ VkImageView history, VkImageView next_history,
+ const Region2D& dst_region,
std::array<f32, 2> jitter, std::array<f32, 2> motion_scale,
- f32 current_weight) {
+ f32 current_weight, f32 kernel_scale, f32 clip_width,
+ f32 clip_motion, f32 kernel_motion) {
const VkPipelineLayout layout = *temporal_upscale_pipeline_layout;
const VkPipeline pipeline = FindOrEmplaceTemporalUpscalePipeline(dst_framebuffer->RenderPass());
const VkSampler sampler = *linear_sampler;
- const std::array<VkImageView, 3> views{input_color.Handle(Shader::TextureType::Color2D),
+ const std::array<VkImageView, 4> views{input_color.Handle(Shader::TextureType::Color2D),
motion_vectors.Handle(Shader::TextureType::Color2D),
- history};
+ history, next_history};
const TemporalUpscalePushConstants push_constants{
.vertex = {.tex_scale = {1.0f, 1.0f}, .tex_offset = {0.0f, 0.0f}},
.jitter = jitter,
.motion_scale = motion_scale,
.current_weight = current_weight,
+ .kernel_scale = kernel_scale,
+ .clip_width = clip_width,
+ .clip_motion = clip_motion,
+ .kernel_motion = kernel_motion,
.padding = 0.0f,
};
RecordShaderReadBarrier(scheduler, input_color);
@@ -785,8 +831,34 @@ void BlitImageHelper::TemporalUpscale(const Framebuffer* dst_framebuffer,
scheduler.RequestRenderpass(dst_framebuffer);
scheduler.Record([this, dst_region, pipeline, layout, sampler, views,
push_constants](vk::CommandBuffer cmdbuf) {
- const VkDescriptorSet descriptor_set = three_textures_descriptor_allocator.Commit();
- UpdateThreeTexturesDescriptorSet(device, descriptor_set, sampler, views);
+ const VkDescriptorSet descriptor_set = temporal_upscale_descriptor_allocator.Commit();
+ UpdateTemporalUpscaleDescriptorSet(device, descriptor_set, sampler, views);
+ cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+ cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
+ nullptr);
+ BindBlitState(cmdbuf, dst_region);
+ cmdbuf.PushConstants(layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
+ push_constants);
+ cmdbuf.Draw(3, 1, 0, 0);
+ });
+ scheduler.InvalidateState();
+}
+
+void BlitImageHelper::TemporalSharpen(const Framebuffer* dst_framebuffer, VkImageView source,
+ const Region2D& dst_region, f32 sharpness) {
+ const VkPipelineLayout layout = *temporal_sharpen_pipeline_layout;
+ const VkPipeline pipeline = FindOrEmplaceTemporalSharpenPipeline(dst_framebuffer->RenderPass());
+ const VkSampler sampler = *nearest_sampler;
+ TemporalSharpenPushConstants push_constants{
+ .vertex = {.tex_scale = {1.0f, 1.0f}, .tex_offset = {0.0f, 0.0f}},
+ .rcas = {},
+ };
+ FSR::FsrRcasCon(push_constants.rcas.data(), sharpness);
+ scheduler.RequestRenderpass(dst_framebuffer);
+ scheduler.Record([this, dst_region, pipeline, layout, sampler, source,
+ push_constants](vk::CommandBuffer cmdbuf) {
+ const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
+ UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, source);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
nullptr);
@@ -1437,6 +1509,59 @@ VkPipeline BlitImageHelper::FindOrEmplaceTemporalUpscalePipeline(VkRenderPass re
return *temporal_upscale_pipelines.back();
}
+VkPipeline BlitImageHelper::FindOrEmplaceTemporalSharpenPipeline(VkRenderPass renderpass) {
+ const auto it = std::ranges::find(temporal_sharpen_keys, renderpass);
+ if (it != temporal_sharpen_keys.end()) {
+ return *temporal_sharpen_pipelines[std::distance(temporal_sharpen_keys.begin(), it)];
+ }
+ temporal_sharpen_keys.push_back(renderpass);
+ const std::array stages = MakeStages(*full_screen_vert, *temporal_sharpen_frag);
+ const VkPipelineColorBlendAttachmentState blend_attachment{
+ .blendEnable = VK_FALSE,
+ .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .colorBlendOp = VK_BLEND_OP_ADD,
+ .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .alphaBlendOp = VK_BLEND_OP_ADD,
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
+ };
+ const VkPipelineColorBlendStateCreateInfo color_blend_create_info{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .logicOpEnable = VK_FALSE,
+ .logicOp = VK_LOGIC_OP_CLEAR,
+ .attachmentCount = 1,
+ .pAttachments = &blend_attachment,
+ .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
+ };
+ const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
+ temporal_sharpen_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
+ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stageCount = static_cast<u32>(stages.size()),
+ .pStages = stages.data(),
+ .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .pInputAssemblyState = &input_assembly_ci,
+ .pTessellationState = nullptr,
+ .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .pDepthStencilState = nullptr,
+ .pColorBlendState = &color_blend_create_info,
+ .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .layout = *temporal_sharpen_pipeline_layout,
+ .renderPass = renderpass,
+ .subpass = 0,
+ .basePipelineHandle = VK_NULL_HANDLE,
+ .basePipelineIndex = 0,
+ }, device.StaticPipelineCache()));
+ return *temporal_sharpen_pipelines.back();
+}
+
VkPipeline BlitImageHelper::FindOrEmplaceDepthStencilPipeline(const BlitImagePipelineKey& key) {
const auto it = std::ranges::find(blit_depth_stencil_keys, key);
if (it != blit_depth_stencil_keys.end()) {
diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h
index 4741f9fe45..a1c802848b 100644
--- a/src/video_core/renderer_vulkan/blit_image.h
+++ b/src/video_core/renderer_vulkan/blit_image.h
@@ -83,11 +83,20 @@ public:
const Region2D& src_region, const Extent3D& src_size);
/// Temporal upscale (temporal_upscale.frag) into dst_framebuffer: the jittered input colour,
- /// accumulated with the history image reprojected along the motion vectors.
+ /// accumulated with the history image reprojected along the motion vectors. The result is also
+ /// stored to next_history (a storage image of the output's size).
void TemporalUpscale(const Framebuffer* dst_framebuffer, const ImageView& input_color,
const ImageView& motion_vectors, VkImageView history,
- const Region2D& dst_region, std::array<f32, 2> jitter,
- std::array<f32, 2> motion_scale, f32 current_weight);
+ VkImageView next_history, const Region2D& dst_region,
+ std::array<f32, 2> jitter,
+ std::array<f32, 2> motion_scale, f32 current_weight,
+ f32 kernel_scale, f32 clip_width, f32 clip_motion,
+ f32 kernel_motion);
+
+ /// RCAS (temporal_sharpen.frag) of source into dst_framebuffer; sharpness in stops as
+ /// FsrRcasCon takes it (0 is the strongest).
+ void TemporalSharpen(const Framebuffer* dst_framebuffer, VkImageView source,
+ const Region2D& dst_region, f32 sharpness);
void BlitColorMSAA(const Framebuffer* dst_framebuffer, const ImageView& src_image_view,
const Region2D& dst_region, const Region2D& src_region);
@@ -179,6 +188,8 @@ private:
[[nodiscard]] VkPipeline FindOrEmplaceTemporalUpscalePipeline(VkRenderPass renderpass);
+ [[nodiscard]] VkPipeline FindOrEmplaceTemporalSharpenPipeline(VkRenderPass renderpass);
+
[[nodiscard]] VkPipeline FindOrEmplaceDepthStencilPipeline(const BlitImagePipelineKey& key);
[[nodiscard]] VkPipeline FindOrEmplaceClearColorPipeline(const BlitImagePipelineKey& key);
@@ -216,19 +227,21 @@ private:
vk::DescriptorSetLayout one_texture_set_layout;
vk::DescriptorSetLayout two_textures_set_layout;
- vk::DescriptorSetLayout three_textures_set_layout;
+ vk::DescriptorSetLayout temporal_upscale_set_layout;
DescriptorAllocator one_texture_descriptor_allocator;
DescriptorAllocator two_textures_descriptor_allocator;
- DescriptorAllocator three_textures_descriptor_allocator;
+ DescriptorAllocator temporal_upscale_descriptor_allocator;
vk::PipelineLayout one_texture_pipeline_layout;
vk::PipelineLayout two_textures_pipeline_layout;
vk::PipelineLayout temporal_upscale_pipeline_layout;
+ vk::PipelineLayout temporal_sharpen_pipeline_layout;
vk::PipelineLayout clear_color_pipeline_layout;
vk::PipelineLayout msaa_copy_pipeline_layout;
vk::PipelineLayout msaa_copy_depth_stencil_pipeline_layout;
vk::ShaderModule full_screen_vert;
vk::ShaderModule blit_color_to_color_frag;
vk::ShaderModule temporal_upscale_frag;
+ vk::ShaderModule temporal_sharpen_frag;
vk::ShaderModule blit_color_srgb_to_linear_frag;
vk::ShaderModule blit_color_msaa_frag;
vk::ShaderModule blit_depth_stencil_frag;
@@ -262,6 +275,8 @@ private:
std::vector<vk::Pipeline> blit_color_pipelines;
std::vector<VkRenderPass> temporal_upscale_keys;
std::vector<vk::Pipeline> temporal_upscale_pipelines;
+ std::vector<VkRenderPass> temporal_sharpen_keys;
+ std::vector<vk::Pipeline> temporal_sharpen_pipelines;
std::vector<BlitImagePipelineKey> blit_depth_stencil_keys;
std::vector<vk::Pipeline> blit_depth_stencil_pipelines;
std::vector<BlitImagePipelineKey> clear_color_keys;
diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
index b020d99b93..e19dabac46 100644
--- a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
@@ -31,7 +31,8 @@ struct DescriptorBank {
bool DescriptorBankInfo::IsSuperset(const DescriptorBankInfo& subset) const noexcept {
return uniform_buffers >= subset.uniform_buffers && storage_buffers >= subset.storage_buffers &&
texture_buffers >= subset.texture_buffers && image_buffers >= subset.image_buffers &&
- textures >= subset.textures && images >= subset.images;
+ textures >= subset.textures && images >= subset.images &&
+ sampled_images >= subset.sampled_images && samplers >= subset.samplers;
}
template <typename Descriptors>
@@ -59,7 +60,7 @@ static DescriptorBankInfo MakeBankInfo(std::span<const Shader::Info> infos) {
}
static void AllocatePool(const Device& device, DescriptorBank& bank) {
- std::array<VkDescriptorPoolSize, 6> pool_sizes;
+ std::array<VkDescriptorPoolSize, 8> pool_sizes;
size_t pool_cursor{};
const u32 sets_per_pool = device.GetSetsPerPool();
const auto add = [&](VkDescriptorType type, u32 count) {
@@ -77,6 +78,8 @@ static void AllocatePool(const Device& device, DescriptorBank& bank) {
add(VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, info.image_buffers);
add(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, info.textures);
add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, info.images);
+ add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, info.sampled_images);
+ add(VK_DESCRIPTOR_TYPE_SAMPLER, info.samplers);
bank.pools.push_back(device.GetLogical().CreateDescriptorPool({
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.pNext = nullptr,
diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.h b/src/video_core/renderer_vulkan/vk_descriptor_pool.h
index 4efeb60373..8213649090 100644
--- a/src/video_core/renderer_vulkan/vk_descriptor_pool.h
+++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.h
@@ -30,6 +30,8 @@ struct DescriptorBankInfo {
u32 image_buffers{}; ///< Number of image buffer descriptors
u32 textures{}; ///< Number of texture descriptors
u32 images{}; ///< Number of image descriptors
+ u32 sampled_images{}; ///< Number of sampled image descriptors (without a sampler)
+ u32 samplers{}; ///< Number of sampler descriptors
s32 score{}; ///< Number of descriptors in total
};
diff --git a/src/video_core/renderer_vulkan/vk_fsr3_upscaler.cpp b/src/video_core/renderer_vulkan/vk_fsr3_upscaler.cpp
new file mode 100644
index 0000000000..99275800d1
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_fsr3_upscaler.cpp
@@ -0,0 +1,693 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <algorithm>
+#include <cfloat>
+#include <cmath>
+#include <cstring>
+#include <numbers>
+#include <span>
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/div_ceil.h"
+#include "common/logging.h"
+#include "video_core/fsr.h"
+#include "video_core/host_shaders/fsr3_upscaler_accumulate_comp_spv.h"
+#include "video_core/host_shaders/fsr3_upscaler_accumulate_sharpen_comp_spv.h"
+#include "video_core/host_shaders/fsr3_upscaler_luma_instability_comp_spv.h"
+#include "video_core/host_shaders/fsr3_upscaler_luma_pyramid_comp_spv.h"
+#include "video_core/host_shaders/fsr3_upscaler_luma_pyramid_nowave_comp_spv.h"
+#include "video_core/host_shaders/fsr3_upscaler_prepare_inputs_comp_spv.h"
+#include "video_core/host_shaders/fsr3_upscaler_prepare_reactivity_comp_spv.h"
+#include "video_core/host_shaders/fsr3_upscaler_rcas_comp_spv.h"
+#include "video_core/host_shaders/fsr3_upscaler_shading_change_comp_spv.h"
+#include "video_core/host_shaders/fsr3_upscaler_shading_change_pyramid_comp_spv.h"
+#include "video_core/host_shaders/fsr3_upscaler_shading_change_pyramid_nowave_comp_spv.h"
+#include "video_core/renderer_vulkan/vk_compute_pass.h"
+#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
+#include "video_core/renderer_vulkan/vk_fsr3_upscaler.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
+#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_memory_allocator.h"
+
+namespace Vulkan {
+namespace {
+
+// cbFSR3Upscaler in ffx_fsr3upscaler_callbacks_glsl.h (std140; the SDK's Fsr3UpscalerConstants).
+struct Constants {
+ std::array<s32, 2> render_size;
+ std::array<s32, 2> previous_render_size;
+ std::array<s32, 2> upscale_size;
+ std::array<s32, 2> previous_upscale_size;
+ std::array<s32, 2> max_render_size;
+ std::array<s32, 2> max_upscale_size;
+ std::array<f32, 4> device_to_view_depth;
+ std::array<f32, 2> jitter;
+ std::array<f32, 2> previous_jitter;
+ std::array<f32, 2> motion_vector_scale;
+ std::array<f32, 2> downscale_factor;
+ std::array<f32, 2> motion_vector_jitter_cancellation;
+ f32 tan_half_fov;
+ f32 jitter_phase_count;
+ f32 delta_time;
+ f32 delta_pre_exposure;
+ f32 view_space_to_meters;
+ f32 frame_index;
+ f32 velocity_factor;
+ f32 reactiveness_scale;
+ f32 shading_change_scale;
+ f32 accumulation_added_per_frame;
+ f32 min_disocclusion_accumulation;
+};
+static_assert(sizeof(Constants) == 148);
+
+// cbSPD and cbRCAS.
+struct SpdConstants {
+ u32 mips;
+ u32 num_work_groups;
+ std::array<u32, 2> work_group_offset;
+ std::array<u32, 2> render_size;
+};
+
+struct RcasConstants {
+ std::array<u32, 4> config;
+};
+
+// Descriptor kinds in binding order, as the pass files number them (FSR3UPSCALER_BIND_*). Every
+// pass also has the two samplers at bindings 1000 (point clamp) and 1001 (linear clamp).
+enum class Kind : u8 { Texture, Image, Uniform };
+constexpr Kind T = Kind::Texture;
+constexpr Kind I = Kind::Image;
+constexpr Kind U = Kind::Uniform;
+
+constexpr std::array PREPARE_INPUTS{T, T, T, I, I, I, I, I, U};
+constexpr std::array LUMA_PYRAMID{T, T, I, I, I, I, I, I, I, I, I, U, U};
+constexpr std::array SHADING_CHANGE_PYRAMID{T, T, T, T, I, I, I, I, I, I, I, U, U};
+constexpr std::array SHADING_CHANGE{T, I, U};
+constexpr std::array PREPARE_REACTIVITY{T, T, T, T, T, T, T, T, T, I, I, I, U};
+constexpr std::array LUMA_INSTABILITY{T, T, T, T, T, T, T, I, I, U};
+constexpr std::array ACCUMULATE{T, T, T, T, T, T, T, T, T, I, I, I, U};
+constexpr std::array RCAS{T, T, I, U, U};
+
+enum PassId : size_t {
+ PassPrepareInputs,
+ PassLumaPyramid,
+ PassShadingChangePyramid,
+ PassShadingChange,
+ PassPrepareReactivity,
+ PassLumaInstability,
+ PassAccumulate,
+ PassAccumulateSharpen,
+ PassRcas,
+ PassCount,
+};
+
+constexpr u32 SPD_STORED_MIPS = 6;
+
+/// One descriptor of a dispatch: an image view, or a uniform buffer range.
+struct Resource {
+ VkImageView view{};
+ VkBuffer buffer{};
+ VkDeviceSize offset{};
+ VkDeviceSize size{};
+};
+
+constexpr VkMemoryBarrier MemoryBarrier(VkAccessFlags src, VkAccessFlags dst) {
+ return VkMemoryBarrier{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = src,
+ .dstAccessMask = dst,
+ };
+}
+
+constexpr VkImageSubresourceRange ALL_LEVELS{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = VK_REMAINING_MIP_LEVELS,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+};
+
+// ffxFsr3UpscalerGetJitterPhaseCount.
+s32 JitterPhaseCount(u32 render_width, u32 display_width) {
+ const f32 ratio = static_cast<f32>(display_width) / static_cast<f32>(render_width);
+ return static_cast<s32>(8.0f * ratio * ratio);
+}
+
+// DLSS takes no camera, so FSR's depth-to-view-space conversion (which only scales its
+// disocclusion thresholds) uses a typical near plane and vertical field of view.
+constexpr f32 CAMERA_NEAR = 0.1f;
+constexpr f32 CAMERA_FOV = 60.0f * std::numbers::pi_v<f32> / 180.0f;
+
+} // Anonymous namespace
+
+class Fsr3Upscaler::Pass final : public ComputePass {
+public:
+ explicit Pass(const Device& device_, Scheduler& scheduler, DescriptorPool& descriptor_pool,
+ std::span<const Kind> kinds, std::span<const u32> code,
+ const std::array<VkSampler, 2>& samplers)
+ : Pass(device_, scheduler, descriptor_pool, MakeLayout(kinds, samplers), code) {}
+
+ void Dispatch(Scheduler& scheduler, ComputePassDescriptorQueue& queue,
+ std::span<const Resource> resources, u32 groups_x, u32 groups_y) {
+ queue.Acquire(scheduler, resources.size());
+ for (const Resource& resource : resources) {
+ if (resource.buffer) {
+ queue.AddBuffer(resource.buffer, resource.offset, resource.size);
+ } else {
+ queue.AddImage(resource.view);
+ }
+ }
+ const void* const descriptor_data{queue.UpdateData()};
+ scheduler.Record([this, descriptor_data, groups_x, groups_y](vk::CommandBuffer cmdbuf) {
+ const VkDescriptorSet set = descriptor_allocator.Commit();
+ device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
+ cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
+ cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
+ cmdbuf.Dispatch(groups_x, groups_y, 1);
+ // The next pass reads what this one wrote (everything stays in GENERAL).
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+ VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0,
+ MemoryBarrier(VK_ACCESS_SHADER_WRITE_BIT,
+ VK_ACCESS_SHADER_READ_BIT |
+ VK_ACCESS_SHADER_WRITE_BIT));
+ });
+ }
+
+private:
+ struct Layout {
+ std::vector<VkDescriptorSetLayoutBinding> bindings;
+ std::vector<VkDescriptorUpdateTemplateEntry> templates;
+ DescriptorBankInfo bank;
+ };
+
+ explicit Pass(const Device& device_, Scheduler& scheduler, DescriptorPool& descriptor_pool,
+ const Layout& pass_layout, std::span<const u32> code)
+ : ComputePass(device_, scheduler, descriptor_pool, pass_layout.bindings,
+ pass_layout.templates, pass_layout.bank, {}, code) {}
+
+ static Layout MakeLayout(std::span<const Kind> kinds,
+ const std::array<VkSampler, 2>& samplers) {
+ Layout result;
+ for (u32 binding = 0; binding < kinds.size(); ++binding) {
+ VkDescriptorType type{};
+ switch (kinds[binding]) {
+ case Kind::Texture:
+ type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
+ ++result.bank.sampled_images;
+ break;
+ case Kind::Image:
+ type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+ ++result.bank.images;
+ break;
+ case Kind::Uniform:
+ type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ ++result.bank.uniform_buffers;
+ break;
+ }
+ result.bindings.push_back({
+ .binding = binding,
+ .descriptorType = type,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
+ .pImmutableSamplers = nullptr,
+ });
+ result.templates.push_back({
+ .dstBinding = binding,
+ .dstArrayElement = 0,
+ .descriptorCount = 1,
+ .descriptorType = type,
+ .offset = binding * sizeof(DescriptorUpdateEntry),
+ .stride = sizeof(DescriptorUpdateEntry),
+ });
+ }
+ for (u32 index = 0; index < samplers.size(); ++index) {
+ result.bindings.push_back({
+ .binding = 1000 + index,
+ .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
+ .pImmutableSamplers = &samplers[index],
+ });
+ ++result.bank.samplers;
+ }
+ result.bank.score = static_cast<s32>(result.bindings.size());
+ return result;
+ }
+};
+
+Fsr3Upscaler::Fsr3Upscaler(const Device& device_, Scheduler& scheduler_,
+ MemoryAllocator& memory_allocator_,
+ StagingBufferPool& staging_buffer_pool_,
+ DescriptorPool& descriptor_pool,
+ ComputePassDescriptorQueue& descriptor_queue_)
+ : device{device_}, scheduler{scheduler_}, memory_allocator{memory_allocator_},
+ staging_buffer_pool{staging_buffer_pool_}, descriptor_queue{descriptor_queue_} {
+ const auto sampler = [&](VkFilter filter, VkSamplerMipmapMode mipmap) {
+ return device.GetLogical().CreateSampler(VkSamplerCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .magFilter = filter,
+ .minFilter = filter,
+ .mipmapMode = mipmap,
+ .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
+ .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
+ .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
+ .mipLodBias = 0.0f,
+ .anisotropyEnable = VK_FALSE,
+ .maxAnisotropy = 1.0f,
+ .compareEnable = VK_FALSE,
+ .compareOp = VK_COMPARE_OP_NEVER,
+ .minLod = 0.0f,
+ .maxLod = VK_LOD_CLAMP_NONE,
+ .borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
+ .unnormalizedCoordinates = VK_FALSE,
+ });
+ };
+ point_sampler = sampler(VK_FILTER_NEAREST, VK_SAMPLER_MIPMAP_MODE_NEAREST);
+ linear_sampler = sampler(VK_FILTER_LINEAR, VK_SAMPLER_MIPMAP_MODE_LINEAR);
+ const std::array<VkSampler, 2> samplers{*point_sampler, *linear_sampler};
+
+ // SPD reduces with quad shuffles where the device has them in compute shaders.
+ const bool quads = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_QUAD_BIT) &&
+ (device.GetSubgroupSupportedStages() & VK_SHADER_STAGE_COMPUTE_BIT) != 0;
+ const auto pass = [&](std::span<const Kind> kinds, std::span<const u32> code) {
+ return std::make_unique<Pass>(device, scheduler, descriptor_pool, kinds, code, samplers);
+ };
+ passes.resize(PassCount);
+ passes[PassPrepareInputs] = pass(PREPARE_INPUTS, FSR3_UPSCALER_PREPARE_INPUTS_COMP_SPV);
+ passes[PassLumaPyramid] =
+ pass(LUMA_PYRAMID, quads ? std::span<const u32>(FSR3_UPSCALER_LUMA_PYRAMID_COMP_SPV)
+ : std::span<const u32>(FSR3_UPSCALER_LUMA_PYRAMID_NOWAVE_COMP_SPV));
+ passes[PassShadingChangePyramid] = pass(
+ SHADING_CHANGE_PYRAMID,
+ quads ? std::span<const u32>(FSR3_UPSCALER_SHADING_CHANGE_PYRAMID_COMP_SPV)
+ : std::span<const u32>(FSR3_UPSCALER_SHADING_CHANGE_PYRAMID_NOWAVE_COMP_SPV));
+ passes[PassShadingChange] = pass(SHADING_CHANGE, FSR3_UPSCALER_SHADING_CHANGE_COMP_SPV);
+ passes[PassPrepareReactivity] =
+ pass(PREPARE_REACTIVITY, FSR3_UPSCALER_PREPARE_REACTIVITY_COMP_SPV);
+ passes[PassLumaInstability] = pass(LUMA_INSTABILITY, FSR3_UPSCALER_LUMA_INSTABILITY_COMP_SPV);
+ passes[PassAccumulate] = pass(ACCUMULATE, FSR3_UPSCALER_ACCUMULATE_COMP_SPV);
+ passes[PassAccumulateSharpen] = pass(ACCUMULATE, FSR3_UPSCALER_ACCUMULATE_SHARPEN_COMP_SPV);
+ passes[PassRcas] = pass(RCAS, FSR3_UPSCALER_RCAS_COMP_SPV);
+}
+
+Fsr3Upscaler::~Fsr3Upscaler() = default;
+
+bool Fsr3Upscaler::IsSupported(const Device& device) {
+ if (!device.IsStorageImageExtendedFormatsSupported()) {
+ return false;
+ }
+ constexpr VkFormatFeatureFlags storage =
+ VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
+ VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
+ for (const VkFormat format :
+ {VK_FORMAT_R8_UNORM, VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16G16_SFLOAT, VK_FORMAT_R32_SFLOAT,
+ VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R16G16B16A16_SFLOAT}) {
+ if (!device.IsFormatSupported(format, storage, FormatType::Optimal)) {
+ return false;
+ }
+ }
+ return device.IsFormatSupported(VK_FORMAT_R32_UINT, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT,
+ FormatType::Optimal) &&
+ device.IsFormatSupported(VK_FORMAT_R32G32B32A32_SFLOAT,
+ VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT, FormatType::Optimal) &&
+ device.IsFormatSupported(VK_FORMAT_R16_SNORM,
+ VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
+ VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT,
+ FormatType::Optimal);
+}
+
+VkImage Fsr3Upscaler::OutputImage() const noexcept {
+ return *output.image;
+}
+
+VkImageView Fsr3Upscaler::OutputView() const noexcept {
+ return *output.view;
+}
+
+Fsr3Upscaler::Texture Fsr3Upscaler::CreateTexture(VkFormat format, u32 width, u32 height,
+ u32 levels) {
+ Texture texture;
+ texture.image = memory_allocator.CreateImage(VkImageCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = format,
+ .extent = {(std::max)(width, 1U), (std::max)(height, 1U), 1},
+ .mipLevels = levels,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = static_cast<VkImageUsageFlags>(
+ VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ (format == VK_FORMAT_R16_SNORM ? 0 : VK_IMAGE_USAGE_STORAGE_BIT)),
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ });
+ const auto view = [&](u32 base_level, u32 level_count) {
+ return device.GetLogical().CreateImageView(VkImageViewCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .image = *texture.image,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = format,
+ .components{},
+ .subresourceRange{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = base_level,
+ .levelCount = level_count,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ });
+ };
+ texture.view = view(0, levels);
+ if (levels > 1) {
+ for (u32 level = 0; level < levels; ++level) {
+ texture.levels.push_back(view(level, 1));
+ }
+ }
+ return texture;
+}
+
+void Fsr3Upscaler::CreateResources(VkExtent2D render_size, VkExtent2D upscale_size) {
+ const u32 rw = render_size.width;
+ const u32 rh = render_size.height;
+ const u32 uw = upscale_size.width;
+ const u32 uh = upscale_size.height;
+ for (size_t i = 0; i < 2; ++i) {
+ accumulation[i] = CreateTexture(VK_FORMAT_R8_UNORM, rw, rh);
+ luma[i] = CreateTexture(VK_FORMAT_R16_SFLOAT, rw, rh);
+ // The pass files declare the luma history as rgba8 (the SDK allocates RGBA16F, which a
+ // storage format qualifier does not match); allocate what the shaders store.
+ luma_history[i] = CreateTexture(VK_FORMAT_R8G8B8A8_UNORM, rw, rh);
+ internal_upscaled[i] = CreateTexture(VK_FORMAT_R16G16B16A16_SFLOAT, uw, uh);
+ }
+ farthest_depth = CreateTexture(VK_FORMAT_R16_SFLOAT, rw, rh);
+ shading_change = CreateTexture(VK_FORMAT_R8_UNORM, rw / 2, rh / 2);
+ new_locks = CreateTexture(VK_FORMAT_R8_UNORM, uw, uh);
+ const u32 spd_levels =
+ static_cast<u32>(std::floor(std::log2(static_cast<f32>((std::max)(rw / 2, rh / 2))))) + 1;
+ spd_mips = CreateTexture(VK_FORMAT_R16G16_SFLOAT, rw / 2, rh / 2,
+ (std::max)(spd_levels, SPD_STORED_MIPS));
+ farthest_depth_mip1 = CreateTexture(VK_FORMAT_R16_SFLOAT, rw / 2, rh / 2);
+ dilated_motion_vectors = CreateTexture(VK_FORMAT_R16G16_SFLOAT, rw, rh);
+ dilated_depth = CreateTexture(VK_FORMAT_R32_SFLOAT, rw, rh);
+ previous_nearest_depth = CreateTexture(VK_FORMAT_R32_UINT, rw, rh);
+ dilated_reactive_masks = CreateTexture(VK_FORMAT_R8G8B8A8_UNORM, rw, rh);
+ frame_info = CreateTexture(VK_FORMAT_R32G32B32A32_SFLOAT, 1, 1);
+ spd_atomic = CreateTexture(VK_FORMAT_R32_UINT, 1, 1);
+ lanczos_lut = CreateTexture(VK_FORMAT_R16_SNORM, 128, 1);
+ default_reactive = CreateTexture(VK_FORMAT_R8_UNORM, 1, 1);
+ output = CreateTexture(VK_FORMAT_R16G16B16A16_SFLOAT, uw, uh);
+ render_extent = render_size;
+ upscale_extent = upscale_size;
+ first_execution = true;
+ frame_parity = 0;
+ LOG_INFO(Render_Vulkan, "FSR 3.1 upscaler: {}x{} -> {}x{}", rw, rh, uw, uh);
+}
+
+void Fsr3Upscaler::Upscale(const Inputs& in) {
+ if (!output.image || in.render_size.width != render_extent.width ||
+ in.render_size.height != render_extent.height ||
+ in.upscale_size.width != upscale_extent.width ||
+ in.upscale_size.height != upscale_extent.height) {
+ if (output.image) {
+ scheduler.Finish(); // the old images may still be in use
+ }
+ CreateResources(in.render_size, in.upscale_size);
+ }
+ const bool first = first_execution;
+ const bool reset = in.reset || first;
+ first_execution = false;
+
+ const s32 rw = static_cast<s32>(render_extent.width);
+ const s32 rh = static_cast<s32>(render_extent.height);
+ const s32 uw = static_cast<s32>(upscale_extent.width);
+ const s32 uh = static_cast<s32>(upscale_extent.height);
+
+ // Constants, as fsr3upscalerDispatch computes them.
+ Constants constants{};
+ constants.render_size = {rw, rh};
+ constants.previous_render_size = first ? constants.render_size : previous_render_size;
+ constants.upscale_size = {uw, uh};
+ constants.previous_upscale_size = first ? constants.upscale_size : previous_upscale_size;
+ constants.max_render_size = constants.render_size;
+ constants.max_upscale_size = constants.upscale_size;
+ {
+ // Reversed infinite depth (setupDeviceDepthToViewSpaceDepthParams with near = FLT_MAX).
+ const f32 near_plane = CAMERA_NEAR;
+ const f32 fov = CAMERA_FOV;
+ const f32 aspect = static_cast<f32>(rw) / static_cast<f32>(rh);
+ const f32 cot_half_fov = std::cos(0.5f * fov) / std::sin(0.5f * fov);
+ constants.device_to_view_depth = {-1.0f * (0.0f + FLT_EPSILON), near_plane,
+ 1.0f / (cot_half_fov / aspect), 1.0f / cot_half_fov};
+ constants.tan_half_fov = std::tan(std::atan(std::tan(fov * 0.5f) * aspect));
+ }
+ constants.jitter = in.jitter;
+ constants.previous_jitter = first ? in.jitter : previous_jitter;
+ constants.motion_vector_scale = {in.motion_vector_scale[0] / static_cast<f32>(rw),
+ in.motion_vector_scale[1] / static_cast<f32>(rh)};
+ constants.downscale_factor = {static_cast<f32>(rw) / static_cast<f32>(uw),
+ static_cast<f32>(rh) / static_cast<f32>(uh)};
+ constants.motion_vector_jitter_cancellation = {0.0f, 0.0f};
+ const s32 phase_count = JitterPhaseCount(render_extent.width, upscale_extent.width);
+ if (reset || jitter_phase_count == 0.0f) {
+ jitter_phase_count = static_cast<f32>(phase_count);
+ } else if (static_cast<f32>(phase_count) > jitter_phase_count) {
+ jitter_phase_count += 1.0f;
+ } else if (static_cast<f32>(phase_count) < jitter_phase_count) {
+ jitter_phase_count -= 1.0f;
+ }
+ constants.jitter_phase_count = jitter_phase_count;
+ constants.delta_time = std::clamp(in.frame_time_ms / 1000.0f, 0.0f, 1.0f);
+ constants.delta_pre_exposure = 1.0f;
+ constants.view_space_to_meters = 1.0f;
+ frame_index = reset ? 0.0f : frame_index + 1.0f;
+ constants.frame_index = frame_index;
+ // The SDK's defaults (FFX_FSR3UPSCALER_CONFIGURE_UPSCALE_KEY_*).
+ constants.velocity_factor = 1.0f;
+ constants.reactiveness_scale = 1.0f;
+ constants.shading_change_scale = 1.0f;
+ constants.accumulation_added_per_frame = 1.0f / 3.0f;
+ constants.min_disocclusion_accumulation = -1.0f / 3.0f;
+ previous_render_size = constants.render_size;
+ previous_upscale_size = constants.upscale_size;
+ previous_jitter = in.jitter;
+
+ // ffxSpdSetup over the whole render area.
+ const u32 spd_groups_x = (render_extent.width - 1) / 64 + 1;
+ const u32 spd_groups_y = (render_extent.height - 1) / 64 + 1;
+ const SpdConstants spd{
+ .mips = static_cast<u32>((std::min)(
+ std::floor(std::log2(static_cast<f32>(
+ (std::max)(render_extent.width, render_extent.height)))),
+ 12.0f)),
+ .num_work_groups = spd_groups_x * spd_groups_y,
+ .work_group_offset = {0, 0},
+ .render_size = {render_extent.width, render_extent.height},
+ };
+ RcasConstants rcas{};
+ FSR::FsrRcasCon(rcas.config.data(), 2.0f - 2.0f * std::clamp(in.sharpness, 0.0f, 1.0f));
+
+ const VkDeviceSize alignment = (std::max<VkDeviceSize>)(device.GetUniformBufferAlignment(), 16);
+ const VkDeviceSize slot = Common::AlignUp(VkDeviceSize{sizeof(Constants)}, alignment);
+ const VkDeviceSize spd_slot = Common::AlignUp(VkDeviceSize{sizeof(SpdConstants)}, alignment);
+ const VkDeviceSize rcas_slot = Common::AlignUp(VkDeviceSize{sizeof(RcasConstants)}, alignment);
+ const StagingBufferRef uniforms =
+ staging_buffer_pool.Request(slot + spd_slot + rcas_slot, MemoryUsage::Upload);
+ std::memcpy(uniforms.mapped_span.data(), &constants, sizeof(constants));
+ std::memcpy(uniforms.mapped_span.data() + slot, &spd, sizeof(spd));
+ std::memcpy(uniforms.mapped_span.data() + slot + spd_slot, &rcas, sizeof(rcas));
+ const Resource cb_fsr{.buffer = uniforms.buffer, .offset = uniforms.offset, .size = slot};
+ const Resource cb_spd{
+ .buffer = uniforms.buffer, .offset = uniforms.offset + slot, .size = spd_slot};
+ const Resource cb_rcas{
+ .buffer = uniforms.buffer, .offset = uniforms.offset + slot + spd_slot, .size = rcas_slot};
+
+ // Clears: everything when the images are new, the history on a reset, and the per-frame
+ // scratch (previous nearest depth, the SPD counter and mips) always.
+ const size_t p = frame_parity;
+ const size_t q = p ^ 1;
+ std::vector<VkImage> clear_zero{*previous_nearest_depth.image, *spd_atomic.image,
+ *spd_mips.image};
+ std::vector<VkImage> clear_frame_info;
+ if (reset) {
+ clear_zero.push_back(*accumulation[p].image);
+ clear_frame_info.push_back(*frame_info.image);
+ }
+ std::vector<VkImage> initialize;
+ if (first) {
+ for (const auto* textures : {&accumulation, &luma, &luma_history, &internal_upscaled}) {
+ for (const Texture& texture : *textures) {
+ initialize.push_back(*texture.image);
+ }
+ }
+ for (const Texture* texture :
+ {&farthest_depth, &shading_change, &new_locks, &spd_mips, &farthest_depth_mip1,
+ &dilated_motion_vectors, &dilated_depth, &previous_nearest_depth,
+ &dilated_reactive_masks, &frame_info, &spd_atomic, &lanczos_lut, &default_reactive,
+ &output}) {
+ initialize.push_back(*texture->image);
+ }
+ clear_zero = initialize;
+ std::erase(clear_zero, *lanczos_lut.image);
+ std::erase(clear_zero, *frame_info.image);
+ clear_frame_info = {*frame_info.image};
+ }
+ std::optional<StagingBufferRef> lut_upload;
+ if (first) {
+ // FSR's Lanczos-2 table (fsr3upscalerCreate).
+ std::array<s16, 128> weights{};
+ for (size_t i = 0; i < weights.size(); ++i) {
+ const f32 x = 2.0f * static_cast<f32>(i) / static_cast<f32>(weights.size() - 1);
+ const f32 pi_x = std::numbers::pi_v<f32> * x;
+ const f32 y = std::abs(x) < FLT_EPSILON
+ ? 1.0f
+ : (std::sin(pi_x) / pi_x) * (std::sin(0.5f * pi_x) / (0.5f * pi_x));
+ weights[i] = static_cast<s16>(std::round(y * 32767.0f));
+ }
+ lut_upload = staging_buffer_pool.Request(sizeof(weights), MemoryUsage::Upload);
+ std::memcpy(lut_upload->mapped_span.data(), weights.data(), sizeof(weights));
+ }
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([initialize, clear_zero, clear_frame_info, lut = *lanczos_lut.image,
+ lut_buffer = lut_upload ? lut_upload->buffer : VkBuffer{},
+ lut_offset = lut_upload ? lut_upload->offset : 0](vk::CommandBuffer cmdbuf) {
+ // The inputs come from the game's draws and dispatches; our images from the last frame.
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+ 0,
+ MemoryBarrier(VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_SHADER_WRITE_BIT |
+ VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
+ VK_ACCESS_TRANSFER_WRITE_BIT));
+ if (!initialize.empty()) {
+ std::vector<VkImageMemoryBarrier> barriers;
+ for (const VkImage image : initialize) {
+ barriers.push_back(VkImageMemoryBarrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = image,
+ .subresourceRange = ALL_LEVELS,
+ });
+ }
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, {}, {}, barriers);
+ }
+ if (lut_buffer) {
+ const VkBufferImageCopy copy{
+ .bufferOffset = lut_offset,
+ .bufferRowLength = 0,
+ .bufferImageHeight = 0,
+ .imageSubresource{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .imageOffset{0, 0, 0},
+ .imageExtent{128, 1, 1},
+ };
+ cmdbuf.CopyBufferToImage(lut_buffer, lut, VK_IMAGE_LAYOUT_GENERAL, copy);
+ }
+ for (const VkImage image : clear_zero) {
+ cmdbuf.ClearColorImage(image, VK_IMAGE_LAYOUT_GENERAL, VkClearColorValue{}, ALL_LEVELS);
+ }
+ for (const VkImage image : clear_frame_info) {
+ VkClearColorValue value{};
+ value.float32[0] = -1.0f;
+ value.float32[1] = 1.0f;
+ cmdbuf.ClearColorImage(image, VK_IMAGE_LAYOUT_GENERAL, value, ALL_LEVELS);
+ }
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+ 0,
+ MemoryBarrier(VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT));
+ });
+
+ const auto tex = [](const Texture& texture) { return Resource{.view = *texture.view}; };
+ const auto mip = [&](u32 level) {
+ return Resource{.view = *spd_mips.levels[(std::min)(level, static_cast<u32>(
+ spd_mips.levels.size() - 1))]};
+ };
+ const Resource color{.view = in.color};
+ const Resource depth{.view = in.depth};
+ const Resource motion{.view = in.motion_vectors};
+ const Resource exposure = tex(frame_info); // FSR's own exposure (auto exposure)
+ const Resource current_luma = tex(luma[p]);
+ const auto groups = [](s32 size, u32 tile) {
+ return Common::DivCeil(static_cast<u32>(size), tile);
+ };
+
+ passes[PassPrepareInputs]->Dispatch(
+ scheduler, descriptor_queue,
+ std::array{motion, depth, color, tex(dilated_motion_vectors), tex(dilated_depth),
+ tex(previous_nearest_depth), tex(farthest_depth), current_luma, cb_fsr},
+ groups(rw, 8), groups(rh, 8));
+ passes[PassLumaPyramid]->Dispatch(
+ scheduler, descriptor_queue,
+ std::array{current_luma, tex(farthest_depth), tex(spd_atomic), tex(frame_info), mip(0),
+ mip(1), mip(2), mip(3), mip(4), mip(5), tex(farthest_depth_mip1), cb_fsr,
+ cb_spd},
+ spd_groups_x, spd_groups_y);
+ passes[PassShadingChangePyramid]->Dispatch(
+ scheduler, descriptor_queue,
+ std::array{current_luma, tex(luma[q]), tex(dilated_motion_vectors), exposure,
+ tex(spd_atomic), mip(0), mip(1), mip(2), mip(3), mip(4), mip(5), cb_fsr, cb_spd},
+ spd_groups_x, spd_groups_y);
+ passes[PassShadingChange]->Dispatch(
+ scheduler, descriptor_queue, std::array{tex(spd_mips), tex(shading_change), cb_fsr},
+ groups(rw / 2, 8), groups(rh / 2, 8));
+ passes[PassPrepareReactivity]->Dispatch(
+ scheduler, descriptor_queue,
+ std::array{tex(previous_nearest_depth), tex(dilated_motion_vectors), tex(dilated_depth),
+ tex(default_reactive), tex(default_reactive), tex(accumulation[p]),
+ tex(shading_change), current_luma, exposure, tex(dilated_reactive_masks),
+ tex(new_locks), tex(accumulation[q]), cb_fsr},
+ groups(rw, 8), groups(rh, 8));
+ passes[PassLumaInstability]->Dispatch(
+ scheduler, descriptor_queue,
+ std::array{exposure, tex(dilated_reactive_masks), tex(dilated_motion_vectors),
+ tex(frame_info), tex(luma_history[p]), tex(farthest_depth_mip1), current_luma,
+ tex(luma_history[q]), tex(farthest_depth), cb_fsr},
+ groups(rw, 8), groups(rh, 8));
+ passes[in.sharpen ? PassAccumulateSharpen : PassAccumulate]->Dispatch(
+ scheduler, descriptor_queue,
+ std::array{exposure, tex(dilated_reactive_masks), tex(dilated_motion_vectors),
+ tex(internal_upscaled[p]), tex(lanczos_lut), tex(farthest_depth_mip1),
+ current_luma, tex(farthest_depth), color, tex(internal_upscaled[q]),
+ tex(output), tex(new_locks), cb_fsr},
+ groups(uw, 8), groups(uh, 8));
+ if (in.sharpen) {
+ passes[PassRcas]->Dispatch(
+ scheduler, descriptor_queue,
+ std::array{exposure, tex(internal_upscaled[q]), tex(output), cb_fsr, cb_rcas},
+ groups(uw, 16), groups(uh, 16));
+ }
+ scheduler.Record([](vk::CommandBuffer cmdbuf) {
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
+ MemoryBarrier(VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT));
+ });
+ frame_parity = q;
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_fsr3_upscaler.h b/src/video_core/renderer_vulkan/vk_fsr3_upscaler.h
new file mode 100644
index 0000000000..e62b5fdeff
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_fsr3_upscaler.h
@@ -0,0 +1,119 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <vector>
+
+#include "common/common_types.h"
+#include "video_core/renderer_vulkan/vk_update_descriptor.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Vulkan {
+
+class DescriptorPool;
+class Device;
+class MemoryAllocator;
+class Scheduler;
+class StagingBufferPool;
+
+/// AMD FidelityFX Super Resolution 3.1 (the FSR 3.1.4 upscaler from FidelityFX SDK v1.1.4, shaders
+/// in externals/FidelityFX-SDK) in place of a game's DLSS evaluation. The host side follows the
+/// SDK's ffx_fsr3upscaler.cpp: the same passes, internal images and constants, recorded through the
+/// scheduler. Inputs are taken as reversed depth, render-size motion vectors and HDR colour, and
+/// the exposure is computed by FSR itself.
+class Fsr3Upscaler {
+public:
+ struct Inputs {
+ VkImageView color; ///< Jittered linear colour at the render size.
+ VkImageView depth; ///< Device depth (reversed) at the render size.
+ VkImageView motion_vectors; ///< Motion to the previous frame at the render size.
+ VkExtent2D render_size;
+ VkExtent2D upscale_size;
+ /// This frame's sample offset in render pixels: input texel k sampled k + 0.5 - jitter.
+ std::array<f32, 2> jitter;
+ /// Turns a motion vector into render pixels (the render size for UV offsets).
+ std::array<f32, 2> motion_vector_scale;
+ f32 frame_time_ms;
+ bool reset;
+ bool sharpen;
+ f32 sharpness; ///< 0 to 1 as FSR takes it: 2 stops of RCAS at 0, none at 1.
+ };
+
+ explicit Fsr3Upscaler(const Device& device, Scheduler& scheduler,
+ MemoryAllocator& memory_allocator, StagingBufferPool& staging_buffer_pool,
+ DescriptorPool& descriptor_pool,
+ ComputePassDescriptorQueue& descriptor_queue);
+ ~Fsr3Upscaler();
+
+ /// Whether the device has what the passes need (formats, shader features).
+ [[nodiscard]] static bool IsSupported(const Device& device);
+
+ /// Records one evaluation into Output(): RGBA16F at the upscale size, GENERAL layout, ready to
+ /// be sampled by a fragment shader.
+ void Upscale(const Inputs& inputs);
+
+ [[nodiscard]] VkImage OutputImage() const noexcept;
+ [[nodiscard]] VkImageView OutputView() const noexcept;
+
+ /// Nearest-neighbour clamp sampler (normalized coordinates), for reading Output().
+ [[nodiscard]] VkSampler PointSampler() const noexcept {
+ return *point_sampler;
+ }
+
+private:
+ class Pass;
+
+ struct Texture {
+ vk::Image image;
+ vk::ImageView view; ///< Every level.
+ std::vector<vk::ImageView> levels; ///< One view per level, for images with mips.
+ };
+
+ void CreateResources(VkExtent2D render_size, VkExtent2D upscale_size);
+ Texture CreateTexture(VkFormat format, u32 width, u32 height, u32 levels = 1);
+
+ const Device& device;
+ Scheduler& scheduler;
+ MemoryAllocator& memory_allocator;
+ StagingBufferPool& staging_buffer_pool;
+ ComputePassDescriptorQueue& descriptor_queue;
+
+ vk::Sampler point_sampler;
+ vk::Sampler linear_sampler;
+ std::vector<std::unique_ptr<Pass>> passes;
+
+ // Ping-pong pairs, indexed by frame parity like the SDK's resource tables.
+ std::array<Texture, 2> accumulation;
+ std::array<Texture, 2> luma;
+ std::array<Texture, 2> luma_history;
+ std::array<Texture, 2> internal_upscaled;
+ Texture farthest_depth; ///< Also the luma instability (the SDK aliases them).
+ Texture shading_change;
+ Texture new_locks;
+ Texture spd_mips;
+ Texture farthest_depth_mip1;
+ Texture dilated_motion_vectors;
+ Texture dilated_depth;
+ Texture previous_nearest_depth;
+ Texture dilated_reactive_masks;
+ Texture frame_info;
+ Texture spd_atomic;
+ Texture lanczos_lut;
+ Texture default_reactive;
+ Texture output;
+
+ VkExtent2D render_extent{};
+ VkExtent2D upscale_extent{};
+ std::array<s32, 2> previous_render_size{};
+ std::array<s32, 2> previous_upscale_size{};
+ std::array<f32, 2> previous_jitter{};
+ f32 jitter_phase_count{};
+ f32 frame_index{};
+ u32 frame_parity{};
+ bool first_execution{true};
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 3898c571f5..0e5124dba6 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -1305,10 +1305,10 @@ bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surf
bool RasterizerVulkan::AccelerateTemporalUpscale(
const Tegra::Engines::Fermi2D::Surface& src, const Tegra::Engines::Fermi2D::Surface& dst,
- const Tegra::Engines::Fermi2D::Surface& motion_vectors, std::array<f32, 2> jitter,
- std::array<f32, 2> motion_scale) {
+ const Tegra::Engines::Fermi2D::Surface& motion_vectors, const Tegra::Texture::TICEntry* depth,
+ std::array<f32, 2> jitter, std::array<f32, 2> motion_scale) {
std::scoped_lock lock{texture_cache.mutex};
- return texture_cache.TemporalUpscale(dst, src, motion_vectors, jitter, motion_scale);
+ return texture_cache.TemporalUpscale(dst, src, motion_vectors, depth, jitter, motion_scale);
}
Tegra::Engines::AccelerateDMAInterface& RasterizerVulkan::AccessAccelerateDMA() {
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index dbb6ebdca8..7fa536f53f 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -133,6 +133,7 @@ public:
bool AccelerateTemporalUpscale(const Tegra::Engines::Fermi2D::Surface& src,
const Tegra::Engines::Fermi2D::Surface& dst,
const Tegra::Engines::Fermi2D::Surface& motion_vectors,
+ const Tegra::Texture::TICEntry* depth,
std::array<f32, 2> jitter,
std::array<f32, 2> motion_scale) override;
Tegra::Engines::AccelerateDMAInterface& AccessAccelerateDMA() override;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 928146027d..e3f427dead 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -1203,11 +1203,13 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
StagingBufferPool& staging_buffer_pool_,
BlitImageHelper& blit_image_helper_,
RenderPassCache& render_pass_cache_,
- DescriptorPool& descriptor_pool,
- ComputePassDescriptorQueue& compute_pass_descriptor_queue)
+ DescriptorPool& descriptor_pool_,
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: device{device_}, scheduler{scheduler_}, memory_allocator{memory_allocator_},
staging_buffer_pool{staging_buffer_pool_}, blit_image_helper{blit_image_helper_},
- render_pass_cache{render_pass_cache_}, resolution{Settings::values.resolution_info} {
+ render_pass_cache{render_pass_cache_}, descriptor_pool{descriptor_pool_},
+ compute_pass_descriptor_queue{compute_pass_descriptor_queue_},
+ resolution{Settings::values.resolution_info} {
// Before anything below reads astc_recompression (passes, view formats).
UseBc3ForSwitch2OnSmallCards(device);
// Huge ASTC arrays always decode on the GPU (IsHugeAstcArray), whatever the decode setting.
@@ -1535,49 +1537,99 @@ void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src,
});
}
-bool TextureCacheRuntime::TemporalUpscale(Framebuffer* dst_framebuffer, ImageView& dst,
- ImageView& input_color, ImageView& motion_vectors,
+bool TextureCacheRuntime::WantsTemporalUpscaleDepth() const {
+ // FSR 3.1 replaces DLSS; the temporal stand-in only runs when FSR cannot (no depth, a device
+ // without the formats, a failed start). EDEN_NX2_DLSS_REPLACEMENT=standin forces
+ // the stand-in for A/B runs.
+ static const bool force_standin = [] {
+ const char* const value = std::getenv("EDEN_NX2_DLSS_REPLACEMENT");
+ return value && std::string_view{value} == "standin";
+ }();
+ return !force_standin && !fsr3_unsupported;
+}
+
+bool TextureCacheRuntime::UpscaleWithFsr3(Framebuffer* dst_framebuffer, ImageView& input_color,
+ ImageView& motion_vectors, ImageView& depth,
+ std::array<u32, 2> render_size,
std::array<f32, 2> jitter,
std::array<f32, 2> motion_scale) {
- if (dst.Samples() != VK_SAMPLE_COUNT_1_BIT || ImageAspectMask(dst.format) != VK_IMAGE_ASPECT_COLOR_BIT) {
+ if (!WantsTemporalUpscaleDepth()) {
return false;
}
- const VkFormat format =
- MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, dst.format).format;
- const VkExtent2D extent{dst.size.width, dst.size.height};
- const VkImage output = dst.ImageHandle();
- bool reset = false;
- if (!temporal_history.image || temporal_history.format != format ||
- temporal_history.extent.width != extent.width ||
- temporal_history.extent.height != extent.height) {
- if (temporal_history.image) {
- scheduler.Finish(); // the old history may still be sampled
+ if (!fsr3_upscaler) {
+ if (!Fsr3Upscaler::IsSupported(device)) {
+ LOG_WARNING(Render_Vulkan, "FSR 3.1 needs formats or features this device lacks; "
+ "using the temporal stand-in");
+ fsr3_unsupported = true;
+ return false;
}
- temporal_history.image = memory_allocator.CreateImage(VkImageCreateInfo{
- .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .imageType = VK_IMAGE_TYPE_2D,
- .format = format,
- .extent = {extent.width, extent.height, 1},
- .mipLevels = 1,
- .arrayLayers = 1,
- .samples = VK_SAMPLE_COUNT_1_BIT,
- .tiling = VK_IMAGE_TILING_OPTIMAL,
- .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
- .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
- .queueFamilyIndexCount = 0,
- .pQueueFamilyIndices = nullptr,
- .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
- });
- temporal_history.view = device.GetLogical().CreateImageView(VkImageViewCreateInfo{
- .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ try {
+ fsr3_upscaler = std::make_unique<Fsr3Upscaler>(device, scheduler, memory_allocator,
+ staging_buffer_pool, descriptor_pool,
+ compute_pass_descriptor_queue);
+ } catch (const vk::Exception& exception) {
+ LOG_ERROR(Render_Vulkan, "FSR 3.1 failed to start ({}); using the temporal stand-in",
+ exception.what());
+ fsr3_unsupported = true;
+ return false;
+ }
+ }
+ const VkExtent2D extent = dst_framebuffer->RenderArea();
+ // A gap in the evaluations (a menu, a movie, a load) starts the history again.
+ const auto now = std::chrono::steady_clock::now();
+ const f32 frame_time_ms =
+ fsr3_last_evaluation
+ ? std::chrono::duration<f32, std::milli>(now - *fsr3_last_evaluation).count()
+ : 1000.0f / 30.0f;
+ const bool reset = !fsr3_last_evaluation || frame_time_ms > 250.0f;
+ fsr3_last_evaluation = now;
+ // FSR's own RCAS, set by the sharpness slider: FSR takes 0-1 for 2 to 0 stops, and the slider
+ // stores stops x 100, so this is the percentage the slider shows; 0% (stored 200) is off.
+ // EDEN_NX2_TEMPORAL_SHARPEN=<stops>|off overrides the slider.
+ static const char* const sharpen_override = std::getenv("EDEN_NX2_TEMPORAL_SHARPEN");
+ const int slider = Settings::values.fsr_sharpening_slider.GetValue();
+ bool sharpen = slider < 200;
+ f32 stops = static_cast<f32>(slider) / 100.0f;
+ if (sharpen_override) {
+ sharpen = std::string_view{sharpen_override} != "off";
+ stops = static_cast<f32>(std::strtod(sharpen_override, nullptr));
+ }
+ fsr3_upscaler->Upscale({
+ .color = input_color.Handle(Shader::TextureType::Color2D),
+ .depth = depth.DepthView(),
+ .motion_vectors = motion_vectors.Handle(Shader::TextureType::Color2D),
+ .render_size = {render_size[0], render_size[1]},
+ .upscale_size = extent,
+ .jitter = jitter,
+ .motion_vector_scale = {motion_scale[0] * static_cast<f32>(render_size[0]),
+ motion_scale[1] * static_cast<f32>(render_size[1])},
+ .frame_time_ms = frame_time_ms,
+ .reset = reset,
+ .sharpen = sharpen,
+ .sharpness = 1.0f - stops / 2.0f,
+ });
+ // Draw the result into the DLSS output, which keeps its own format; the blit leaves FSR's
+ // output image in SHADER_READ_ONLY_OPTIMAL, so return it to GENERAL for the next frame.
+ const Region2D region{
+ .start = {0, 0},
+ .end = {static_cast<s32>(extent.width), static_cast<s32>(extent.height)},
+ };
+ const VkImage output = fsr3_upscaler->OutputImage();
+ blit_image_helper.BlitColor(dst_framebuffer, fsr3_upscaler->OutputView(), output,
+ fsr3_upscaler->PointSampler(), region, region,
+ {extent.width, extent.height, 1});
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([output](vk::CommandBuffer cmdbuf) {
+ const VkImageMemoryBarrier barrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
- .flags = 0,
- .image = *temporal_history.image,
- .viewType = VK_IMAGE_VIEW_TYPE_2D,
- .format = format,
- .components{},
+ .srcAccessMask = VK_ACCESS_SHADER_READ_BIT,
+ .dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = output,
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
@@ -1585,7 +1637,82 @@ bool TextureCacheRuntime::TemporalUpscale(Framebuffer* dst_framebuffer, ImageVie
.baseArrayLayer = 0,
.layerCount = 1,
},
- });
+ };
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+ VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, barrier);
+ });
+ static bool logged{};
+ if (!logged) {
+ logged = true;
+ LOG_INFO(Render_Vulkan, "DLSS replaced by FSR 3.1: {}x{} -> {}x{}", render_size[0],
+ render_size[1], extent.width, extent.height);
+ }
+ return true;
+}
+
+bool TextureCacheRuntime::TemporalUpscale(Framebuffer* dst_framebuffer, ImageView& dst,
+ ImageView& input_color, ImageView& motion_vectors,
+ ImageView* depth, std::array<u32, 2> render_size,
+ std::array<f32, 2> jitter,
+ std::array<f32, 2> motion_scale) {
+ if (dst.Samples() != VK_SAMPLE_COUNT_1_BIT || ImageAspectMask(dst.format) != VK_IMAGE_ASPECT_COLOR_BIT) {
+ return false;
+ }
+ if (depth && UpscaleWithFsr3(dst_framebuffer, input_color, motion_vectors, *depth, render_size,
+ jitter, motion_scale)) {
+ return true;
+ }
+ const VkFormat format =
+ MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, dst.format).format;
+ // The framebuffer's area is the output's size on the host (scaled with resolution scaling).
+ const VkExtent2D extent = dst_framebuffer->RenderArea();
+ // The history is kept at half-float precision: in the output's format (B10G11R11 here, 5-6
+ // mantissa bits) a frame's small share of the blend rounded away, so the history never
+ // converged on the detail the jittered frames add.
+ constexpr VkFormat history_format = VK_FORMAT_R16G16B16A16_SFLOAT;
+ bool reset = false;
+ if (!temporal_history.images[0] || temporal_history.format != format ||
+ temporal_history.extent.width != extent.width ||
+ temporal_history.extent.height != extent.height) {
+ if (temporal_history.images[0]) {
+ scheduler.Finish(); // the old history may still be in use
+ }
+ for (size_t i = 0; i < temporal_history.images.size(); ++i) {
+ temporal_history.images[i] = memory_allocator.CreateImage(VkImageCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = history_format,
+ .extent = {extent.width, extent.height, 1},
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ });
+ temporal_history.views[i] = device.GetLogical().CreateImageView(VkImageViewCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .image = *temporal_history.images[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = history_format,
+ .components{},
+ .subresourceRange{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ });
+ }
+ temporal_history.current = 0;
temporal_history.format = format;
temporal_history.extent = extent;
reset = true;
@@ -1593,24 +1720,15 @@ bool TextureCacheRuntime::TemporalUpscale(Framebuffer* dst_framebuffer, ImageVie
static_cast<u32>(format));
}
- // The history holds this pass's previous result, copied right after drawing it: the game may
- // draw into its DLSS output afterwards (post-processing), which must not feed back. The history
- // stays in the GENERAL layout, which the blit helper's descriptors use.
- const VkImage history = *temporal_history.image;
- const VkImageSubresourceLayers output_layers{
- .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
- .mipLevel = static_cast<u32>(dst.range.base.level),
- .baseArrayLayer = static_cast<u32>(dst.range.base.layer),
- .layerCount = 1,
- };
- static constexpr VkImageSubresourceLayers history_layers{
- .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
- .mipLevel = 0,
- .baseArrayLayer = 0,
- .layerCount = 1,
- };
+ // The shader reads the previous result from one history image and stores this one to the
+ // other: the game may draw into its DLSS output afterwards (post-processing), which must not
+ // feed back. Both stay in the GENERAL layout, which the blit helper's descriptors use.
+ const size_t read_index = temporal_history.current;
+ const size_t write_index = read_index ^ 1;
+ const VkImage history = *temporal_history.images[read_index];
+ const VkImage next_history = *temporal_history.images[write_index];
const auto barrier = [](VkImage image, VkAccessFlags src_access, VkAccessFlags dst_access,
- VkImageLayout old_layout, const VkImageSubresourceLayers& layers) {
+ VkImageLayout old_layout) {
return VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
@@ -1623,20 +1741,24 @@ bool TextureCacheRuntime::TemporalUpscale(Framebuffer* dst_framebuffer, ImageVie
.image = image,
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
- .baseMipLevel = layers.mipLevel,
+ .baseMipLevel = 0,
.levelCount = 1,
- .baseArrayLayer = layers.baseArrayLayer,
+ .baseArrayLayer = 0,
.layerCount = 1,
},
};
};
if (reset) {
- // A new history image has no contents yet; the first frame does not read it.
+ // New history images have no contents yet; the first frame does not use what it reads.
scheduler.RequestOutsideRenderPassOperationContext();
- scheduler.Record([history, barrier](vk::CommandBuffer cmdbuf) {
- cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
- 0, barrier(history, 0, VK_ACCESS_SHADER_READ_BIT,
- VK_IMAGE_LAYOUT_UNDEFINED, history_layers));
+ scheduler.Record([history, next_history, barrier](vk::CommandBuffer cmdbuf) {
+ constexpr VkAccessFlags access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
+ const std::array barriers{
+ barrier(history, 0, access, VK_IMAGE_LAYOUT_UNDEFINED),
+ barrier(next_history, 0, access, VK_IMAGE_LAYOUT_UNDEFINED),
+ };
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, {}, {}, barriers);
});
}
const Region2D dst_region{
@@ -1644,42 +1766,79 @@ bool TextureCacheRuntime::TemporalUpscale(Framebuffer* dst_framebuffer, ImageVie
.end = {static_cast<s32>(extent.width), static_cast<s32>(extent.height)},
};
// Each frame weighs at most 0.1 against the history (scaled by how close its samples land to
- // the pixel; the neighbourhood clip limits what older samples contribute).
- constexpr f32 current_weight = 0.1f;
+ // the pixel; the neighbourhood clip limits what older samples contribute). Still pixels use a
+ // sample kernel a third as wide as for moving ones (the jittered frames fill in the detail) and
+ // a clip of 1.5 deviations instead of 1.
+ // EDEN_NX2_TEMPORAL_WEIGHT / _KERNEL / _CLIP / _CLIP_MOTION / _KERNEL_MOTION
+ // override them (all 1 = the previous stand-in's filter).
+ const auto tuned = [](const char* name, f32 fallback) {
+ const char* value = std::getenv(name);
+ return value ? static_cast<f32>(std::strtod(value, nullptr)) : fallback;
+ };
+ static const f32 current_weight = tuned("EDEN_NX2_TEMPORAL_WEIGHT", 0.1f);
+ static const f32 kernel_scale = tuned("EDEN_NX2_TEMPORAL_KERNEL", 3.0f);
+ static const f32 clip_width = tuned("EDEN_NX2_TEMPORAL_CLIP", 1.5f);
+ static const f32 clip_motion = tuned("EDEN_NX2_TEMPORAL_CLIP_MOTION", 1.0f);
+ static const f32 kernel_motion = tuned("EDEN_NX2_TEMPORAL_KERNEL_MOTION", 1.0f);
blit_image_helper.TemporalUpscale(dst_framebuffer, input_color, motion_vectors,
- *temporal_history.view, dst_region, jitter, motion_scale,
- reset ? 1.0f : current_weight);
+ *temporal_history.views[read_index],
+ *temporal_history.views[write_index], dst_region, jitter,
+ motion_scale, reset ? 1.0f : current_weight, kernel_scale,
+ clip_width, clip_motion, kernel_motion);
+ // RCAS on the output, set by the FSR sharpness slider (stops as FSR takes them, 0% = stored 200
+ // turns it off). It reads the unsharpened result just stored, which stays the history.
+ // EDEN_NX2_TEMPORAL_SHARPEN=<stops>|off overrides the slider.
+ static const char* const sharpen_override = std::getenv("EDEN_NX2_TEMPORAL_SHARPEN");
+ const int slider = Settings::values.fsr_sharpening_slider.GetValue();
+ bool sharpen = slider < 200;
+ f32 sharpness = static_cast<f32>(slider) / 100.0f;
+ if (sharpen_override) {
+ sharpen = std::string_view{sharpen_override} != "off";
+ sharpness = static_cast<f32>(std::strtod(sharpen_override, nullptr));
+ }
+ const VkImage output = dst.ImageHandle();
+ const u32 output_level = static_cast<u32>(dst.range.base.level);
+ const u32 output_layer = static_cast<u32>(dst.range.base.layer);
scheduler.RequestOutsideRenderPassOperationContext();
- scheduler.Record([output, history, extent, output_layers, barrier](vk::CommandBuffer cmdbuf) {
- constexpr VkAccessFlags written = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
- const std::array before{
- barrier(output, written, VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_LAYOUT_GENERAL,
- output_layers),
- barrier(history, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
- VK_IMAGE_LAYOUT_GENERAL, history_layers),
- };
- cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
- VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
- VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, {}, before);
- const VkImageCopy copy{
- .srcSubresource = output_layers,
- .srcOffset = {0, 0, 0},
- .dstSubresource = history_layers,
- .dstOffset = {0, 0, 0},
- .extent = {extent.width, extent.height, 1},
- };
- cmdbuf.CopyImage(output, VK_IMAGE_LAYOUT_GENERAL, history, VK_IMAGE_LAYOUT_GENERAL, copy);
- const std::array after{
- barrier(output, VK_ACCESS_TRANSFER_READ_BIT,
- VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
- VK_ACCESS_TRANSFER_READ_BIT,
- VK_IMAGE_LAYOUT_GENERAL, output_layers),
- barrier(history, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
- VK_IMAGE_LAYOUT_GENERAL, history_layers),
+ scheduler.Record([history, next_history, barrier, output, output_level, output_layer,
+ sharpen](vk::CommandBuffer cmdbuf) {
+ // The next frame reads what was stored now and stores over what was read now; the
+ // sharpening pass reads what was stored now and draws over the output again.
+ const std::array barriers{
+ barrier(next_history, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
+ VK_IMAGE_LAYOUT_GENERAL),
+ barrier(history, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT,
+ VK_IMAGE_LAYOUT_GENERAL),
+ VkImageMemoryBarrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = output,
+ .subresourceRange{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = output_level,
+ .levelCount = 1,
+ .baseArrayLayer = output_layer,
+ .layerCount = 1,
+ },
+ },
};
- cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0,
- {}, {}, after);
+ constexpr VkPipelineStageFlags stages =
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ cmdbuf.PipelineBarrier(stages, stages, 0, {}, {},
+ vk::Span<VkImageMemoryBarrier>(barriers.data(),
+ sharpen ? barriers.size() : 2));
});
+ if (sharpen) {
+ blit_image_helper.TemporalSharpen(dst_framebuffer, *temporal_history.views[write_index],
+ dst_region, sharpness);
+ }
+ temporal_history.current = write_index;
return true;
}
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 564b2cbf9e..e7dbb97628 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -6,13 +6,17 @@
#pragma once
+#include <chrono>
#include <deque>
+#include <memory>
+#include <optional>
#include <span>
#include "video_core/texture_cache/texture_cache_base.h"
#include "shader_recompiler/shader_info.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h"
+#include "video_core/renderer_vulkan/vk_fsr3_upscaler.h"
#include "video_core/renderer_vulkan/vk_render_pass_cache.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/texture_cache/image_view_base.h"
@@ -99,12 +103,17 @@ public:
Tegra::Engines::Fermi2D::Filter filter,
Tegra::Engines::Fermi2D::Operation operation);
- /// Stands in for a DLSS evaluation: temporal upscale of input_color into dst, accumulated with
- /// a copy of dst's previous contents (the last output). False when dst cannot take it.
+ /// Stands in for a DLSS evaluation: temporal upscale of input_color (render_size on the host)
+ /// into dst, with FSR 3.1 when given the depth, else with the temporal stand-in. False when dst
+ /// cannot take it.
bool TemporalUpscale(Framebuffer* dst_framebuffer, ImageView& dst, ImageView& input_color,
- ImageView& motion_vectors, std::array<f32, 2> jitter,
+ ImageView& motion_vectors, ImageView* depth,
+ std::array<u32, 2> render_size, std::array<f32, 2> jitter,
std::array<f32, 2> motion_scale);
+ /// True when TemporalUpscale would use the depth (FSR 3.1 not forced off and not failed).
+ [[nodiscard]] bool WantsTemporalUpscaleDepth() const;
+
void CopyImage(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies);
void CopyImageMSAA(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies);
@@ -187,11 +196,24 @@ public:
StagingBufferPool& staging_buffer_pool;
BlitImageHelper& blit_image_helper;
RenderPassCache& render_pass_cache;
+ DescriptorPool& descriptor_pool;
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue;
+
+ /// FSR 3.1 in place of a DLSS evaluation (TemporalUpscale), created on first use.
+ bool UpscaleWithFsr3(Framebuffer* dst_framebuffer, ImageView& input_color,
+ ImageView& motion_vectors, ImageView& depth,
+ std::array<u32, 2> render_size, std::array<f32, 2> jitter,
+ std::array<f32, 2> motion_scale);
+ std::unique_ptr<Fsr3Upscaler> fsr3_upscaler;
+ bool fsr3_unsupported{};
+ std::optional<std::chrono::steady_clock::time_point> fsr3_last_evaluation;
- /// TemporalUpscale's history: its previous result, copied right after drawing it.
+ /// TemporalUpscale's history: its previous result (images[current]) and the image the next
+ /// result is stored to.
struct TemporalHistory {
- vk::Image image;
- vk::ImageView view;
+ std::array<vk::Image, 2> images;
+ std::array<vk::ImageView, 2> views;
+ size_t current{};
VkFormat format{};
VkExtent2D extent{};
} temporal_history;
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 4929c2df44..abb22b9bfb 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -1098,6 +1098,7 @@ template <class P>
bool TextureCache<P>::TemporalUpscale(const Tegra::Engines::Fermi2D::Surface& dst,
const Tegra::Engines::Fermi2D::Surface& src,
const Tegra::Engines::Fermi2D::Surface& motion_vectors,
+ const Tegra::Texture::TICEntry* depth,
std::array<f32, 2> jitter, std::array<f32, 2> motion_scale) {
if constexpr (FRAMEBUFFER_BLITS) {
return false;
@@ -1123,6 +1124,12 @@ bool TextureCache<P>::TemporalUpscale(const Tegra::Engines::Fermi2D::Surface& ds
has_deleted_images = false;
const ImageId motion_id =
FindOrInsertImage(motion_info, motion_vectors.Address(), RelaxedOptions{});
+ // The depth only feeds FSR 3.1 (looked up only when FSR can run); without it the stand-in
+ // runs.
+ ImageId depth_id{};
+ if (depth && motion_id && runtime.WantsTemporalUpscaleDepth()) {
+ depth_id = FindOrInsertImage(ImageInfo(*depth), depth->Address(), RelaxedOptions{});
+ }
if (has_deleted_images || !motion_id) {
return false; // the colour and output ids may be stale; the caller blits instead
}
@@ -1131,12 +1138,47 @@ bool TextureCache<P>::TemporalUpscale(const Tegra::Engines::Fermi2D::Surface& ds
PrepareImage(src_id, false, false);
PrepareImage(motion_id, false, false);
PrepareImage(dst_id, true, false);
+ if (depth_id) {
+ PrepareImage(depth_id, false, false);
+ if (slot_images[depth_id].info.num_samples != 1 ||
+ slot_images[depth_id].info.format != PixelFormat::D32_FLOAT) {
+ depth_id = {};
+ }
+ }
for (const ImageId id : {src_id, motion_id, dst_id}) {
- if (True(slot_images[id].flags & ImageFlagBits::Rescaled) ||
- slot_images[id].info.num_samples != 1) {
+ if (slot_images[id].info.num_samples != 1) {
return false;
}
}
+ // With resolution scaling, bring the images that can follow to the same scale, as BlitImage
+ // does; the stand-in reads each image at its own size, so any left behind still work.
+ const auto rescaled = [&](ImageId id) {
+ return True(slot_images[id].flags & ImageFlagBits::Rescaled);
+ };
+ if (rescaled(src_id) || rescaled(motion_id) || rescaled(dst_id) ||
+ (depth_id && rescaled(depth_id))) {
+ for (const ImageId id : {src_id, motion_id, dst_id, depth_id}) {
+ if (id && !rescaled(id) && ImageCanRescale(slot_images[id])) {
+ ScaleUp(slot_images[id]);
+ }
+ }
+ }
+ // FSR 3.1 reads colour, motion and depth texel for texel: all at the colour's scale.
+ if (depth_id && (rescaled(depth_id) != rescaled(src_id) ||
+ rescaled(motion_id) != rescaled(src_id))) {
+ depth_id = {};
+ }
+ const Extent3D src_size = slot_images[src_id].info.size;
+ const std::array<u32, 2> render_size =
+ rescaled(src_id)
+ ? std::array{Settings::values.resolution_info.ScaleUp(src_size.width),
+ Settings::values.resolution_info.ScaleUp(src_size.height)}
+ : std::array{src_size.width, src_size.height};
+ // The jitter is in the input's guest pixels.
+ if (rescaled(src_id)) {
+ const f32 factor = Settings::values.resolution_info.up_factor;
+ jitter = {jitter[0] * factor, jitter[1] * factor};
+ }
const auto view = [&](ImageId id, const Tegra::Engines::Fermi2D::Surface& surface,
PixelFormat format) -> std::optional<std::pair<FramebufferId, ImageViewId>> {
const std::optional base = slot_images[id].TryFindBase(surface.Address());
@@ -1152,12 +1194,21 @@ bool TextureCache<P>::TemporalUpscale(const Tegra::Engines::Fermi2D::Surface& ds
if (!src_view || !motion_view || !dst_view) {
return false;
}
+ ImageViewId depth_view{};
+ if (depth_id) {
+ if (const std::optional base = slot_images[depth_id].TryFindBase(depth->Address())) {
+ const SubresourceRange range{.base = *base, .extent = {1, 1}};
+ depth_view = FindOrEmplaceImageView(
+ depth_id, ImageViewInfo(ImageViewType::e2D, PixelFormat::D32_FLOAT, range));
+ }
+ }
// After the views: creating them may reallocate the framebuffer slots.
Framebuffer* const dst_framebuffer = &slot_framebuffers[dst_view->first];
- return runtime.TemporalUpscale(dst_framebuffer, slot_image_views[dst_view->second],
- slot_image_views[src_view->second],
- slot_image_views[motion_view->second], jitter,
- motion_scale);
+ return runtime.TemporalUpscale(
+ dst_framebuffer, slot_image_views[dst_view->second], slot_image_views[src_view->second],
+ slot_image_views[motion_view->second],
+ depth_view ? &slot_image_views[depth_view] : nullptr, render_size, jitter,
+ motion_scale);
}
}
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h
index 09deb8da02..5847cae7ba 100644
--- a/src/video_core/texture_cache/texture_cache_base.h
+++ b/src/video_core/texture_cache/texture_cache_base.h
@@ -288,7 +288,8 @@ public:
bool TemporalUpscale(const Tegra::Engines::Fermi2D::Surface& dst,
const Tegra::Engines::Fermi2D::Surface& src,
const Tegra::Engines::Fermi2D::Surface& motion_vectors,
- std::array<f32, 2> jitter, std::array<f32, 2> motion_scale);
+ const Tegra::Texture::TICEntry* depth, std::array<f32, 2> jitter,
+ std::array<f32, 2> motion_scale);
/// Try to find a cached image view in the given CPU address
[[nodiscard]] std::pair<ImageView*, bool> TryFindFramebufferImageView(
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 5f75bcc18e..edc091318c 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -561,6 +561,11 @@ FN_MAX_LIMIT_LIST
return features.features.shaderStorageImageReadWithoutFormat;
}
+ /// Returns true if storage images of the extended formats (e.g. R16F, RG16F, R8) are supported.
+ bool IsStorageImageExtendedFormatsSupported() const {
+ return features.features.shaderStorageImageExtendedFormats;
+ }
+
/// Returns true if shader int64 is supported.
bool IsShaderInt64Supported() const {
return features.features.shaderInt64;