diff --git a/.gitmodules b/.gitmodules
index ef5fa3cfcc738f2bc8dedb0ec219903e71500f49..e270bddb0a91069af1eff869560e8b817f7cc844 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -34,3 +34,6 @@
 [submodule "lib/Vulkan-Hpp"]
 	path = lib/Vulkan-Hpp
 	url = https://github.com/KhronosGroup/Vulkan-Hpp
+[submodule "modules/upscaling/lib/NVIDIAImageScaling"]
+	path = modules/upscaling/lib/NVIDIAImageScaling
+	url = https://github.com/NVIDIAGameWorks/NVIDIAImageScaling.git
diff --git a/modules/shader_compiler/include/vkcv/shader/Compiler.hpp b/modules/shader_compiler/include/vkcv/shader/Compiler.hpp
index d4be7384dfffeeb068a13660004be138e62722a1..cabc2a2628557254f0faed222b6b9f5c27429ad1 100644
--- a/modules/shader_compiler/include/vkcv/shader/Compiler.hpp
+++ b/modules/shader_compiler/include/vkcv/shader/Compiler.hpp
@@ -35,6 +35,7 @@ namespace vkcv::shader {
 		std::string getDefine(const std::string& name) const;
 		
 		void setDefine(const std::string& name, const std::string& value);
+		
 	};
 	
 }
diff --git a/modules/upscaling/CMakeLists.txt b/modules/upscaling/CMakeLists.txt
index dec392573d31a7348f8440162410b4fc91757b51..6d6e7b987e2e7e448f1e8c5925dc731aafc4f3d0 100644
--- a/modules/upscaling/CMakeLists.txt
+++ b/modules/upscaling/CMakeLists.txt
@@ -17,6 +17,9 @@ set(vkcv_upscaling_sources
 		
 		${vkcv_upscaling_include}/vkcv/upscaling/FSRUpscaling.hpp
 		${vkcv_upscaling_source}/vkcv/upscaling/FSRUpscaling.cpp
+		
+		${vkcv_upscaling_include}/vkcv/upscaling/NISUpscaling.hpp
+		${vkcv_upscaling_source}/vkcv/upscaling/NISUpscaling.cpp
 )
 
 # Setup some path variables to load libraries
@@ -26,6 +29,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 NVIDIAImageScaling
+include(config/NVIDIAImageScaling.cmake)
+
 # adding source files to the project
 add_library(vkcv_upscaling ${vkcv_build_attribute} ${vkcv_upscaling_sources})
 
diff --git a/modules/upscaling/config/NVIDIAImageScaling.cmake b/modules/upscaling/config/NVIDIAImageScaling.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..083891c67e369dc783c79f5ac4386ae15e6e3261
--- /dev/null
+++ b/modules/upscaling/config/NVIDIAImageScaling.cmake
@@ -0,0 +1,15 @@
+
+use_git_submodule("${vkcv_upscaling_lib_path}/NVIDIAImageScaling" nvidia_nis_status)
+
+if (${nvidia_nis_status})
+	include_shader(${vkcv_upscaling_lib_path}/NVIDIAImageScaling/NIS/NIS_Scaler.h ${vkcv_upscaling_include} ${vkcv_upscaling_source})
+	include_shader(${vkcv_upscaling_lib_path}/NVIDIAImageScaling/NIS/NIS_Main.glsl ${vkcv_upscaling_include} ${vkcv_upscaling_source})
+	
+	list(APPEND vkcv_upscaling_includes ${vkcv_upscaling_lib}/NVIDIAImageScaling/NIS)
+	
+	list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_source}/NIS_Scaler.h.cxx)
+	list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_source}/NIS_Main.glsl.cxx)
+	
+	list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_include}/NIS_Scaler.h.hxx)
+	list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_include}/NIS_Main.glsl.hxx)
+endif ()
diff --git a/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp b/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp
index 4d3316718bf995f5cc8ccddde8c74575beba7745..7b2e96bb62544db29775faa4473d0feccd2e813c 100644
--- a/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp
+++ b/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp
@@ -30,7 +30,6 @@ namespace vkcv::upscaling {
 	
 	class FSRUpscaling : public Upscaling {
 	private:
-
 		ComputePipelineHandle m_easuPipeline;
 		ComputePipelineHandle m_rcasPipeline;
 
diff --git a/modules/upscaling/include/vkcv/upscaling/NISUpscaling.hpp b/modules/upscaling/include/vkcv/upscaling/NISUpscaling.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..efab311d4fb452285bb3d88262ddbf3fbd04a810
--- /dev/null
+++ b/modules/upscaling/include/vkcv/upscaling/NISUpscaling.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "Upscaling.hpp"
+
+#include <vkcv/ShaderProgram.hpp>
+
+namespace vkcv::upscaling {
+	
+	class NISUpscaling : public Upscaling {
+	private:
+		ComputePipelineHandle m_scalerPipeline;
+		
+		DescriptorSetLayoutHandle m_scalerDescriptorSetLayout;
+		DescriptorSetHandle m_scalerDescriptorSet;
+		
+		Buffer<uint8_t> m_scalerConstants;
+		SamplerHandle m_sampler;
+		ImageHandle m_coefScaleImage;
+		ImageHandle m_coefUsmImage;
+		
+		uint32_t m_blockWidth;
+		uint32_t m_blockHeight;
+		
+		bool m_hdr;
+		float m_sharpness;
+		
+	public:
+		explicit NISUpscaling(Core &core);
+		
+		void recordUpscaling(const CommandStreamHandle &cmdStream,
+							 const ImageHandle &input,
+							 const ImageHandle &output) override;
+		
+		[[nodiscard]]
+		bool isHdrEnabled() const;
+		
+		void setHdrEnabled(bool enabled);
+		
+		[[nodiscard]]
+		float getSharpness() const;
+		
+		void setSharpness(float sharpness);
+		
+	};
+	
+}
diff --git a/modules/upscaling/lib/NVIDIAImageScaling b/modules/upscaling/lib/NVIDIAImageScaling
new file mode 160000
index 0000000000000000000000000000000000000000..7a468267104585ce5cd683aebd8e4cb74f826807
--- /dev/null
+++ b/modules/upscaling/lib/NVIDIAImageScaling
@@ -0,0 +1 @@
+Subproject commit 7a468267104585ce5cd683aebd8e4cb74f826807
diff --git a/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp b/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp
index 0f6dfcdeaa3b1efc6f58c1c2d0eb61bbd2090a80..0a5e08acfd543ac8b2f439b423e97823d272e473 100644
--- a/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp
+++ b/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp
@@ -1,3 +1,4 @@
+
 #include "vkcv/upscaling/FSRUpscaling.hpp"
 
 #include <stdint.h>
@@ -104,7 +105,6 @@ namespace vkcv::upscaling {
 	    descriptorBindings.insert(std::make_pair(3, binding_3));
 
 	    return descriptorBindings;
-
 	}
 	
 	template<typename T>
@@ -159,10 +159,11 @@ namespace vkcv::upscaling {
 			return false;
 		}
 		
-		return compiler.compileSource(vkcv::ShaderStage::COMPUTE,
-									  FSR_PASS_GLSL_SHADER.c_str(),
-									  [&directory, &compiled] (vkcv::ShaderStage shaderStage,
-									  		const std::filesystem::path& path) {
+		return compiler.compileSource(
+				vkcv::ShaderStage::COMPUTE,
+				FSR_PASS_GLSL_SHADER.c_str(),
+				[&directory, &compiled] (vkcv::ShaderStage shaderStage,
+										 const std::filesystem::path& path) {
 				if (compiled) {
 					compiled(shaderStage, path);
 				}
@@ -198,6 +199,7 @@ namespace vkcv::upscaling {
 			SamplerMipmapMode::NEAREST,
 			SamplerAddressMode::CLAMP_TO_EDGE
 	)),
+	
 	m_hdr(false),
 	m_sharpness(0.875f) {
 		vkcv::shader::GLSLCompiler easuCompiler;
diff --git a/modules/upscaling/src/vkcv/upscaling/NISUpscaling.cpp b/modules/upscaling/src/vkcv/upscaling/NISUpscaling.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..998d7f83e7a05d456dfe544a87953769181eada4
--- /dev/null
+++ b/modules/upscaling/src/vkcv/upscaling/NISUpscaling.cpp
@@ -0,0 +1,278 @@
+
+#include "vkcv/upscaling/NISUpscaling.hpp"
+
+#include <NIS_Config.h>
+
+#include "NIS_Main.glsl.hxx"
+#include "NIS_Scaler.h.hxx"
+
+#include <vkcv/File.hpp>
+#include <vkcv/Logger.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+
+namespace vkcv::upscaling {
+	
+	static DescriptorBindings getDescriptorBindings() {
+		DescriptorBindings descriptorBindings = {};
+		
+		auto binding_0 = DescriptorBinding {
+				0,
+				DescriptorType::UNIFORM_BUFFER_DYNAMIC,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		auto binding_1 = DescriptorBinding{
+				1,
+				DescriptorType::SAMPLER,
+				1,
+				ShaderStage::COMPUTE
+		};
+		
+		auto binding_2 = DescriptorBinding {
+				2,
+				DescriptorType::IMAGE_SAMPLED,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		auto binding_3 = DescriptorBinding{
+				3,
+				DescriptorType::IMAGE_STORAGE,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		auto binding_4 = DescriptorBinding {
+				4,
+				DescriptorType::IMAGE_SAMPLED,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		auto binding_5 = DescriptorBinding {
+				5,
+				DescriptorType::IMAGE_SAMPLED,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		descriptorBindings.insert(std::make_pair(0, binding_0));
+		descriptorBindings.insert(std::make_pair(1, binding_1));
+		descriptorBindings.insert(std::make_pair(2, binding_2));
+		descriptorBindings.insert(std::make_pair(3, binding_3));
+		descriptorBindings.insert(std::make_pair(4, binding_4));
+		descriptorBindings.insert(std::make_pair(5, binding_5));
+		
+		return descriptorBindings;
+	}
+	
+	static ImageHandle createFilterImage(Core &core,
+										 const void* data) {
+		const size_t rowPitch = kFilterSize * 2;
+		const size_t imageSize = rowPitch * kPhaseCount;
+		
+		Image image = core.createImage(
+				vk::Format::eR16G16B16A16Sfloat,
+				kFilterSize / 4,
+				kPhaseCount
+		);
+		
+		image.fill(data, imageSize);
+		
+		return image.getHandle();
+	}
+	
+	static bool writeShaderCode(const std::filesystem::path &shaderPath, const std::string& code) {
+		std::ofstream file (shaderPath.string(), std::ios::out);
+		
+		if (!file.is_open()) {
+			vkcv_log(LogLevel::ERROR, "The file could not be opened (%s)", shaderPath.string().c_str());
+			return false;
+		}
+		
+		file.seekp(0);
+		file.write(code.c_str(), static_cast<std::streamsize>(code.length()));
+		file.close();
+		
+		return true;
+	}
+	
+	static bool compileNISShader(vkcv::shader::GLSLCompiler& compiler,
+								 const shader::ShaderCompiledFunction& compiled) {
+		std::filesystem::path directory = generateTemporaryDirectoryPath();
+		
+		if (!std::filesystem::create_directory(directory)) {
+			vkcv_log(LogLevel::ERROR, "The directory could not be created (%s)", directory.string().c_str());
+			return false;
+		}
+		
+		if (!writeShaderCode(directory / "NIS_Scaler.h", NIS_SCALER_H_SHADER)) {
+			return false;
+		}
+		
+		return compiler.compileSource(
+				vkcv::ShaderStage::COMPUTE,
+				NIS_MAIN_GLSL_SHADER.c_str(),
+				[&directory, &compiled] (vkcv::ShaderStage shaderStage,
+										 const std::filesystem::path& path) {
+				if (compiled) {
+					compiled(shaderStage, path);
+				}
+				
+				std::filesystem::remove_all(directory);
+			}, directory
+		);
+	}
+	
+	NISUpscaling::NISUpscaling(Core &core) :
+	Upscaling(core),
+	m_scalerPipeline(),
+	
+	m_scalerDescriptorSetLayout(m_core.createDescriptorSetLayout(getDescriptorBindings())),
+	m_scalerDescriptorSet(m_core.createDescriptorSet(m_scalerDescriptorSetLayout)),
+	
+	m_scalerConstants(m_core.createBuffer<uint8_t>(
+			BufferType::UNIFORM, sizeof(NISConfig),
+			BufferMemoryType::HOST_VISIBLE
+	)),
+	m_sampler(m_core.createSampler(
+			SamplerFilterType::LINEAR,
+			SamplerFilterType::LINEAR,
+			SamplerMipmapMode::NEAREST,
+			SamplerAddressMode::CLAMP_TO_EDGE
+	)),
+	
+	m_coefScaleImage(createFilterImage(m_core, coef_scale_fp16)),
+	m_coefUsmImage(createFilterImage(m_core, coef_usm_fp16)),
+	
+	m_blockWidth(0),
+	m_blockHeight(0),
+	
+	m_hdr(false),
+	m_sharpness(0.875f) {
+		vkcv::shader::GLSLCompiler scalerCompiler;
+		
+		scalerCompiler.setDefine("NIS_SCALER", "1");
+		scalerCompiler.setDefine("NIS_GLSL", "1");
+		
+		NISOptimizer optimizer (true, NISGPUArchitecture::NVIDIA_Generic);
+		
+		m_blockWidth = optimizer.GetOptimalBlockWidth();
+		m_blockHeight = optimizer.GetOptimalBlockHeight();
+		
+		scalerCompiler.setDefine("NIS_BLOCK_WIDTH", std::to_string(m_blockWidth));
+		scalerCompiler.setDefine("NIS_BLOCK_HEIGHT", std::to_string(m_blockHeight));
+		
+		const uint32_t threadGroupSize = optimizer.GetOptimalThreadGroupSize();
+		
+		scalerCompiler.setDefine("NIS_THREAD_GROUP_SIZE", std::to_string(threadGroupSize));
+		
+		{
+			ShaderProgram program;
+			compileNISShader(scalerCompiler, [&program](vkcv::ShaderStage shaderStage,
+														const std::filesystem::path& path) {
+				program.addShader(shaderStage, path);
+			});
+			
+			m_scalerPipeline = m_core.createComputePipeline({program,{
+					m_scalerDescriptorSetLayout
+			}});
+			
+			
+			DescriptorWrites writes;
+			writes.uniformBufferWrites.emplace_back(
+					0, m_scalerConstants.getHandle(), true
+			);
+			
+			writes.samplerWrites.emplace_back(1, m_sampler);
+			writes.sampledImageWrites.emplace_back(4, m_coefScaleImage);
+			writes.sampledImageWrites.emplace_back(5, m_coefUsmImage);
+			
+			m_core.writeDescriptorSet(m_scalerDescriptorSet, writes);
+		}
+	}
+	
+	void NISUpscaling::recordUpscaling(const CommandStreamHandle &cmdStream,
+									   const ImageHandle &input,
+									   const ImageHandle &output) {
+		m_core.recordBeginDebugLabel(cmdStream, "vkcv::upscaling::NISUpscaling", {
+				0.0f, 1.0f, 0.0f, 1.0f
+		});
+		
+		const uint32_t inputWidth = m_core.getImageWidth(input);
+		const uint32_t inputHeight = m_core.getImageHeight(input);
+		
+		const uint32_t outputWidth = m_core.getImageWidth(output);
+		const uint32_t outputHeight = m_core.getImageHeight(output);
+		
+		NISConfig config {};
+		NVScalerUpdateConfig(
+				config,
+				m_sharpness,
+				0, 0,
+				inputWidth,
+				inputHeight,
+				inputWidth,
+				inputHeight,
+				0, 0,
+				outputWidth,
+				outputHeight,
+				outputWidth,
+				outputHeight,
+				m_hdr? NISHDRMode::PQ : NISHDRMode::None
+		);
+		
+		m_scalerConstants.fill(
+				reinterpret_cast<uint8_t*>(&config),
+				sizeof(config)
+		);
+		
+		uint32_t dispatch[3];
+		dispatch[0] = (outputWidth + (m_blockWidth - 1)) / m_blockWidth;
+		dispatch[1] = (outputHeight + (m_blockHeight - 1)) / m_blockHeight;
+		dispatch[2] = 1;
+		
+		m_core.recordBufferMemoryBarrier(cmdStream, m_scalerConstants.getHandle());
+		
+		{
+			DescriptorWrites writes;
+			writes.sampledImageWrites.emplace_back(2, input);
+			writes.storageImageWrites.emplace_back(3, output);
+			
+			m_core.writeDescriptorSet(m_scalerDescriptorSet, writes);
+		}
+		
+		m_core.recordComputeDispatchToCmdStream(
+				cmdStream,
+				m_scalerPipeline,
+				dispatch,
+				{DescriptorSetUsage(0, m_scalerDescriptorSet, { 0 })},
+				PushConstants(0)
+		);
+		
+		m_core.recordEndDebugLabel(cmdStream);
+	}
+	
+	bool NISUpscaling::isHdrEnabled() const {
+		return m_hdr;
+	}
+	
+	void NISUpscaling::setHdrEnabled(bool enabled) {
+		m_hdr = enabled;
+	}
+	
+	float NISUpscaling::getSharpness() const {
+		return m_sharpness;
+	}
+	
+	void NISUpscaling::setSharpness(float sharpness) {
+		m_sharpness = (sharpness < 0.0f ? 0.0f : (sharpness > 1.0f ? 1.0f : sharpness));
+	}
+
+}
diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp
index dd4e0374e7e18477f69fde79e090e3d4c5689aa5..f01b87a79b287609822635ecc67f7541e1a8e3b6 100644
--- a/projects/voxelization/src/main.cpp
+++ b/projects/voxelization/src/main.cpp
@@ -10,6 +10,7 @@
 #include "ShadowMapping.hpp"
 #include <vkcv/upscaling/FSRUpscaling.hpp>
 #include <vkcv/upscaling/BilinearUpscaling.hpp>
+#include <vkcv/upscaling/NISUpscaling.hpp>
 #include <vkcv/effects/BloomAndFlaresEffect.hpp>
 
 int main(int argc, const char** argv) {
@@ -600,8 +601,15 @@ int main(int argc, const char** argv) {
 	bool fsrMipLoadBiasFlagBackup = fsrMipLoadBiasFlag;
 	
 	vkcv::upscaling::BilinearUpscaling upscaling1 (core);
+	vkcv::upscaling::NISUpscaling upscaling2 (core);
 	
-	bool bilinearUpscaling = false;
+	const std::vector<const char*> modeNames = {
+			"Bilinear Upscaling",
+			"FSR Upscaling",
+			"NIS Upscaling"
+	};
+	
+	int upscalingMode = 0;
 	
 	vkcv::gui::GUI gui(core, windowHandle);
 
@@ -880,10 +888,18 @@ int main(int argc, const char** argv) {
 		core.prepareImageForSampling(cmdStream, swapBuffer);
 		core.recordEndDebugLabel(cmdStream);
 		
-		if (bilinearUpscaling) {
-			upscaling1.recordUpscaling(cmdStream, swapBuffer, swapBuffer2);
-		} else {
-			upscaling.recordUpscaling(cmdStream, swapBuffer, swapBuffer2);
+		switch (upscalingMode) {
+			case 0:
+				upscaling1.recordUpscaling(cmdStream, swapBuffer, swapBuffer2);
+				break;
+			case 1:
+				upscaling.recordUpscaling(cmdStream, swapBuffer, swapBuffer2);
+				break;
+			case 2:
+				upscaling2.recordUpscaling(cmdStream, swapBuffer, swapBuffer2);
+				break;
+			default:
+				break;
 		}
 		
 		core.prepareImageForStorage(cmdStream, swapchainInput);
@@ -946,18 +962,19 @@ int main(int argc, const char** argv) {
 			ImGui::DragFloat("Absorption density", &absorptionDensity, 0.0001);
 			ImGui::DragFloat("Volumetric ambient", &volumetricAmbient, 0.002);
 			
-			float fsrSharpness = upscaling.getSharpness();
+			float sharpness = upscaling.getSharpness();
 			
 			ImGui::Combo("FSR Quality Mode", &fsrModeIndex, fsrModeNames.data(), fsrModeNames.size());
-			ImGui::DragFloat("FSR Sharpness", &fsrSharpness, 0.001, 0.0f, 1.0f);
+			ImGui::DragFloat("FSR Sharpness", &sharpness, 0.001, 0.0f, 1.0f);
 			ImGui::Checkbox("FSR Mip Lod Bias", &fsrMipLoadBiasFlag);
-			ImGui::Checkbox("Bilinear Upscaling", &bilinearUpscaling);
+			ImGui::Combo("Upscaling Mode", &upscalingMode, modeNames.data(), modeNames.size());
 			
 			if ((fsrModeIndex >= 0) && (fsrModeIndex <= 4)) {
 				fsrMode = static_cast<vkcv::upscaling::FSRQualityMode>(fsrModeIndex);
 			}
 			
-			upscaling.setSharpness(fsrSharpness);
+			upscaling.setSharpness(sharpness);
+			upscaling2.setSharpness(sharpness);
 
 			if (ImGui::Button("Reload forward pass")) {