diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index cd3676f45bf0891de97ab88ff74cdd980f6920da..21153c3f191e30b45a7f6a3d23ab7a887dba2263 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -163,7 +163,7 @@ namespace vkcv
          */
         [[nodiscard]]
         PipelineHandle createComputePipeline(
-            const ShaderProgram &config, 
+            const ShaderProgram &shaderProgram,
             const std::vector<vk::DescriptorSetLayout> &descriptorSetLayouts);
 
         /**
diff --git a/modules/shader_compiler/CMakeLists.txt b/modules/shader_compiler/CMakeLists.txt
index 4b674ec41ed4ea5f42dc73187c212e6a69952cec..6fee42bfb571168cd2371e21e231ce417efa41f0 100644
--- a/modules/shader_compiler/CMakeLists.txt
+++ b/modules/shader_compiler/CMakeLists.txt
@@ -10,6 +10,9 @@ set(vkcv_shader_compiler_include ${PROJECT_SOURCE_DIR}/include)
 
 # Add source and header files to the module
 set(vkcv_shader_compiler_sources
+		${vkcv_shader_compiler_include}/vkcv/shader/Compiler.hpp
+		${vkcv_shader_compiler_source}/vkcv/shader/Compiler.cpp
+		
 		${vkcv_shader_compiler_include}/vkcv/shader/GLSLCompiler.hpp
 		${vkcv_shader_compiler_source}/vkcv/shader/GLSLCompiler.cpp
 )
diff --git a/modules/shader_compiler/include/vkcv/shader/Compiler.hpp b/modules/shader_compiler/include/vkcv/shader/Compiler.hpp
index df22f23e761724369b2bb7f47088d011a2af6ee9..71771f7f3ec488e80070bce113586a694eea8e6a 100644
--- a/modules/shader_compiler/include/vkcv/shader/Compiler.hpp
+++ b/modules/shader_compiler/include/vkcv/shader/Compiler.hpp
@@ -1,6 +1,11 @@
 #pragma once
 
+#include <filesystem>
+#include <map>
+#include <string>
+
 #include <vkcv/Event.hpp>
+#include <vkcv/ShaderStage.hpp>
 
 namespace vkcv::shader {
 	
@@ -8,6 +13,9 @@ namespace vkcv::shader {
 	
 	class Compiler {
 	private:
+	protected:
+		std::unordered_map<std::string, std::string> m_defines;
+		
 	public:
 		virtual bool compileSource(ShaderStage shaderStage, const char* shaderSource,
 								   const ShaderCompiledFunction& compiled,
@@ -17,6 +25,9 @@ namespace vkcv::shader {
 							 const ShaderCompiledFunction& compiled,
 							 const std::filesystem::path& includePath, bool update) = 0;
 		
+		std::string getDefine(const std::string& name) const;
+		
+		void setDefine(const std::string& name, const std::string& value);
 	};
 	
 }
diff --git a/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp b/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp
index 0a72c357065d80c7457808fbd21480fae49e9e0e..eca84def118625e21df1c645cfc71b6bcddf7393 100644
--- a/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp
+++ b/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp
@@ -7,7 +7,7 @@
 
 namespace vkcv::shader {
 	
-	class GLSLCompiler : Compiler {
+	class GLSLCompiler : public Compiler {
 	private:
 	public:
 		GLSLCompiler();
diff --git a/modules/shader_compiler/src/vkcv/shader/Compiler.cpp b/modules/shader_compiler/src/vkcv/shader/Compiler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f5ec0435ca8b82dc5f328921f43a39338d1be456
--- /dev/null
+++ b/modules/shader_compiler/src/vkcv/shader/Compiler.cpp
@@ -0,0 +1,14 @@
+
+#include "vkcv/shader/Compiler.hpp"
+
+namespace vkcv::shader {
+	
+	std::string Compiler::getDefine(const std::string &name) const {
+		return m_defines.at(name);
+	}
+	
+	void Compiler::setDefine(const std::string &name, const std::string &value) {
+		m_defines[name] = value;
+	}
+	
+}
diff --git a/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
index 5f177e93214e868275e7c1f9d094041a303adb82..ed2bf740f7dc5806b8872d5a718992a2b8632e6f 100644
--- a/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
+++ b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
@@ -2,6 +2,7 @@
 #include "vkcv/shader/GLSLCompiler.hpp"
 
 #include <fstream>
+#include <strstream>
 #include <glslang/SPIRV/GlslangToSpv.h>
 #include <glslang/StandAlone/DirStackFileIncluder.h>
 
@@ -12,7 +13,7 @@ namespace vkcv::shader {
 	
 	static uint32_t s_CompilerCount = 0;
 	
-	GLSLCompiler::GLSLCompiler() {
+	GLSLCompiler::GLSLCompiler() : Compiler() {
 		if (s_CompilerCount == 0) {
 			glslang::InitializeProcess();
 		}
@@ -20,7 +21,7 @@ namespace vkcv::shader {
 		s_CompilerCount++;
 	}
 	
-	GLSLCompiler::GLSLCompiler(const GLSLCompiler &other) {
+	GLSLCompiler::GLSLCompiler(const GLSLCompiler &other) : Compiler(other) {
 		s_CompilerCount++;
 	}
 	
@@ -223,6 +224,11 @@ namespace vkcv::shader {
 			EShMsgSpvRules |
 			EShMsgVulkanRules
 		);
+		
+		std::strstream defines;
+		for (const auto& define : m_defines) {
+			defines << "#define " << define.first << " " << define.second << std::endl;
+		}
 
 		std::string preprocessedGLSL;
 
@@ -237,6 +243,19 @@ namespace vkcv::shader {
 			return false;
 		}
 		
+		size_t pos = preprocessedGLSL.find("#version");
+		if (pos >= preprocessedGLSL.length()) {
+			pos = 0;
+		}
+		
+		const size_t epos = preprocessedGLSL.find_last_of("#extension", pos);
+		if (epos < preprocessedGLSL.length()) {
+			pos = epos;
+		}
+		
+		pos = preprocessedGLSL.find('\n', pos) + 1;
+		preprocessedGLSL = preprocessedGLSL.insert(pos, defines.str());
+		
 		const char* preprocessedCString = preprocessedGLSL.c_str();
 		shader.setStrings(&preprocessedCString, 1);
 
diff --git a/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp b/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp
index cb2dc6c18af4dd90b881a4fcc2d74a3792b6309c..6e07a0973a2ad49a53ab2c26b09c21408959eea5 100644
--- a/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp
+++ b/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp
@@ -1,15 +1,50 @@
 #pragma once
 
+#include <vkcv/Core.hpp>
+#include <vkcv/Handles.hpp>
 #include <vkcv/ShaderProgram.hpp>
 
 namespace vkcv::upscaling {
 	
+	struct FSRConstants {
+		uint32_t Const0 [4];
+		uint32_t Const1 [4];
+		uint32_t Const2 [4];
+		uint32_t Const3 [4];
+		uint32_t Sample [4];
+	};
+	
 	class FSRUpscaling {
 	private:
-		ShaderProgram m_program;
+		Core& m_core;
+		
+		PipelineHandle m_easuPipeline;
+		PipelineHandle m_rcasPipeline;
+		
+		DescriptorSetHandle m_descriptorSet;
+		Buffer<FSRConstants> m_constants;
+		ImageHandle m_intermediateImage;
+		SamplerHandle m_sampler;
+		
+		bool m_hdr;
+		float m_sharpness;
 	
 	public:
-		FSRUpscaling();
+		explicit FSRUpscaling(Core& core);
+		
+		~FSRUpscaling() = default;
+		
+		void recordUpscaling(const CommandStreamHandle& cmdStream,
+							 const ImageHandle& input,
+							 const ImageHandle& output);
+		
+		bool isHdrEnabled() const;
+		
+		void setHdrEnabled(bool enabled);
+		
+		float getSharpness() const;
+		
+		void setSharpness(float sharpness);
 		
 	};
 
diff --git a/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp b/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp
index 934d550cdf9fb31c90cc7ace41e1d9066f558cd7..6eba68684b9b0d133ec3a5bd0c88a60ce2715e2f 100644
--- a/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp
+++ b/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp
@@ -18,6 +18,15 @@
 
 namespace vkcv::upscaling {
 	
+	static std::vector<DescriptorBinding> getDescriptorBindings() {
+		return std::vector<DescriptorBinding>({
+			DescriptorBinding(0, DescriptorType::UNIFORM_BUFFER, 1, ShaderStage::COMPUTE),
+			DescriptorBinding(1, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::COMPUTE),
+			DescriptorBinding(2, DescriptorType::IMAGE_STORAGE, 1, ShaderStage::COMPUTE),
+			DescriptorBinding(3, DescriptorType::SAMPLER, 1, ShaderStage::COMPUTE)
+		});
+	}
+	
 	static bool writeShaderCode(const std::filesystem::path &shaderPath, const std::string& code) {
 		std::ofstream file (shaderPath.string(), std::ios::out);
 		
@@ -33,7 +42,8 @@ namespace vkcv::upscaling {
 		return true;
 	}
 	
-	static bool compileFSRShaders(const shader::ShaderCompiledFunction& compiled) {
+	static bool compileFSRShader(vkcv::shader::GLSLCompiler& compiler,
+								 const shader::ShaderCompiledFunction& compiled) {
 		std::filesystem::path directory = generateTemporaryFilePath();
 		
 		if (!std::filesystem::create_directory(directory)) {
@@ -49,16 +59,185 @@ namespace vkcv::upscaling {
 			return false;
 		}
 		
-		vkcv::shader::GLSLCompiler compiler;
 		return compiler.compileSource(vkcv::ShaderStage::COMPUTE,
 									  FSR_PASS_GLSL_SHADER.c_str(),
 									  compiled, directory);
 	}
 	
-	FSRUpscaling::FSRUpscaling() {
-		compileFSRShaders([&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-			m_program.addShader(shaderStage, path);
-		});
+	FSRUpscaling::FSRUpscaling(Core& core) :
+	m_core(core),
+	m_easuPipeline(),
+	m_rcasPipeline(),
+	m_descriptorSet(m_core.createDescriptorSet(getDescriptorBindings())),
+	m_constants(m_core.createBuffer<FSRConstants>(
+			BufferType::UNIFORM,1,
+			BufferMemoryType::HOST_VISIBLE
+	)),
+	m_intermediateImage(),
+	m_sampler(m_core.createSampler(
+			SamplerFilterType::LINEAR,
+			SamplerFilterType::LINEAR,
+			SamplerMipmapMode::NEAREST,
+			SamplerAddressMode::CLAMP_TO_EDGE
+	)),
+	m_hdr(false),
+	m_sharpness(1.0f) {
+		const auto descriptorSetLayout = m_core.getDescriptorSet(m_descriptorSet).layout;
+		
+		vkcv::shader::GLSLCompiler easuCompiler, rcasCompiler;
+		
+		easuCompiler.setDefine("SAMPLE_BILINEAR", "0");
+		easuCompiler.setDefine("SAMPLE_RCAS", "0");
+		easuCompiler.setDefine("SAMPLE_EASU", "1");
+		
+		rcasCompiler.setDefine("SAMPLE_BILINEAR", "0");
+		rcasCompiler.setDefine("SAMPLE_RCAS", "1");
+		rcasCompiler.setDefine("SAMPLE_EASU", "0");
+		
+		{
+			ShaderProgram program;
+			compileFSRShader(easuCompiler, [&program, &descriptorSetLayout](vkcv::ShaderStage shaderStage,
+					const std::filesystem::path& path) {
+				program.addShader(shaderStage, path);
+			});
+			
+			m_easuPipeline = m_core.createComputePipeline(program, { descriptorSetLayout });
+		}
+		
+		{
+			ShaderProgram program;
+			compileFSRShader(rcasCompiler, [&program, &descriptorSetLayout](vkcv::ShaderStage shaderStage,
+					const std::filesystem::path& path) {
+				program.addShader(shaderStage, path);
+			});
+			
+			m_rcasPipeline = m_core.createComputePipeline(program, { descriptorSetLayout });
+		}
 	}
-
+	
+	void FSRUpscaling::recordUpscaling(const CommandStreamHandle& cmdStream,
+									   const ImageHandle& input,
+									   const ImageHandle& output) {
+		const uint32_t inputWidth = m_core.getImageWidth(input);
+		const uint32_t inputHeight = m_core.getImageWidth(input);
+		
+		const uint32_t outputWidth = m_core.getImageWidth(output);
+		const uint32_t outputHeight = m_core.getImageWidth(output);
+		
+		if ((outputWidth != m_core.getImageWidth(m_intermediateImage)) ||
+			(outputHeight != m_core.getImageHeight(m_intermediateImage))) {
+			m_intermediateImage = m_core.createImage(
+					vk::Format::eR8G8B8A8Srgb,
+					outputWidth, outputHeight,1,
+					false,
+					true
+			).getHandle();
+		}
+		
+		{
+			FSRConstants consts = {};
+			
+			FsrEasuCon(
+					consts.Const0, consts.Const1, consts.Const2, consts.Const3,
+					static_cast<AF1>(inputWidth), static_cast<AF1>(inputHeight),
+					static_cast<AF1>(inputWidth), static_cast<AF1>(inputHeight),
+					static_cast<AF1>(outputWidth), static_cast<AF1>(outputHeight)
+			);
+			
+			consts.Sample[0] = (((m_hdr) && (m_sharpness <= 0.0f)) ? 1 : 0);
+			m_constants.fill(&consts);
+		}
+		
+		static const uint32_t threadGroupWorkRegionDim = 16;
+		
+		const uint32_t dispatch[3] = {
+				(outputWidth + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim,
+				(outputHeight + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim,
+				1
+		};
+		
+		m_core.recordImageMemoryBarrier(cmdStream, input);
+		
+		if (m_sharpness >= 0.0f) {
+			{
+				DescriptorWrites writes;
+				writes.sampledImageWrites.emplace_back(1, input);
+				writes.storageImageWrites.emplace_back(2, m_intermediateImage);
+				
+				m_core.writeDescriptorSet(m_descriptorSet, writes);
+			}
+			
+			m_core.recordComputeDispatchToCmdStream(
+					cmdStream,
+					m_easuPipeline,
+					dispatch,
+					{DescriptorSetUsage(0, m_core.getDescriptorSet(m_descriptorSet).vulkanHandle)},
+					PushConstants(0)
+			);
+			
+			{
+				FSRConstants consts = {};
+				
+				FsrRcasCon(consts.Const0, 1.0f / m_sharpness);
+				consts.Sample[0] = (m_hdr ? 1 : 0);
+				
+				m_constants.fill(&consts);
+			}
+			
+			m_core.prepareImageForSampling(cmdStream, m_intermediateImage);
+			
+			{
+				DescriptorWrites writes;
+				writes.sampledImageWrites.emplace_back(1, m_intermediateImage);
+				writes.storageImageWrites.emplace_back(2, output);
+				
+				m_core.writeDescriptorSet(m_descriptorSet, writes);
+			}
+			
+			m_core.recordComputeDispatchToCmdStream(
+					cmdStream,
+					m_rcasPipeline,
+					dispatch,
+					{DescriptorSetUsage(0, m_core.getDescriptorSet(m_descriptorSet).vulkanHandle)},
+					PushConstants(0)
+			);
+			
+			m_core.prepareImageForStorage(cmdStream, m_intermediateImage);
+		} else {
+			{
+				DescriptorWrites writes;
+				writes.sampledImageWrites.emplace_back(1, input);
+				writes.storageImageWrites.emplace_back(2, output);
+				
+				m_core.writeDescriptorSet(m_descriptorSet, writes);
+			}
+			
+			m_core.recordComputeDispatchToCmdStream(
+					cmdStream,
+					m_rcasPipeline,
+					dispatch,
+					{DescriptorSetUsage(0, m_core.getDescriptorSet(m_descriptorSet).vulkanHandle)},
+					PushConstants(0)
+			);
+		}
+		
+		m_core.recordImageMemoryBarrier(cmdStream, output);
+	}
+	
+	bool FSRUpscaling::isHdrEnabled() const {
+		return m_hdr;
+	}
+	
+	void FSRUpscaling::setHdrEnabled(bool enabled) {
+		m_hdr = enabled;
+	}
+	
+	float FSRUpscaling::getSharpness() const {
+		return m_sharpness;
+	}
+	
+	void FSRUpscaling::setSharpness(float sharpness) {
+		m_sharpness = sharpness;
+	}
+	
 }