diff --git a/.gitmodules b/.gitmodules
index ae8c7aef11bba6c98adc54f5f9899f3255125196..3fe8d1304b6b6087300a26607d3533b6a2be1a78 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -40,3 +40,6 @@
 [submodule "modules/algorithm/lib/FidelityFX-SPD"]
 	path = modules/algorithm/lib/FidelityFX-SPD
 	url = https://github.com/GPUOpen-Effects/FidelityFX-SPD.git
+[submodule "modules/upscaling/lib/FidelityFX-FSR2"]
+	path = modules/upscaling/lib/FidelityFX-FSR2
+	url = https://github.com/TheJackiMonster/FidelityFX-FSR2.git
diff --git a/include/vkcv/FeatureManager.hpp b/include/vkcv/FeatureManager.hpp
index f2138f6f3b7c5f639d42e98a83991704df0eb352..9ccc9880e807529bb6eb392515973d51f3aa6e37 100644
--- a/include/vkcv/FeatureManager.hpp
+++ b/include/vkcv/FeatureManager.hpp
@@ -325,6 +325,26 @@ namespace vkcv {
 		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceVulkan13Features &features,
 										bool required) const;
 		
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceCoherentMemoryFeaturesAMD.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceCoherentMemoryFeaturesAMD &features,
+										bool required) const;
+		
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceSubgroupSizeControlFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceSubgroupSizeControlFeatures &features,
+										bool required) const;
+		
 		/**
 		 * @brief Checks support of the @p vk::PhysicalDeviceIndexTypeUint8FeaturesEXT.
 		 *
diff --git a/modules/upscaling/CMakeLists.txt b/modules/upscaling/CMakeLists.txt
index 47a963d43f0444216a7a972987a7d584333a1fc8..4578f97fd654a5cad0e66549fbc944ab16051b11 100644
--- a/modules/upscaling/CMakeLists.txt
+++ b/modules/upscaling/CMakeLists.txt
@@ -20,6 +20,9 @@ set(vkcv_upscaling_sources
 		
 		${vkcv_upscaling_include}/vkcv/upscaling/NISUpscaling.hpp
 		${vkcv_upscaling_source}/vkcv/upscaling/NISUpscaling.cpp
+		
+		${vkcv_upscaling_include}/vkcv/upscaling/FSR2Upscaling.hpp
+		${vkcv_upscaling_source}/vkcv/upscaling/FSR2Upscaling.cpp
 )
 
 # Setup some path variables to load libraries
@@ -29,6 +32,9 @@ set(vkcv_upscaling_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_upscaling_lib})
 # Check and load FidelityFX_FSR
 include(config/FidelityFX_FSR.cmake)
 
+# Check and load FidelityFX_FSR2
+include(config/FidelityFX_FSR2.cmake)
+
 # Check and load NVIDIAImageScaling
 include(config/NVIDIAImageScaling.cmake)
 
diff --git a/modules/upscaling/README.md b/modules/upscaling/README.md
index 57f6dbdc8a9457982580170755a751fee74cefa6..9c4bed679f5a7a756e8b25edc85360672f8fabd0 100644
--- a/modules/upscaling/README.md
+++ b/modules/upscaling/README.md
@@ -6,10 +6,11 @@ A VkCV module to upscale images in realtime
 
 ### Dependencies (required):
 
-| Name of dependency | Used as submodule |
-|----------------------------------------------------|---|
-| [FidelityFX-FSR](https://github.com/GPUOpen-Effects/FidelityFX-FSR/)   | ✅ |
-| [NVIDIAImageScaling](https://github.com/NVIDIAGameWorks/NVIDIAImageScaling/)   | ✅ |
+| Name of dependency                                                           | Used as submodule |
+|------------------------------------------------------------------------------|---|
+| [FidelityFX-FSR](https://github.com/GPUOpen-Effects/FidelityFX-FSR/)         | ✅ |
+| [NVIDIAImageScaling](https://github.com/NVIDIAGameWorks/NVIDIAImageScaling/) | ✅ |
+| [FidelityFX-FSR2](https://github.com/GPUOpen-Effects/FidelityFX-FSR2/)       | ✅ |
 
 ## Docs
 
diff --git a/modules/upscaling/config/FidelityFX_FSR2.cmake b/modules/upscaling/config/FidelityFX_FSR2.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..3dbc43e6721ae139f9ceb76c9f05b6fbce010fba
--- /dev/null
+++ b/modules/upscaling/config/FidelityFX_FSR2.cmake
@@ -0,0 +1,14 @@
+
+use_git_submodule("${vkcv_upscaling_lib_path}/FidelityFX-FSR2" ffx_fsr2_status)
+
+if (${ffx_fsr2_status})
+	set(FFX_FSR2_API_DX12 OFF CACHE INTERNAL "")
+	set(FFX_FSR2_API_VK ON CACHE INTERNAL "")
+	
+	add_subdirectory(${vkcv_upscaling_lib}/FidelityFX-FSR2/src/ffx-fsr2-api)
+	
+	list(APPEND vkcv_upscaling_libraries ${FFX_FSR2_API} ${FFX_FSR2_API_VK})
+	
+	list(APPEND vkcv_upscaling_includes ${vkcv_upscaling_lib}/FidelityFX-FSR2/src/ffx-fsr2-api)
+	list(APPEND vkcv_upscaling_includes ${vkcv_upscaling_lib}/FidelityFX-FSR2/src/ffx-fsr2-api/vk)
+endif ()
diff --git a/modules/upscaling/include/vkcv/upscaling/BilinearUpscaling.hpp b/modules/upscaling/include/vkcv/upscaling/BilinearUpscaling.hpp
index 4f0bfea8499aaa04ce9d7503b47b08e4cd8cde26..a3f29276aa95e17e11ce2a044721d47d558dd869 100644
--- a/modules/upscaling/include/vkcv/upscaling/BilinearUpscaling.hpp
+++ b/modules/upscaling/include/vkcv/upscaling/BilinearUpscaling.hpp
@@ -23,7 +23,7 @@ namespace vkcv::upscaling {
 		explicit BilinearUpscaling(Core& core);
 
         /**
-         * Record the comands of the bilinear upscaling instance to
+         * Record the commands of the bilinear upscaling instance to
          * scale the image of the input handle to the resolution of
          * the output image handle via bilinear interpolation.
          *
diff --git a/modules/upscaling/include/vkcv/upscaling/FSR2Upscaling.hpp b/modules/upscaling/include/vkcv/upscaling/FSR2Upscaling.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..95dbd9a804ab6ec6a0232cafac7bc43dcf8b5d6d
--- /dev/null
+++ b/modules/upscaling/include/vkcv/upscaling/FSR2Upscaling.hpp
@@ -0,0 +1,235 @@
+#pragma once
+
+#include "Upscaling.hpp"
+
+#include <vector>
+
+struct FfxFsr2ContextDescription;
+struct FfxFsr2Context;
+
+namespace vkcv::upscaling {
+
+	/**
+     * @addtogroup vkcv_upscaling
+     * @{
+     */
+
+	/**
+     * Enum to set the mode of quality for
+     * FSR2 upscaling.
+     */
+	enum class FSR2QualityMode : int {
+		/**
+		 * Don't upscale anything.
+		 */
+		NONE = 0,
+		
+		/**
+		 * High quality of FSR upscaling:
+		 * 1.5x per dimension
+		 */
+		QUALITY = 2,
+		
+		/**
+		 * Medium quality of FSR upscaling:
+		 * 1.7x per dimension
+		 */
+		BALANCED = 3,
+		
+		/**
+		 * Low quality of FSR upscaling:
+		 * 2.0x per dimension
+		 */
+		PERFORMANCE = 4,
+		
+		/**
+         * Lowest quality of FSR upscaling:
+         * 3.0x per dimension
+         */
+		ULTRA_PERFORMANCE = 5,
+	};
+	
+	/**
+     * Calculates the internal resolution for actual rendering if
+     * a specific mode of quality is used for upscaling with FSR2.
+     *
+     * @param[in] mode Mode of quality
+     * @param[in] outputWidth Final resolution width
+     * @param[in] outputHeight Final resolution height
+     * @param[out] inputWidth Internal resolution width
+     * @param[out] inputHeight Internal resolution height
+     */
+	void getFSR2Resolution(FSR2QualityMode mode,
+						   uint32_t outputWidth, uint32_t outputHeight,
+						   uint32_t &inputWidth, uint32_t &inputHeight);
+	
+	/**
+	 * Returns the matching negative lod bias to reduce artifacts
+	 * upscaling with FSR2 under a given mode of quality.
+	 *
+	 * @param mode Mode of quality
+	 * @return Lod bias
+	 */
+	float getFSR2LodBias(FSR2QualityMode mode);
+
+	/**
+     * A class to handle upscaling via FidelityFX Super Resolution.
+     * https://github.com/GPUOpen-Effects/FidelityFX-FSR2
+     */
+	class FSR2Upscaling : public Upscaling {
+	private:
+		std::vector<char> m_scratchBuffer;
+		
+		std::unique_ptr<FfxFsr2ContextDescription> m_description;
+		std::unique_ptr<FfxFsr2Context> m_context;
+		
+		ImageHandle m_depth;
+		ImageHandle m_velocity;
+		
+		uint32_t m_frameIndex;
+		
+		float m_frameDeltaTime;
+		bool m_reset;
+		
+		float m_near;
+		float m_far;
+		float m_fov;
+		
+		/**
+		* Current state of HDR support.
+		*/
+		bool m_hdr;
+		
+		/**
+		 * Sharpness will improve the upscaled image quality with
+		 * a factor between 0.0f for no sharpening and 1.0f for
+		 * maximum sharpening.
+		 *
+		 * The default value for sharpness should be 0.875f.
+		 *
+		 * Beware that 0.0f or any negative value of sharpness will
+		 * disable the sharpening pass completely.
+		 */
+		float m_sharpness;
+		
+		void createFSR2Context(uint32_t displayWidth,
+							   uint32_t displayHeight,
+							   uint32_t renderWidth,
+							   uint32_t renderHeight);
+		
+		void destroyFSR2Context();
+		
+	public:
+		/**
+         * Constructor to create an instance for FSR upscaling.
+         *
+         * @param[in,out] core Reference to a Core instance
+         */
+		explicit FSR2Upscaling(Core& core);
+		
+		/**
+		 * Destructor to free the instance for FSR upscaling.
+		 */
+		~FSR2Upscaling();
+		
+		/**
+		 * Update the upscaling instance with current frame
+		 * delta time and whether the temporal data needs to
+		 * be reset (for example because the camera switched).
+		 *
+		 * @param[in] deltaTime Current frame delta time
+		 * @param[in] reset Reset temporal frame data
+		 */
+		void update(float deltaTime, bool reset = false);
+		
+		/**
+		 * Calculates the jitter offset for the projection
+		 * matrix of the camera to use in the current frame.
+		 *
+		 * @param[in] renderWidth Render resolution width
+		 * @param[in] renderHeight Render resolution height
+		 * @param[out] jitterOffsetX Jitter offset x-coordinate
+		 * @param[out] jitterOffsetY Jitter offset y-coordinate
+		 */
+		void calcJitterOffset(uint32_t renderWidth,
+							 uint32_t renderHeight,
+							 float& jitterOffsetX,
+							 float& jitterOffsetY) const;
+		
+		/**
+		 * Bind the depth buffer image to use with the FSR2
+		 * upscaling instance for utilizing depth information.
+		 *
+		 * @param[in] depthInput Depth input image handle
+		 */
+		void bindDepthBuffer(const ImageHandle& depthInput);
+		
+		/**
+		 * Bind the velocity buffer image to use with the FSR2
+		 * upscaling instance for utilizing 2D motion vectors.
+		 *
+		 * @param[in] velocityInput Velocity input image handle
+		 */
+		void bindVelocityBuffer(const ImageHandle& velocityInput);
+		
+		/**
+		 * Record the commands of the FSR2 upscaling instance to
+		 * scale the image of the input handle to the resolution of
+		 * the output image handle via FidelityFX Super Resolution.
+		 *
+		 * @param[in] cmdStream Command stream handle to record commands
+		 * @param[in] colorInput Color input image handle
+		 * @param[in] output Output image handle
+		 */
+		void recordUpscaling(const CommandStreamHandle& cmdStream,
+							 const ImageHandle& colorInput,
+							 const ImageHandle& output) override;
+		
+		/**
+		 * Set the required camera values for the FSR2 upscaling
+		 * instance including near- and far-plane as well as
+		 * the FOV angle vertical.
+		 *
+		 * @param[in] near Camera near plane
+		 * @param[in] far Camera far plane
+		 * @param[in] fov Camera field of view angle vertical
+		 */
+		void setCamera(float near, float far, float fov);
+		
+		/**
+         * Checks if HDR support is enabled and returns the status as boolean.
+         *
+         * @return true if HDR is supported, otherwise false
+         */
+		[[nodiscard]]
+		bool isHdrEnabled() const;
+		
+		/**
+		 * Changes the status of HDR support of the FSR upscaling instance.
+		 *
+		 * @param[in] enabled New status of HDR support
+		 */
+		void setHdrEnabled(bool enabled);
+		
+		/**
+         * Returns the amount of sharpness the FSR upscaling instance is using.
+         *
+         * @return The amount of sharpness
+         */
+		[[nodiscard]]
+		float getSharpness() const;
+		
+		/**
+		 * Changes the amount of sharpness of the FSR upscaling instance.
+		 * The new sharpness value is restricted by 0.0f as lower and 1.0f
+		 * as upper boundary.
+		 *
+		 * @param[in] sharpness New sharpness value
+		 */
+		void setSharpness(float sharpness);
+		
+	};
+	
+	/** @} */
+
+}
\ No newline at end of file
diff --git a/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp b/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp
index 64bc78aca4fe42ff813d05fc016a70c7108bd557..5a4c59ef02c2f17ed017535ca79b834e7391546a 100644
--- a/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp
+++ b/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp
@@ -44,7 +44,7 @@ namespace vkcv::upscaling {
          * Low quality of FSR upscaling:
          * 2.0x per dimension
          */
-		PERFORMANCE = 4
+		PERFORMANCE = 4,
 	};
 
     /**
@@ -188,7 +188,7 @@ namespace vkcv::upscaling {
 		explicit FSRUpscaling(Core& core);
 
         /**
-         * Record the comands of the FSR upscaling instance to
+         * Record the commands of the FSR upscaling instance to
          * scale the image of the input handle to the resolution of
          * the output image handle via FidelityFX Super Resolution.
          *
diff --git a/modules/upscaling/include/vkcv/upscaling/NISUpscaling.hpp b/modules/upscaling/include/vkcv/upscaling/NISUpscaling.hpp
index 913983400808f3353adf3a1c653045345d36c6f1..62fadf8332afd78ad7cb9dbe1eadda75de35d1c3 100644
--- a/modules/upscaling/include/vkcv/upscaling/NISUpscaling.hpp
+++ b/modules/upscaling/include/vkcv/upscaling/NISUpscaling.hpp
@@ -84,7 +84,7 @@ namespace vkcv::upscaling {
 		explicit NISUpscaling(Core &core);
 		
 		/**
-         * Record the comands of the NIS upscaling instance to
+         * Record the commands of the NIS upscaling instance to
          * scale the image of the input handle to the resolution of
          * the output image handle via NVIDIA Image Scaling.
          *
diff --git a/modules/upscaling/include/vkcv/upscaling/Upscaling.hpp b/modules/upscaling/include/vkcv/upscaling/Upscaling.hpp
index 4d04746f0849615a112d04827c7af41811031116..06e35208744734f903c8a48aa6abcd9478685c1e 100644
--- a/modules/upscaling/include/vkcv/upscaling/Upscaling.hpp
+++ b/modules/upscaling/include/vkcv/upscaling/Upscaling.hpp
@@ -32,7 +32,7 @@ namespace vkcv::upscaling {
 		~Upscaling() = default;
 
         /**
-         * Record the comands of the given upscaling instance to
+         * Record the commands of the given upscaling instance to
          * scale the image of the input handle to the resolution of
          * the output image handle.
          *
diff --git a/modules/upscaling/lib/FidelityFX-FSR2 b/modules/upscaling/lib/FidelityFX-FSR2
new file mode 160000
index 0000000000000000000000000000000000000000..0ce4ff5c5a0210273be7e3085bb4b15d0590431c
--- /dev/null
+++ b/modules/upscaling/lib/FidelityFX-FSR2
@@ -0,0 +1 @@
+Subproject commit 0ce4ff5c5a0210273be7e3085bb4b15d0590431c
diff --git a/modules/upscaling/src/vkcv/upscaling/FSR2Upscaling.cpp b/modules/upscaling/src/vkcv/upscaling/FSR2Upscaling.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0478596daef709ef8b5ae583221d402d36d76dba
--- /dev/null
+++ b/modules/upscaling/src/vkcv/upscaling/FSR2Upscaling.cpp
@@ -0,0 +1,349 @@
+
+#include "vkcv/upscaling/FSR2Upscaling.hpp"
+
+#include <cmath>
+
+#define FFX_GCC
+#include <ffx_fsr2.h>
+#include <ffx_fsr2_vk.h>
+#undef FFX_GCC
+
+namespace vkcv::upscaling {
+	
+	void getFSR2Resolution(FSR2QualityMode mode,
+						   uint32_t outputWidth, uint32_t outputHeight,
+						   uint32_t &inputWidth, uint32_t &inputHeight) {
+		float scale;
+		
+		switch (mode) {
+			case FSR2QualityMode::QUALITY:
+				scale = 1.5f;
+				break;
+			case FSR2QualityMode::BALANCED:
+				scale = 1.7f;
+				break;
+			case FSR2QualityMode::PERFORMANCE:
+				scale = 2.0f;
+				break;
+			case FSR2QualityMode::ULTRA_PERFORMANCE:
+				scale = 3.0f;
+				break;
+			default:
+				scale = 1.0f;
+				break;
+		}
+		
+		inputWidth = static_cast<uint32_t>(
+				std::round(static_cast<float>(outputWidth) / scale)
+		);
+		
+		inputHeight = static_cast<uint32_t>(
+				std::round(static_cast<float>(outputHeight) / scale)
+		);
+	}
+	
+	float getFSR2LodBias(FSR2QualityMode mode) {
+		switch (mode) {
+			case FSR2QualityMode::QUALITY:
+				return -1.58f;
+			case FSR2QualityMode::BALANCED:
+				return -1.76f;
+			case FSR2QualityMode::PERFORMANCE:
+				return -2.0f;
+			case FSR2QualityMode::ULTRA_PERFORMANCE:
+				return -2.58f;
+			default:
+				return 0.0f;
+		}
+	}
+	
+	void FSR2Upscaling::createFSR2Context(uint32_t displayWidth,
+									 uint32_t displayHeight,
+									 uint32_t renderWidth,
+									 uint32_t renderHeight) {
+		m_description->displaySize.width = displayWidth;
+		m_description->displaySize.height = displayHeight;
+		
+		m_description->maxRenderSize.width = renderWidth;
+		m_description->maxRenderSize.height = renderHeight;
+		
+		m_description->flags = FFX_FSR2_ENABLE_AUTO_EXPOSURE;
+		
+		if (m_hdr) {
+			m_description->flags |= FFX_FSR2_ENABLE_HIGH_DYNAMIC_RANGE;
+		}
+		
+		if ((m_description->displaySize.width * m_description->displaySize.height <= 1) ||
+			(m_description->maxRenderSize.width * m_description->maxRenderSize.height <= 1)) {
+			return;
+		}
+		
+		if (!m_context) {
+			m_context.reset(new FfxFsr2Context());
+		}
+		
+		memset(m_context.get(), 0, sizeof(*m_context));
+		assert(ffxFsr2ContextCreate(m_context.get(), m_description.get()) == FFX_OK);
+	}
+	
+	void FSR2Upscaling::destroyFSR2Context() {
+		m_core.getContext().getDevice().waitIdle();
+		
+		if (m_context) {
+			assert(ffxFsr2ContextDestroy(m_context.get()) == FFX_OK);
+			m_context.reset(nullptr);
+		}
+		
+		m_frameIndex = 0;
+	}
+	
+	FSR2Upscaling::FSR2Upscaling(Core &core) :
+	Upscaling(core),
+	m_scratchBuffer(),
+	
+	m_description(new FfxFsr2ContextDescription()),
+	m_context(nullptr),
+	
+	m_depth(),
+	m_velocity(),
+	
+	m_frameIndex(0),
+	
+	m_frameDeltaTime(0.0f),
+	m_reset(false),
+	
+	m_near(0.0f),
+	m_far(0.0f),
+	m_fov(0.0f),
+	
+	m_hdr(false),
+	m_sharpness(0.875f) {
+		const auto& physicalDevice = core.getContext().getPhysicalDevice();
+		
+		memset(m_description.get(), 0, sizeof(*m_description));
+		
+		m_scratchBuffer.resize(ffxFsr2GetScratchMemorySizeVK(physicalDevice));
+		
+		assert(ffxFsr2GetInterfaceVK(
+				&(m_description->callbacks),
+				m_scratchBuffer.data(),
+				m_scratchBuffer.size(),
+				physicalDevice,
+				vkGetDeviceProcAddr
+		) == FFX_OK);
+		
+		m_description->device = ffxGetDeviceVK(core.getContext().getDevice());
+		
+		createFSR2Context(1, 1, 1, 1);
+	}
+	
+	FSR2Upscaling::~FSR2Upscaling() {
+		destroyFSR2Context();
+		
+		m_scratchBuffer.clear();
+		m_description->callbacks.scratchBuffer = nullptr;
+	}
+	
+	void FSR2Upscaling::update(float deltaTime, bool reset) {
+		if (reset) {
+			m_frameIndex = 0;
+		}
+		
+		m_frameDeltaTime = deltaTime;
+		m_reset = reset;
+	}
+	
+	void FSR2Upscaling::calcJitterOffset(uint32_t renderWidth,
+										 uint32_t renderHeight,
+										 float &jitterOffsetX,
+										 float &jitterOffsetY) const {
+		const int32_t phaseCount = ffxFsr2GetJitterPhaseCount(
+				static_cast<int32_t>(renderWidth),
+				static_cast<int32_t>(renderHeight)
+		);
+		
+		const int32_t phaseIndex = (static_cast<int32_t>(m_frameIndex) % phaseCount);
+		
+		assert(ffxFsr2GetJitterOffset(
+				&jitterOffsetX,
+				&jitterOffsetY,
+				phaseIndex,
+				phaseCount
+		) == FFX_OK);
+		
+		jitterOffsetX *= +2.0f / renderWidth;
+		jitterOffsetY *= -2.0f / renderHeight;
+	}
+	
+	void FSR2Upscaling::bindDepthBuffer(const ImageHandle &depthInput) {
+		m_depth = depthInput;
+	}
+	
+	void FSR2Upscaling::bindVelocityBuffer(const ImageHandle &velocityInput) {
+		m_velocity = velocityInput;
+	}
+	
+	void FSR2Upscaling::recordUpscaling(const CommandStreamHandle &cmdStream,
+										const ImageHandle &colorInput,
+										const ImageHandle &output) {
+		m_core.recordBeginDebugLabel(cmdStream, "vkcv::upscaling::FSR2Upscaling", {
+				1.0f, 0.05f, 0.05f, 1.0f
+		});
+		
+		FfxFsr2DispatchDescription dispatch;
+		memset(&dispatch, 0, sizeof(dispatch));
+		
+		const uint32_t inputWidth = m_core.getImageWidth(colorInput);
+		const uint32_t inputHeight = m_core.getImageHeight(colorInput);
+		
+		const uint32_t outputWidth = m_core.getImageWidth(output);
+		const uint32_t outputHeight = m_core.getImageHeight(output);
+		
+		if ((m_description->displaySize.width != outputWidth) ||
+			(m_description->displaySize.height != outputHeight) ||
+			(m_description->maxRenderSize.width < inputWidth) ||
+			(m_description->maxRenderSize.height < inputHeight) ||
+			(m_hdr != ((m_description->flags & FFX_FSR2_ENABLE_HIGH_DYNAMIC_RANGE) != 0))) {
+			destroyFSR2Context();
+			
+			createFSR2Context(
+					outputWidth,
+					outputHeight,
+					inputWidth,
+					inputHeight
+			);
+		}
+		
+		if (m_context) {
+			const bool sharpeningEnabled = (
+					(m_sharpness > +0.0f) &&
+					((inputWidth < outputWidth) || (inputHeight < outputHeight))
+			);
+			
+			dispatch.color = ffxGetTextureResourceVK(
+					m_context.get(),
+					m_core.getVulkanImage(colorInput),
+					m_core.getVulkanImageView(colorInput),
+					inputWidth,
+					inputHeight,
+					static_cast<VkFormat>(m_core.getImageFormat(colorInput))
+			);
+			
+			dispatch.depth = ffxGetTextureResourceVK(
+					m_context.get(),
+					m_core.getVulkanImage(m_depth),
+					m_core.getVulkanImageView(m_depth),
+					m_core.getImageWidth(m_depth),
+					m_core.getImageHeight(m_depth),
+					static_cast<VkFormat>(m_core.getImageFormat(m_depth))
+			);
+			
+			dispatch.motionVectors = ffxGetTextureResourceVK(
+					m_context.get(),
+					m_core.getVulkanImage(m_velocity),
+					m_core.getVulkanImageView(m_velocity),
+					m_core.getImageWidth(m_velocity),
+					m_core.getImageHeight(m_velocity),
+					static_cast<VkFormat>(m_core.getImageFormat(m_velocity))
+			);
+			
+			dispatch.exposure = ffxGetTextureResourceVK(
+					m_context.get(),
+					nullptr,
+					nullptr,
+					1,
+					1,
+					VK_FORMAT_UNDEFINED
+			);
+			
+			dispatch.reactive = ffxGetTextureResourceVK(
+					m_context.get(),
+					nullptr,
+					nullptr,
+					1,
+					1,
+					VK_FORMAT_UNDEFINED
+			);
+			
+			dispatch.transparencyAndComposition = ffxGetTextureResourceVK(
+					m_context.get(),
+					nullptr,
+					nullptr,
+					1,
+					1,
+					VK_FORMAT_UNDEFINED
+			);
+			
+			dispatch.output = ffxGetTextureResourceVK(
+					m_context.get(),
+					m_core.getVulkanImage(output),
+					m_core.getVulkanImageView(output),
+					outputWidth,
+					outputHeight,
+					static_cast<VkFormat>(m_core.getImageFormat(output))
+			);
+			
+			calcJitterOffset(
+					inputWidth,
+					inputHeight,
+					dispatch.jitterOffset.x,
+					dispatch.jitterOffset.y
+			);
+			
+			dispatch.motionVectorScale.x = static_cast<float>(+2.0f);
+			dispatch.motionVectorScale.y = static_cast<float>(-2.0f);
+			
+			dispatch.renderSize.width = inputWidth;
+			dispatch.renderSize.height = inputHeight;
+			
+			dispatch.enableSharpening = sharpeningEnabled;
+			dispatch.sharpness = m_sharpness;
+			
+			dispatch.frameTimeDelta = m_frameDeltaTime * 1000.0f; // from seconds to milliseconds
+			dispatch.preExposure = 1.0f;
+			dispatch.reset = m_reset;
+			
+			dispatch.cameraNear = m_near;
+			dispatch.cameraFar = m_far;
+			dispatch.cameraFovAngleVertical = m_fov;
+			
+			m_core.recordCommandsToStream(cmdStream, [&](const vk::CommandBuffer& cmdBuffer) {
+				dispatch.commandList = ffxGetCommandListVK(cmdBuffer);
+				
+				assert(ffxFsr2ContextDispatch(
+						m_context.get(),
+						&dispatch
+				) == FFX_OK);
+				
+				m_frameIndex++;
+				m_reset = false;
+			}, nullptr);
+		}
+		
+		m_core.updateImageLayoutManual(output, vk::ImageLayout::eGeneral);
+		m_core.recordEndDebugLabel(cmdStream);
+	}
+	
+	void FSR2Upscaling::setCamera(float near, float far, float fov) {
+		m_near = near;
+		m_far = far;
+		m_fov = fov;
+	}
+	
+	bool FSR2Upscaling::isHdrEnabled() const {
+		return m_hdr;
+	}
+	
+	void FSR2Upscaling::setHdrEnabled(bool enabled) {
+		m_hdr = enabled;
+	}
+	
+	float FSR2Upscaling::getSharpness() const {
+		return m_sharpness;
+	}
+	
+	void FSR2Upscaling::setSharpness(float sharpness) {
+		m_sharpness = (sharpness < 0.0f ? 0.0f : (sharpness > 1.0f ? 1.0f : sharpness));
+	}
+	
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/CMakeLists.txt b/projects/indirect_dispatch/CMakeLists.txt
index 53b0d1b15dc2eeb32308e16ad81d476ed502d370..ad59d5b24efa1af1b387253b4c007fd25c50500f 100644
--- a/projects/indirect_dispatch/CMakeLists.txt
+++ b/projects/indirect_dispatch/CMakeLists.txt
@@ -25,7 +25,25 @@ target_sources(indirect_dispatch PRIVATE
     src/MotionBlurSetup.cpp)
 
 # including headers of dependencies and the VkCV framework
-target_include_directories(indirect_dispatch SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include} ${vkcv_gui_include})
+target_include_directories(indirect_dispatch SYSTEM BEFORE PRIVATE
+		${vkcv_include}
+		${vkcv_includes}
+		${vkcv_testing_include}
+		${vkcv_camera_include}
+		${vkcv_shader_compiler_include}
+		${vkcv_gui_include}
+		${vkcv_upscaling_include}
+)
 
 # linking with libraries from all dependencies and the VkCV framework
-target_link_libraries(indirect_dispatch vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_testing vkcv_camera vkcv_shader_compiler vkcv_gui)
\ No newline at end of file
+target_link_libraries(indirect_dispatch
+		vkcv
+		${vkcv_libraries}
+		vkcv_asset_loader
+		${vkcv_asset_loader_libraries}
+		vkcv_testing
+		vkcv_camera
+		vkcv_shader_compiler
+		vkcv_gui
+		vkcv_upscaling
+)
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/App.cpp b/projects/indirect_dispatch/src/App.cpp
index f17a3df647359018411e5bebcc3175295359678b..f871338af2670087eaf851901790631a4711f680 100644
--- a/projects/indirect_dispatch/src/App.cpp
+++ b/projects/indirect_dispatch/src/App.cpp
@@ -4,6 +4,11 @@
 #include <vkcv/Sampler.hpp>
 #include <vkcv/gui/GUI.hpp>
 
+#include <vkcv/upscaling/FSR2Upscaling.hpp>
+#include <vkcv/upscaling/FSRUpscaling.hpp>
+#include <vkcv/upscaling/NISUpscaling.hpp>
+#include <vkcv/upscaling/BilinearUpscaling.hpp>
+
 #include <chrono>
 #include <functional>
 
@@ -35,7 +40,6 @@ App::App() :
 	m_cameraManager(m_core.getWindow(m_windowHandle)){}
 
 bool App::initialize() {
-
 	if (!loadMeshPass(m_core, &m_meshPass))
 		return false;
 
@@ -64,7 +68,12 @@ bool App::initialize() {
 		return false;
 
 	m_linearSampler = vkcv::samplerLinear(m_core, true);
-	m_renderTargets = createRenderTargets(m_core, m_windowWidth, m_windowHeight);
+	m_renderTargets = createRenderTargets(
+			m_core,
+			m_windowWidth,
+			m_windowHeight,
+			vkcv::upscaling::FSR2QualityMode::NONE
+	);
 
 	auto cameraHandle = m_cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
 	m_cameraManager.getCamera(cameraHandle).setPosition(glm::vec3(0, 1, -3));
@@ -143,11 +152,46 @@ void App::run() {
 			}
 		}
 	});
-
+	
+	vkcv::upscaling::FSR2Upscaling fsr2 (m_core);
+	
+	fsr2.bindDepthBuffer(m_renderTargets.depthBuffer);
+	fsr2.bindVelocityBuffer(m_renderTargets.motionBuffer);
+	
+	vkcv::upscaling::FSR2QualityMode fsrMode = vkcv::upscaling::FSR2QualityMode::NONE;
+	vkcv::upscaling::FSR2QualityMode oldFsrMode = fsrMode;
+	
+	int fsrModeIndex = static_cast<int>(fsrMode);
+	
+	const std::vector<const char*> fsrModeNames = {
+			"None",
+			"Quality",
+			"Balanced",
+			"Performance",
+			"Ultra Performance"
+	};
+	
+	bool fsrMipLoadBiasFlag = true;
+	bool fsrMipLoadBiasFlagBackup = fsrMipLoadBiasFlag;
+	
+	vkcv::upscaling::FSRUpscaling fsr1 (m_core);
+	vkcv::upscaling::BilinearUpscaling bilinear (m_core);
+	vkcv::upscaling::NISUpscaling nis (m_core);
+	
+	const std::vector<const char*> modeNames = {
+			"FSR Upscaling 1.0",
+			"FSR Upscaling 2.1.1",
+			"NIS Upscaling",
+			"Bilinear Upscaling"
+	};
+	
+	int upscalingMode = 3;
+	
+	vkcv::SamplerHandle fsr2Sampler;
+	
 	auto frameEndTime = std::chrono::system_clock::now();
 
 	while (vkcv::Window::hasOpenWindow()) {
-
 		vkcv::Window::pollEvents();
 
 		if (!freezeFrame) {
@@ -167,13 +211,42 @@ void App::run() {
 		if (!m_core.beginFrame(swapchainWidth, swapchainHeight,m_windowHandle))
 			continue;
 
-		const bool hasResolutionChanged = (swapchainWidth != m_windowWidth) || (swapchainHeight != m_windowHeight);
+		const bool hasResolutionChanged = (
+				(swapchainWidth != m_windowWidth) ||
+				(swapchainHeight != m_windowHeight) ||
+				(oldFsrMode != fsrMode) ||
+				(fsrMipLoadBiasFlagBackup != fsrMipLoadBiasFlag)
+		);
+		
 		if (hasResolutionChanged) {
 			m_windowWidth  = swapchainWidth;
 			m_windowHeight = swapchainHeight;
-
-			m_renderTargets = createRenderTargets(m_core, m_windowWidth, m_windowHeight);
+			oldFsrMode = fsrMode;
+			fsrMipLoadBiasFlagBackup = fsrMipLoadBiasFlag;
+			
+			fsr2Sampler = m_core.createSampler(
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerMipmapMode::LINEAR,
+					vkcv::SamplerAddressMode::REPEAT,
+					fsrMipLoadBiasFlag? vkcv::upscaling::getFSR2LodBias(fsrMode) : 0.0f
+			);
+			
+			vkcv::DescriptorWrites meshPassDescriptorWrites;
+			meshPassDescriptorWrites.writeSampler(1, fsr2Sampler);
+			m_core.writeDescriptorSet(m_meshPass.descriptorSet, meshPassDescriptorWrites);
+
+			m_renderTargets = createRenderTargets(
+					m_core,
+					m_windowWidth,
+					m_windowHeight,
+					fsrMode
+			);
+			
 			m_motionBlur.setResolution(m_windowWidth, m_windowHeight);
+			
+			fsr2.bindDepthBuffer(m_renderTargets.depthBuffer);
+			fsr2.bindVelocityBuffer(m_renderTargets.motionBuffer);
 		}
 
 		if(!freezeFrame)
@@ -183,20 +256,41 @@ void App::run() {
 		const float fDeltaTimeSeconds = microsecondToSecond * std::chrono::duration_cast<std::chrono::microseconds>(frameEndTime - frameStartTime).count();
 
 		m_cameraManager.update(fDeltaTimeSeconds);
-
-		const auto      time                = frameEndTime - appStartTime;
-		const float     fCurrentTime        = std::chrono::duration_cast<std::chrono::milliseconds>(time).count() * 0.001f;
+		fsr2.update(fDeltaTimeSeconds, false);
+		
+		const auto& camera = m_cameraManager.getActiveCamera();
+		float near, far;
+		
+		camera.getNearFar(near, far);
+		fsr2.setCamera(near, far, camera.getFov());
+
+		const auto  time         = frameEndTime - appStartTime;
+		const float fCurrentTime = std::chrono::duration_cast<std::chrono::milliseconds>(time).count() * 0.001f;
+		
+		float jitterX, jitterY;
+		
+		fsr2.calcJitterOffset(
+				m_core.getImageWidth(m_renderTargets.colorBuffer),
+				m_core.getImageHeight(m_renderTargets.colorBuffer),
+				jitterX,
+				jitterY
+		);
+		
+		const glm::mat4 jitterMatrix = glm::translate(
+				glm::identity<glm::mat4>(),
+				glm::vec3(jitterX, jitterY, 0.0f)
+		);
 
 		// update matrices
 		if (!freezeFrame) {
-
-			viewProjection = m_cameraManager.getActiveCamera().getMVP();
+			viewProjection = camera.getMVP();
 
 			for (Object& obj : sceneObjects) {
 				if (obj.modelMatrixUpdate) {
 					obj.modelMatrixUpdate(fCurrentTime, obj);
 				}
-				obj.mvp = viewProjection * obj.modelMatrix;
+				
+				obj.mvp = jitterMatrix * viewProjection * obj.modelMatrix;
 			}
 		}
 
@@ -212,7 +306,8 @@ void App::run() {
 
 		const std::vector<vkcv::ImageHandle> prepassRenderTargets = {
 			m_renderTargets.motionBuffer,
-			m_renderTargets.depthBuffer };
+			m_renderTargets.depthBuffer
+		};
 
 		std::vector<vkcv::InstanceDrawcall> prepassSceneDrawcalls;
 		for (const Object& obj : sceneObjects) {
@@ -225,12 +320,15 @@ void App::run() {
 			prepassPushConstants,
 			prepassSceneDrawcalls,
 			prepassRenderTargets,
-			m_windowHandle);
+			m_windowHandle
+		);
 
 		// sky prepass
 		glm::mat4 skyPrepassMatrices[2] = {
 			viewProjection,
-			viewProjectionPrevious };
+			viewProjectionPrevious
+		};
+		
 		vkcv::PushConstants skyPrepassPushConstants(sizeof(glm::mat4) * 2);
 		skyPrepassPushConstants.appendDrawcall(skyPrepassMatrices);
 
@@ -240,12 +338,14 @@ void App::run() {
 			skyPrepassPushConstants,
 			{ skyDrawcall },
 			prepassRenderTargets,
-			m_windowHandle);
+			m_windowHandle
+		);
 
 		// main pass
 		const std::vector<vkcv::ImageHandle> renderTargets   = { 
 			m_renderTargets.colorBuffer, 
-			m_renderTargets.depthBuffer };
+			m_renderTargets.depthBuffer
+		};
 
 		vkcv::PushConstants meshPushConstants(2 * sizeof(glm::mat4));
 		for (const Object& obj : sceneObjects) {
@@ -266,11 +366,12 @@ void App::run() {
 			meshPushConstants,
 			forwardSceneDrawcalls,
 			renderTargets,
-			m_windowHandle);
+			m_windowHandle
+		);
 
 		// sky
 		vkcv::PushConstants skyPushConstants = vkcv::pushConstants<glm::mat4>();
-		skyPushConstants.appendDrawcall(viewProjection);
+		skyPushConstants.appendDrawcall(jitterMatrix * viewProjection);
 
 		m_core.recordDrawcallsToCmdStream(
 			cmdStream,
@@ -278,35 +379,82 @@ void App::run() {
 			skyPushConstants,
 			{ skyDrawcall },
 			renderTargets,
-			m_windowHandle);
+			m_windowHandle
+		);
+		
+		// upscaling
+		m_core.prepareImageForSampling(cmdStream, m_renderTargets.colorBuffer);
+		
+		switch (upscalingMode) {
+			case 0:
+				m_core.prepareImageForStorage(cmdStream, m_renderTargets.finalBuffer);
+				
+				fsr1.recordUpscaling(
+						cmdStream,
+						m_renderTargets.colorBuffer,
+						m_renderTargets.finalBuffer
+				);
+				break;
+			case 1:
+				m_core.prepareImageForSampling(cmdStream, m_renderTargets.depthBuffer);
+				m_core.prepareImageForSampling(cmdStream, m_renderTargets.motionBuffer);
+				
+				m_core.prepareImageForSampling(cmdStream, m_renderTargets.finalBuffer);
+				
+				fsr2.recordUpscaling(
+						cmdStream,
+						m_renderTargets.colorBuffer,
+						m_renderTargets.finalBuffer
+				);
+				break;
+			case 2:
+				m_core.prepareImageForStorage(cmdStream, m_renderTargets.finalBuffer);
+				
+				nis.recordUpscaling(
+						cmdStream,
+						m_renderTargets.colorBuffer,
+						m_renderTargets.finalBuffer
+				);
+				break;
+			case 3:
+				m_core.prepareImageForStorage(cmdStream, m_renderTargets.finalBuffer);
+				
+				bilinear.recordUpscaling(
+						cmdStream,
+						m_renderTargets.colorBuffer,
+						m_renderTargets.finalBuffer
+				);
+				break;
+			default:
+				break;
+		}
+		
+		m_core.prepareImageForSampling(cmdStream, m_renderTargets.finalBuffer);
 
 		// motion blur
 		vkcv::ImageHandle motionBlurOutput;
 
 		if (motionVectorVisualisationMode == eMotionVectorVisualisationMode::None) {
-			float cameraNear;
-			float cameraFar;
-			m_cameraManager.getActiveCamera().getNearFar(cameraNear, cameraFar);
-
 			motionBlurOutput = m_motionBlur.render(
 				cmdStream,
 				m_renderTargets.motionBuffer,
-				m_renderTargets.colorBuffer,
+				m_renderTargets.finalBuffer,
 				m_renderTargets.depthBuffer,
 				motionBlurMode,
-				cameraNear,
-				cameraFar,
+				near,
+				far,
 				fDeltaTimeSeconds,
 				static_cast<float>(cameraShutterSpeedInverse),
 				motionBlurTileOffsetLength,
-				motionBlurFastPathThreshold);
-		}
-		else {
+				motionBlurFastPathThreshold
+			);
+		} else {
 			motionBlurOutput = m_motionBlur.renderMotionVectorVisualisation(
 				cmdStream,
 				m_renderTargets.motionBuffer,
 				motionVectorVisualisationMode,
-				motionVectorVisualisationRange);
+				motionVectorVisualisationRange
+			);
 		}
 
 		// gamma correction
@@ -330,7 +478,8 @@ void App::run() {
 			m_gammaCorrectionPass.pipeline,
 			fullScreenImageDispatch,
 			{ vkcv::useDescriptorSet(0, m_gammaCorrectionPass.descriptorSet) },
-			vkcv::PushConstants(0));
+			vkcv::PushConstants(0)
+		);
 
 		m_core.prepareSwapchainImageForPresent(cmdStream);
 		m_core.submitCommandStream(cmdStream);
@@ -364,6 +513,21 @@ void App::run() {
 		ImGui::InputFloat("Object mean height",         &objectMeanHeight);
 		ImGui::InputFloat("Object rotation speed X",    &objectRotationSpeedX);
 		ImGui::InputFloat("Object rotation speed Y",    &objectRotationSpeedY);
+		
+		float sharpness = fsr2.getSharpness();
+		
+		ImGui::Combo("FSR Quality Mode", &fsrModeIndex, fsrModeNames.data(), fsrModeNames.size());
+		ImGui::DragFloat("FSR Sharpness", &sharpness, 0.001, 0.0f, 1.0f);
+		ImGui::Checkbox("FSR Mip Lod Bias", &fsrMipLoadBiasFlag);
+		ImGui::Combo("Upscaling Mode", &upscalingMode, modeNames.data(), modeNames.size());
+		
+		if ((fsrModeIndex >= 0) && (fsrModeIndex <= 4)) {
+			fsrMode = static_cast<vkcv::upscaling::FSR2QualityMode>(fsrModeIndex);
+		}
+		
+		fsr1.setSharpness(sharpness);
+		fsr2.setSharpness(sharpness);
+		nis.setSharpness(sharpness);
 
 		ImGui::End();
 		gui.endGUI();
diff --git a/projects/indirect_dispatch/src/AppSetup.cpp b/projects/indirect_dispatch/src/AppSetup.cpp
index 0a334932defb8ea3632d4047a4ca8e66e91faa20..5dc6c81e0e9599a561c3de1e359563c1f25334ea 100644
--- a/projects/indirect_dispatch/src/AppSetup.cpp
+++ b/projects/indirect_dispatch/src/AppSetup.cpp
@@ -299,16 +299,29 @@ bool loadComputePass(vkcv::Core& core, const std::filesystem::path& path, Comput
 	return true;
 }
 
-AppRenderTargets createRenderTargets(vkcv::Core& core, const uint32_t width, const uint32_t height) {
+AppRenderTargets createRenderTargets(vkcv::Core& core,
+									 uint32_t width,
+									 uint32_t height,
+									 vkcv::upscaling::FSR2QualityMode mode) {
 	AppRenderTargets targets;
-	vkcv::ImageConfig depthBufferConfig (width, height);
+	uint32_t renderWidth, renderHeight;
+	
+	vkcv::upscaling::getFSR2Resolution(
+			mode,
+			width,
+			height,
+			renderWidth,
+			renderHeight
+	);
+	
+	vkcv::ImageConfig depthBufferConfig (renderWidth, renderHeight);
 
 	targets.depthBuffer = core.createImage(
 		AppConfig::depthBufferFormat,
 		depthBufferConfig
 	);
 	
-	vkcv::ImageConfig bufferConfig (width, height);
+	vkcv::ImageConfig bufferConfig (renderWidth, renderHeight);
 	bufferConfig.setSupportingColorAttachment(true);
 
 	targets.colorBuffer = core.createImage(
@@ -320,6 +333,20 @@ AppRenderTargets createRenderTargets(vkcv::Core& core, const uint32_t width, con
 		AppConfig::motionBufferFormat,
 		bufferConfig
 	);
+	
+	vkcv::ImageConfig finalConfig (width, height);
+	finalConfig.setSupportingColorAttachment(true);
+	finalConfig.setSupportingStorage(true);
+	
+	targets.finalBuffer = core.createImage(
+			AppConfig::colorBufferFormat,
+			finalConfig
+	);
+	
+	core.setDebugLabel(targets.depthBuffer, "Depth buffer");
+	core.setDebugLabel(targets.colorBuffer, "Color buffer");
+	core.setDebugLabel(targets.motionBuffer, "Motion buffer");
+	core.setDebugLabel(targets.finalBuffer, "Final buffer");
 
 	return targets;
 }
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/AppSetup.hpp b/projects/indirect_dispatch/src/AppSetup.hpp
index 41e020c357a3d868775a581170596e1748e39700..d06910b5f02325d27ee7f2a0e0fbfe4cf7560dab 100644
--- a/projects/indirect_dispatch/src/AppSetup.hpp
+++ b/projects/indirect_dispatch/src/AppSetup.hpp
@@ -1,10 +1,12 @@
 #pragma once
 #include <vkcv/Core.hpp>
+#include <vkcv/upscaling/FSR2Upscaling.hpp>
 
 struct AppRenderTargets {
 	vkcv::ImageHandle depthBuffer;
 	vkcv::ImageHandle colorBuffer;
 	vkcv::ImageHandle motionBuffer;
+	vkcv::ImageHandle finalBuffer;
 };
 
 struct GraphicPassHandles {
@@ -46,4 +48,7 @@ bool loadSkyPrePass(vkcv::Core& core, GraphicPassHandles* outHandles);
 
 bool loadComputePass(vkcv::Core& core, const std::filesystem::path& path, ComputePassHandles* outComputePass);
 
-AppRenderTargets createRenderTargets(vkcv::Core& core, const uint32_t width, const uint32_t height);
\ No newline at end of file
+AppRenderTargets createRenderTargets(vkcv::Core& core,
+									 uint32_t width,
+									 uint32_t height,
+									 vkcv::upscaling::FSR2QualityMode mode);
\ No newline at end of file
diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp
index fecdaf98b8429dcb18c8aec811ce40c24eb5d0f4..6b6e2eea46dd6d0e87d2cb8e33ce4c69c4429ca1 100644
--- a/projects/voxelization/src/main.cpp
+++ b/projects/voxelization/src/main.cpp
@@ -658,7 +658,8 @@ int main(int argc, const char** argv) {
 				width, height
 		);
 
-		if ((width != fsrWidth) || ((height != fsrHeight)) || (fsrMipLoadBiasFlagBackup != fsrMipLoadBiasFlag)) {
+		if ((width != fsrWidth) || ((height != fsrHeight)) ||
+			(fsrMipLoadBiasFlagBackup != fsrMipLoadBiasFlag)) {
 			fsrWidth = width;
 			fsrHeight = height;
 			fsrMipLoadBiasFlagBackup = fsrMipLoadBiasFlag;
diff --git a/src/vkcv/Context.cpp b/src/vkcv/Context.cpp
index e642139fe71e988488cbd47041599a061b481d93..c9d48456550f786ce6f6ee25d755ad258bc11d1f 100644
--- a/src/vkcv/Context.cpp
+++ b/src/vkcv/Context.cpp
@@ -426,6 +426,24 @@ namespace vkcv {
 			);
 		}
 		
+		if (featureManager.useExtension(VK_AMD_DEVICE_COHERENT_MEMORY_EXTENSION_NAME, false)) {
+			featureManager.useFeatures<vk::PhysicalDeviceCoherentMemoryFeaturesAMD>(
+				[](vk::PhysicalDeviceCoherentMemoryFeaturesAMD &features) {
+					features.setDeviceCoherentMemory(true);
+				},
+				false
+			);
+		}
+		
+		if (featureManager.useExtension(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME, false)) {
+			featureManager.useFeatures<vk::PhysicalDeviceSubgroupSizeControlFeatures>(
+					[](vk::PhysicalDeviceSubgroupSizeControlFeatures &features) {
+						features.setSubgroupSizeControl(true);
+					},
+					false
+			);
+		}
+		
 		if (featureManager.useExtension(VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME, false)) {
 			featureManager.useFeatures<vk::PhysicalDeviceIndexTypeUint8FeaturesEXT>(
 				[](vk::PhysicalDeviceIndexTypeUint8FeaturesEXT &features) {
@@ -467,13 +485,37 @@ namespace vkcv {
 
 		vk::Device device = physicalDevice.createDevice(deviceCreateInfo);
 
-		QueueManager queueManager =
-			QueueManager::create(device, queuePairsGraphics, queuePairsCompute, queuePairsTransfer);
+		QueueManager queueManager = QueueManager::create(
+				device,
+				queuePairsGraphics,
+				queuePairsCompute,
+				queuePairsTransfer
+		);
+		
+		const bool coherentDeviceMemory = featureManager.checkFeatures<vk::PhysicalDeviceCoherentMemoryFeaturesAMD>(
+				vk::StructureType::ePhysicalDeviceCoherentMemoryFeaturesAMD,
+				[](const vk::PhysicalDeviceCoherentMemoryFeaturesAMD &features) {
+					return features.deviceCoherentMemory;
+				}
+		);
 
 		vma::AllocatorCreateFlags vmaFlags;
-		const vma::AllocatorCreateInfo allocatorCreateInfo(vmaFlags, physicalDevice, device, 0,
-														   nullptr, nullptr, nullptr, nullptr,
-														   instance, VK_HEADER_VERSION_COMPLETE);
+		if (coherentDeviceMemory) {
+			vmaFlags |= vma::AllocatorCreateFlagBits::eAmdDeviceCoherentMemory;
+		}
+		
+		const vma::AllocatorCreateInfo allocatorCreateInfo(
+				vmaFlags,
+				physicalDevice,
+				device,
+				0,
+				nullptr,
+				nullptr,
+				nullptr,
+				nullptr,
+				instance,
+				VK_HEADER_VERSION_COMPLETE
+		);
 
 		vma::Allocator allocator = vma::createAllocator(allocatorCreateInfo);
 
diff --git a/src/vkcv/DescriptorSetUsage.cpp b/src/vkcv/DescriptorSetUsage.cpp
index 70fc7811791e02d57cb3a4de3d1c2f75db6e9b3a..cfa2ba1998e124cec0f1fc851356d28009288e1b 100644
--- a/src/vkcv/DescriptorSetUsage.cpp
+++ b/src/vkcv/DescriptorSetUsage.cpp
@@ -5,7 +5,10 @@ namespace vkcv {
 
 	DescriptorSetUsage useDescriptorSet(uint32_t location, const DescriptorSetHandle &descriptorSet,
 										const std::vector<uint32_t> &dynamicOffsets) {
-		DescriptorSetUsage usage(location, descriptorSet, dynamicOffsets);
+		DescriptorSetUsage usage;
+		usage.location = location;
+		usage.descriptorSet = descriptorSet;
+		usage.dynamicOffsets = dynamicOffsets;
 		return usage;
 	}
 
diff --git a/src/vkcv/FeatureManager.cpp b/src/vkcv/FeatureManager.cpp
index bc9640c781c33abfb0ac7dee915696ce563bbc0a..afb2ff8c2d91f290718fb9e53edf2c075f7ebe2b 100644
--- a/src/vkcv/FeatureManager.cpp
+++ b/src/vkcv/FeatureManager.cpp
@@ -477,6 +477,25 @@ namespace vkcv {
 		return true;
 	}
 	
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceCoherentMemoryFeaturesAMD &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceCoherentMemoryFeaturesAMD);
+		
+		vkcv_check_feature(deviceCoherentMemory);
+		
+		return true;
+	}
+	
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceSubgroupSizeControlFeatures &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceSubgroupSizeControlFeatures);
+		
+		vkcv_check_feature(subgroupSizeControl);
+		vkcv_check_feature(computeFullSubgroups);
+		
+		return true;
+	}
+	
 	bool FeatureManager::checkSupport(const vk::PhysicalDeviceIndexTypeUint8FeaturesEXT &features,
 									  bool required) const {
 		vkcv_check_init_features2(vk::PhysicalDeviceIndexTypeUint8FeaturesEXT);
diff --git a/src/vkcv/ImageManager.cpp b/src/vkcv/ImageManager.cpp
index fa227fc6c9838b214c027a80ae984f0ce7940704..65c645897520f9e92049824d163b27a1fb5e7124 100644
--- a/src/vkcv/ImageManager.cpp
+++ b/src/vkcv/ImageManager.cpp
@@ -404,20 +404,33 @@ namespace vkcv {
 		
 		const uint32_t mipLevelsMax = image.m_viewPerMip.size();
 		
-		if (mipLevelOffset > mipLevelsMax)
+		if (mipLevelOffset > mipLevelsMax) {
 			mipLevelOffset = mipLevelsMax;
+		}
 		
-		if ((!mipLevelCount) || (mipLevelOffset + mipLevelCount > mipLevelsMax))
+		if ((!mipLevelCount) || (mipLevelOffset + mipLevelCount > mipLevelsMax)) {
 			mipLevelCount = mipLevelsMax - mipLevelOffset;
+		}
 		
-		vk::ImageSubresourceRange imageSubresourceRange(aspectFlags, mipLevelOffset, mipLevelCount,
-														0, image.m_layers);
+		vk::ImageSubresourceRange imageSubresourceRange(
+				aspectFlags,
+				mipLevelOffset,
+				mipLevelCount,
+				0,
+				image.m_layers
+		);
 		
 		// TODO: precise AccessFlagBits, will require a lot of context
-		return vk::ImageMemoryBarrier(vk::AccessFlagBits::eMemoryWrite,
-									  vk::AccessFlagBits::eMemoryRead, image.m_layout, newLayout,
-									  VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED,
-									  image.m_handle, imageSubresourceRange);
+		return vk::ImageMemoryBarrier(
+				vk::AccessFlagBits::eMemoryWrite,
+				vk::AccessFlagBits::eMemoryRead,
+				image.m_layout,
+				newLayout,
+				VK_QUEUE_FAMILY_IGNORED,
+				VK_QUEUE_FAMILY_IGNORED,
+				image.m_handle,
+				imageSubresourceRange
+		);
 	}
 	
 	void ImageManager::switchImageLayoutImmediate(const ImageHandle &handle,
diff --git a/src/vkcv/VertexData.cpp b/src/vkcv/VertexData.cpp
index 9b9db90166a0be6f0e3a4a5666f837ceba293690..a1fcfb3692fcc6051e237e759ce29c38e4c688f4 100644
--- a/src/vkcv/VertexData.cpp
+++ b/src/vkcv/VertexData.cpp
@@ -4,7 +4,9 @@
 namespace vkcv {
 
 	VertexBufferBinding vertexBufferBinding(const BufferHandle &buffer, size_t offset) {
-		VertexBufferBinding binding(buffer, offset);
+		VertexBufferBinding binding;
+		binding.buffer = buffer;
+		binding.offset = offset;
 		return binding;
 	}