diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index bcca46d642bee3a5c50a277ff533a02005378e84..a892f0d2c1a22aed7afc3b21d0dc2f3ef2be61ef 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -302,5 +302,8 @@ namespace vkcv
 	
 		void recordMemoryBarrier(const CommandStreamHandle& cmdStream);
 		
+		void recordBlitImage(const CommandStreamHandle& cmdStream, const ImageHandle& src, const ImageHandle& dst,
+							 SamplerFilterType filterType);
+		
     };
 }
diff --git a/modules/upscaling/CMakeLists.txt b/modules/upscaling/CMakeLists.txt
index 1625c3ccceb5434f33b7983d64376d0e5b21b677..0767e5c4d2d60c001ac9d6792efcd623456284a8 100644
--- a/modules/upscaling/CMakeLists.txt
+++ b/modules/upscaling/CMakeLists.txt
@@ -9,6 +9,12 @@ set(vkcv_upscaling_source ${PROJECT_SOURCE_DIR}/src)
 set(vkcv_upscaling_include ${PROJECT_SOURCE_DIR}/include)
 
 set(vkcv_upscaling_sources
+		${vkcv_upscaling_include}/vkcv/upscaling/Upscaling.hpp
+		${vkcv_upscaling_source}/vkcv/upscaling/Upscaling.cpp
+		
+		${vkcv_upscaling_include}/vkcv/upscaling/BilinearUpscaling.hpp
+		${vkcv_upscaling_source}/vkcv/upscaling/BilinearUpscaling.cpp
+		
 		${vkcv_upscaling_include}/vkcv/upscaling/FSRUpscaling.hpp
 		${vkcv_upscaling_source}/vkcv/upscaling/FSRUpscaling.cpp
 )
diff --git a/modules/upscaling/include/vkcv/upscaling/BilinearUpscaling.hpp b/modules/upscaling/include/vkcv/upscaling/BilinearUpscaling.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..52124dc8e36bee7ef7c00de6afcf3457296a7623
--- /dev/null
+++ b/modules/upscaling/include/vkcv/upscaling/BilinearUpscaling.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "Upscaling.hpp"
+
+namespace vkcv::upscaling {
+	
+	class BilinearUpscaling : public Upscaling {
+	private:
+	public:
+		BilinearUpscaling(Core& core);
+		
+		void recordUpscaling(const CommandStreamHandle& cmdStream,
+							 const ImageHandle& input,
+							 const ImageHandle& output) override;
+	
+	};
+
+}
diff --git a/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp b/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp
index 8492029f37e82fc86608aa47afbadab39b52217c..2a1338b85e3ee60a33215157aaaa15817f2db97f 100644
--- a/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp
+++ b/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
-#include <vkcv/Core.hpp>
-#include <vkcv/Handles.hpp>
+#include "Upscaling.hpp"
+
 #include <vkcv/ShaderProgram.hpp>
 
 namespace vkcv::upscaling {
@@ -28,10 +28,8 @@ namespace vkcv::upscaling {
 		uint32_t Sample [4];
 	};
 	
-	class FSRUpscaling {
+	class FSRUpscaling : public Upscaling {
 	private:
-		Core& m_core;
-		
 		PipelineHandle m_easuPipeline;
 		PipelineHandle m_rcasPipeline;
 		
@@ -61,11 +59,9 @@ namespace vkcv::upscaling {
 	public:
 		explicit FSRUpscaling(Core& core);
 		
-		~FSRUpscaling() = default;
-		
 		void recordUpscaling(const CommandStreamHandle& cmdStream,
 							 const ImageHandle& input,
-							 const ImageHandle& output);
+							 const ImageHandle& output) override;
 		
 		[[nodiscard]]
 		bool isHdrEnabled() const;
diff --git a/modules/upscaling/include/vkcv/upscaling/Upscaling.hpp b/modules/upscaling/include/vkcv/upscaling/Upscaling.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c44e878ad78f0a3359599c76f781371505fd3a85
--- /dev/null
+++ b/modules/upscaling/include/vkcv/upscaling/Upscaling.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <vkcv/Core.hpp>
+#include <vkcv/Handles.hpp>
+
+namespace vkcv::upscaling {
+	
+	class Upscaling {
+	protected:
+		Core& m_core;
+	
+	public:
+		Upscaling(Core& core);
+		
+		~Upscaling() = default;
+		
+		virtual void recordUpscaling(const CommandStreamHandle& cmdStream,
+									 const ImageHandle& input,
+							 		 const ImageHandle& output) = 0;
+	
+	};
+	
+}
diff --git a/modules/upscaling/src/vkcv/upscaling/BilinearUpscaling.cpp b/modules/upscaling/src/vkcv/upscaling/BilinearUpscaling.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9c36acf5d050e3f4f19223020357b6c32534a2de
--- /dev/null
+++ b/modules/upscaling/src/vkcv/upscaling/BilinearUpscaling.cpp
@@ -0,0 +1,13 @@
+
+#include "vkcv/upscaling/BilinearUpscaling.hpp"
+
+namespace vkcv::upscaling {
+	
+	BilinearUpscaling::BilinearUpscaling(Core &core) : Upscaling(core) {}
+	
+	void BilinearUpscaling::recordUpscaling(const CommandStreamHandle &cmdStream, const ImageHandle &input,
+											const ImageHandle &output) {
+		m_core.recordBlitImage(cmdStream, input, output, SamplerFilterType::LINEAR);
+	}
+
+}
diff --git a/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp b/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp
index df7b8e8ec8cc22c49f7c22d4601bb3e2839732e8..460a6d0b459fe7d1d2a917a62138fea2e5a40908 100644
--- a/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp
+++ b/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp
@@ -152,7 +152,7 @@ namespace vkcv::upscaling {
 	}
 	
 	FSRUpscaling::FSRUpscaling(Core& core) :
-	m_core(core),
+	Upscaling(core),
 	m_easuPipeline(),
 	m_rcasPipeline(),
 	m_easuDescriptorSet(m_core.createDescriptorSet(getDescriptorBindings())),
diff --git a/modules/upscaling/src/vkcv/upscaling/Upscaling.cpp b/modules/upscaling/src/vkcv/upscaling/Upscaling.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b0c3dee9b1c799c0e1f07b59b03d3ad46bd453ed
--- /dev/null
+++ b/modules/upscaling/src/vkcv/upscaling/Upscaling.cpp
@@ -0,0 +1,8 @@
+
+#include "vkcv/upscaling/Upscaling.hpp"
+
+namespace vkcv::upscaling {
+	
+	Upscaling::Upscaling(Core &core) : m_core(core) {}
+	
+}
diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp
index 4d8d5d1ebdd22fbd679e704a581c55b96beb0281..e7f9caa493714d30f13f64c292f1b6e51e5170b1 100644
--- a/projects/voxelization/src/main.cpp
+++ b/projects/voxelization/src/main.cpp
@@ -12,6 +12,7 @@
 #include "ShadowMapping.hpp"
 #include "BloomAndFlares.hpp"
 #include <vkcv/upscaling/FSRUpscaling.hpp>
+#include <vkcv/upscaling/BilinearUpscaling.hpp>
 
 int main(int argc, const char** argv) {
 	const char* applicationName = "Voxelization";
@@ -558,6 +559,10 @@ int main(int argc, const char** argv) {
 	bool fsrMipLoadBiasFlag = true;
 	bool fsrMipLoadBiasFlagBackup = fsrMipLoadBiasFlag;
 	
+	vkcv::upscaling::BilinearUpscaling upscaling1 (core);
+	
+	bool bilinearUpscaling = false;
+	
 	vkcv::gui::GUI gui(core, window);
 
 	glm::vec2   lightAnglesDegree               = glm::vec2(90.f, 0.f);
@@ -826,7 +831,11 @@ int main(int argc, const char** argv) {
 		core.prepareImageForStorage(cmdStream, swapBuffer2);
 		core.prepareImageForSampling(cmdStream, swapBuffer);
 		
-		upscaling.recordUpscaling(cmdStream, swapBuffer, swapBuffer2);
+		if (bilinearUpscaling) {
+			upscaling1.recordUpscaling(cmdStream, swapBuffer, swapBuffer2);
+		} else {
+			upscaling.recordUpscaling(cmdStream, swapBuffer, swapBuffer2);
+		}
 		
 		core.prepareImageForStorage(cmdStream, swapchainInput);
 		core.prepareImageForSampling(cmdStream, swapBuffer2);
@@ -891,8 +900,9 @@ int main(int argc, const char** argv) {
 			float fsrSharpness = upscaling.getSharpness();
 			
 			ImGui::Combo("FSR Quality Mode", &fsrModeIndex, fsrModeNames.data(), fsrModeNames.size());
-			ImGui::DragFloat("FSR Sharpness", &fsrSharpness, 0.01, 0.0f, 1.0f);
+			ImGui::DragFloat("FSR Sharpness", &fsrSharpness, 0.001, 0.0f, 1.0f);
 			ImGui::Checkbox("FSR Mip Lod Bias", &fsrMipLoadBiasFlag);
+			ImGui::Checkbox("Bilinear Upscaling", &bilinearUpscaling);
 			
 			if ((fsrModeIndex >= 0) && (fsrModeIndex <= 4)) {
 				fsrMode = static_cast<vkcv::upscaling::FSRQualityMode>(fsrModeIndex);
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index 00b59da8c2eae363dca354800b0af0484f04c5cc..54bdab0ea1d4d43d6f9d2c87cfbb1367a94fd265 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -610,4 +610,54 @@ namespace vkcv
 		}, nullptr);
 	}
 	
+	void Core::recordBlitImage(const CommandStreamHandle& cmdStream, const ImageHandle& src, const ImageHandle& dst,
+							   SamplerFilterType filterType) {
+		recordCommandsToStream(cmdStream, [&](const vk::CommandBuffer cmdBuffer) {
+			m_ImageManager->recordImageLayoutTransition(
+					src, vk::ImageLayout::eTransferSrcOptimal, cmdBuffer
+			);
+			
+			m_ImageManager->recordImageLayoutTransition(
+					dst, vk::ImageLayout::eTransferDstOptimal, cmdBuffer
+			);
+			
+			const std::array<vk::Offset3D, 2> offsets = {
+					vk::Offset3D(0, 0, 0),
+					vk::Offset3D(0, 0, 1)
+			};
+			
+			const bool srcDepth = isDepthFormat(m_ImageManager->getImageFormat(src));
+			const bool dstDepth = isDepthFormat(m_ImageManager->getImageFormat(dst));
+			
+			const vk::ImageBlit blit = vk::ImageBlit(
+					vk::ImageSubresourceLayers(
+							srcDepth?
+							vk::ImageAspectFlagBits::eDepth :
+							vk::ImageAspectFlagBits::eColor,
+							0, 0, 1
+					),
+					offsets,
+					vk::ImageSubresourceLayers(
+							dstDepth?
+							vk::ImageAspectFlagBits::eDepth :
+							vk::ImageAspectFlagBits::eColor,
+							0, 0, 1
+					),
+					offsets
+			);
+			
+			cmdBuffer.blitImage(
+					m_ImageManager->getVulkanImage(src),
+					vk::ImageLayout::eTransferSrcOptimal,
+					m_ImageManager->getVulkanImage(dst),
+					vk::ImageLayout::eTransferDstOptimal,
+					1,
+					&blit,
+					filterType == SamplerFilterType::LINEAR?
+					vk::Filter::eLinear :
+					vk::Filter::eNearest
+			);
+		}, nullptr);
+	}
+	
 }
diff --git a/src/vkcv/ImageManager.hpp b/src/vkcv/ImageManager.hpp
index 646b3211f761f0c71596fc088b39b784d5f39a5c..4d99422118e8d464ea75d9f013b471f3dd40fd8c 100644
--- a/src/vkcv/ImageManager.hpp
+++ b/src/vkcv/ImageManager.hpp
@@ -13,6 +13,8 @@
 #include "vkcv/ImageConfig.hpp"
 
 namespace vkcv {
+	
+	bool isDepthImageFormat(vk::Format format);
 
 	class ImageManager
 	{