diff --git a/include/vkcv/DescriptorWrites.hpp b/include/vkcv/DescriptorWrites.hpp
index 00e2d9a0494aec08767ced3a12086c812f6f1451..28de2ed7fa6b7e71bfa49b67a337f80f2e05ddcf 100644
--- a/include/vkcv/DescriptorWrites.hpp
+++ b/include/vkcv/DescriptorWrites.hpp
@@ -20,20 +20,15 @@ namespace vkcv {
 		uint32_t	mipLevel;
 	};
 
-	struct UniformBufferDescriptorWrite {
-		inline UniformBufferDescriptorWrite(uint32_t binding, BufferHandle buffer, bool dynamic = false) :
-		binding(binding), buffer(buffer), dynamic(dynamic) {};
+	struct BufferDescriptorWrite {
+		inline BufferDescriptorWrite(uint32_t binding, BufferHandle buffer, bool dynamic = false,
+									 uint32_t offset = 0, uint32_t size = 0) :
+		binding(binding), buffer(buffer), dynamic(dynamic), offset(offset), size(size) {};
 		uint32_t		binding;
 		BufferHandle	buffer;
 		bool 			dynamic;
-	};
-
-	struct StorageBufferDescriptorWrite {
-		inline StorageBufferDescriptorWrite(uint32_t binding, BufferHandle buffer, bool dynamic = false) :
-		binding(binding), buffer(buffer), dynamic(dynamic) {};
-		uint32_t		binding;
-		BufferHandle	buffer;
-		bool			dynamic;
+		uint32_t 		offset;
+		uint32_t 		size;
 	};
 
 	struct SamplerDescriptorWrite {
@@ -45,8 +40,8 @@ namespace vkcv {
 	struct DescriptorWrites {
 		std::vector<SampledImageDescriptorWrite>		sampledImageWrites;
 		std::vector<StorageImageDescriptorWrite>		storageImageWrites;
-		std::vector<UniformBufferDescriptorWrite>	    uniformBufferWrites;
-		std::vector<StorageBufferDescriptorWrite>	    storageBufferWrites;
+		std::vector<BufferDescriptorWrite>	    		uniformBufferWrites;
+		std::vector<BufferDescriptorWrite>	    		storageBufferWrites;
 		std::vector<SamplerDescriptorWrite>			    samplerWrites;
 	};
 }
\ No newline at end of file
diff --git a/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
index 341e219fdbbe3ea815925e295069b8a719aca8a4..7d4bf289d8c4135ba776cfd85a270ea277aa40ed 100644
--- a/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
+++ b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
@@ -214,26 +214,28 @@ namespace vkcv::shader {
 		
 		std::string source (shaderSource);
 		
-		std::strstream defines;
-		for (const auto& define : m_defines) {
-			defines << "#define " << define.first << " " << define.second << std::endl;
-		}
-		
-		defines << '\0';
-		
-		size_t pos = source.find("#version") + 8;
-		if (pos >= source.length()) {
-			pos = 0;
-		}
-		
-		const size_t epos = source.find_last_of("#extension", pos) + 10;
-		if (epos < source.length()) {
-			pos = epos;
+		if (!m_defines.empty()) {
+			std::strstream defines;
+			for (const auto& define : m_defines) {
+				defines << "#define " << define.first << " " << define.second << std::endl;
+			}
+			
+			defines << '\0';
+
+			size_t pos = source.find("#version") + 8;
+			if (pos >= source.length()) {
+				pos = 0;
+			}
+			
+			const size_t epos = source.find_last_of("#extension", pos) + 10;
+			if (epos < source.length()) {
+				pos = epos;
+			}
+			
+			pos = source.find('\n', pos) + 1;
+			source = source.insert(pos, defines.str());
 		}
 		
-		pos = source.find('\n', pos) + 1;
-		source = source.insert(pos, defines.str());
-		
 		const char *shaderStrings [1];
 		shaderStrings[0] = source.c_str();
 		
diff --git a/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp b/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp
index d028beceabd08ac8b1ae2aea13919477016b5cd1..3d418c2461d5f36f3295e8bd76f92b679dda5d55 100644
--- a/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp
+++ b/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp
@@ -111,7 +111,7 @@ namespace vkcv::upscaling {
 	m_easuDescriptorSet(m_core.createDescriptorSet(getDescriptorBindings())),
 	m_rcasDescriptorSet(m_core.createDescriptorSet(getDescriptorBindings())),
 	m_constants(m_core.createBuffer<FSRConstants>(
-			BufferType::UNIFORM,1,
+			BufferType::UNIFORM,2,
 			BufferMemoryType::HOST_VISIBLE
 	)),
 	m_intermediateImage(),
@@ -122,8 +122,9 @@ namespace vkcv::upscaling {
 			SamplerAddressMode::CLAMP_TO_EDGE
 	)),
 	m_hdr(false),
-	m_sharpness(0/*.875f*/) {
-		vkcv::shader::GLSLCompiler easuCompiler, rcasCompiler;
+	m_sharpness(0.875f) {
+		vkcv::shader::GLSLCompiler easuCompiler;
+		vkcv::shader::GLSLCompiler rcasCompiler;
 		
 		const auto& features = m_core.getContext().getPhysicalDevice().getFeatures2();
 		const bool float16Support = (
@@ -159,7 +160,11 @@ namespace vkcv::upscaling {
 			});
 			
 			DescriptorWrites writes;
-			writes.uniformBufferWrites.emplace_back(0, m_constants.getHandle(), true);
+			writes.uniformBufferWrites.emplace_back(
+					0, m_constants.getHandle(),
+					true, 0, sizeof(FSRConstants)
+			);
+			
 			writes.samplerWrites.emplace_back(3, m_sampler);
 			
 			m_core.writeDescriptorSet(m_easuDescriptorSet, writes);
@@ -177,7 +182,11 @@ namespace vkcv::upscaling {
 			});
 			
 			DescriptorWrites writes;
-			writes.uniformBufferWrites.emplace_back(0, m_constants.getHandle(), true);
+			writes.uniformBufferWrites.emplace_back(
+					0, m_constants.getHandle(),
+					true, sizeof(FSRConstants), sizeof(FSRConstants)
+			);
+			
 			writes.samplerWrites.emplace_back(3, m_sampler);
 			
 			m_core.writeDescriptorSet(m_rcasDescriptorSet, writes);
@@ -218,7 +227,7 @@ namespace vkcv::upscaling {
 			
 			consts.Sample[0] = (((m_hdr) && (m_sharpness <= +0.0f)) ? 1 : 0);
 			
-			m_constants.fill(&consts);
+			m_constants.fill(&consts, 1, 0);
 		}
 		
 		static const uint32_t threadGroupWorkRegionDim = 16;
@@ -262,7 +271,7 @@ namespace vkcv::upscaling {
 				FsrRcasCon(consts.Const0, (1.0f - m_sharpness) * 2.0f);
 				consts.Sample[0] = (m_hdr ? 1 : 0);
 				
-				m_constants.fill(&consts);
+				m_constants.fill(&consts, 1, 1);
 			}
 			
 			m_core.recordBufferMemoryBarrier(cmdStream, m_constants.getHandle());
@@ -290,7 +299,7 @@ namespace vkcv::upscaling {
 			
 			m_core.recordComputeDispatchToCmdStream(
 					cmdStream,
-					m_rcasPipeline,
+					m_easuPipeline,
 					dispatch,
 					{DescriptorSetUsage(0, m_core.getDescriptorSet(
 							m_easuDescriptorSet
diff --git a/projects/particle_simulation/src/main.cpp b/projects/particle_simulation/src/main.cpp
index 0d83644b866f5f89fb33c68f1d5a79fcee8c028a..07ba6b194ce72dbad15a921ca13a4814c6d4f5df 100644
--- a/projects/particle_simulation/src/main.cpp
+++ b/projects/particle_simulation/src/main.cpp
@@ -163,13 +163,13 @@ int main(int argc, const char **argv) {
     particleBuffer.fill(particleSystem.getParticles());
 
     vkcv::DescriptorWrites setWrites;
-    setWrites.uniformBufferWrites = {vkcv::UniformBufferDescriptorWrite(0,color.getHandle()),
-                                     vkcv::UniformBufferDescriptorWrite(1,position.getHandle())};
-    setWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(2,particleBuffer.getHandle())};
+    setWrites.uniformBufferWrites = {vkcv::BufferDescriptorWrite(0,color.getHandle()),
+                                     vkcv::BufferDescriptorWrite(1,position.getHandle())};
+    setWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(2,particleBuffer.getHandle())};
     core.writeDescriptorSet(descriptorSet, setWrites);
 
     vkcv::DescriptorWrites computeWrites;
-    computeWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0,particleBuffer.getHandle())};
+    computeWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(0,particleBuffer.getHandle())};
     core.writeDescriptorSet(computeDescriptorSet, computeWrites);
 
     if (!particlePipeline || !computePipeline)
diff --git a/projects/voxelization/resources/shaders/postEffects.comp b/projects/voxelization/resources/shaders/postEffects.comp
new file mode 100644
index 0000000000000000000000000000000000000000..c0f9fe1a764bcdabac5501e2f82692c6f476e9e6
--- /dev/null
+++ b/projects/voxelization/resources/shaders/postEffects.comp
@@ -0,0 +1,149 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+#include "luma.inc"
+
+layout(set=0, binding=0)        uniform texture2D   inTexture;
+layout(set=0, binding=1)        uniform sampler     textureSampler;
+layout(set=0, binding=2, rgba8) uniform image2D     outImage;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+layout( push_constant ) uniform constants{
+    float time;
+};
+
+// from: https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
+vec3 ACESFilm(vec3 x)
+{
+    float a = 2.51f;
+    float b = 0.03f;
+    float c = 2.43f;
+    float d = 0.59f;
+    float e = 0.14f;
+    return clamp((x*(a*x+b))/(x*(c*x+d)+e), 0, 1);
+}
+
+// From Dave Hoskins: https://www.shadertoy.com/view/4djSRW.
+float hash(vec3 p3){
+    p3 = fract(p3 * 0.1031);
+    p3 += dot(p3,p3.yzx + 19.19);
+    return fract((p3.x + p3.y) * p3.z);
+}
+
+// From iq: https://www.shadertoy.com/view/4sfGzS.
+float noise(vec3 x){
+    vec3 i = floor(x);
+    vec3 f = fract(x);
+    f = f*f*(3.0-2.0*f);
+    return mix(mix(mix(hash(i+vec3(0, 0, 0)),
+    hash(i+vec3(1, 0, 0)),f.x),
+    mix(hash(i+vec3(0, 1, 0)),
+    hash(i+vec3(1, 1, 0)),f.x),f.y),
+    mix(mix(hash(i+vec3(0, 0, 1)),
+    hash(i+vec3(1, 0, 1)),f.x),
+    mix(hash(i+vec3(0, 1, 1)),
+    hash(i+vec3(1, 1, 1)),f.x),f.y),f.z);
+}
+
+// From: https://www.shadertoy.com/view/3sGSWVF
+// Slightly high-passed continuous value-noise.
+float grainSource(vec3 x, float strength, float pitch){
+    float center = noise(x);
+    float v1 = center - noise(vec3( 1, 0, 0)/pitch + x) + 0.5;
+    float v2 = center - noise(vec3( 0, 1, 0)/pitch + x) + 0.5;
+    float v3 = center - noise(vec3(-1, 0, 0)/pitch + x) + 0.5;
+    float v4 = center - noise(vec3( 0,-1, 0)/pitch + x) + 0.5;
+
+    float total = (v1 + v2 + v3 + v4) / 4.0;
+    return mix(1, 0.5 + total, strength);
+}
+
+vec3 applyGrain(ivec2 uv, vec3 c){
+    float grainLift     = 0.6;
+    float grainStrength = 0.4;
+    float grainTimeFactor = 0.1;
+
+    float timeColorOffset = 1.2;
+    vec3 grain = vec3(
+    grainSource(vec3(uv, floor(grainTimeFactor*time)),                   grainStrength, grainLift),
+    grainSource(vec3(uv, floor(grainTimeFactor*time + timeColorOffset)), grainStrength, grainLift),
+    grainSource(vec3(uv, floor(grainTimeFactor*time - timeColorOffset)), grainStrength, grainLift));
+
+    return c * grain;
+}
+
+vec2 computeDistortedUV(vec2 uv, float aspectRatio){
+    uv          = uv * 2 - 1;
+    float   r2  = dot(uv, uv);
+    float   k1  = 0.02f;
+
+    float maxR2     = dot(vec2(1), vec2(1));
+    float maxFactor = maxR2 * k1;
+
+    // correction only needed for pincushion distortion
+    maxFactor       = min(maxFactor, 0);
+
+    uv /= 1 + r2*k1;
+
+    // correction to avoid going out of [-1, 1] range when using barrel distortion
+    uv *= 1 + maxFactor;
+
+    return uv * 0.5 + 0.5;
+}
+
+float computeLocalContrast(vec2 uv){
+    float lumaMin = 100;
+    float lumaMax = 0;
+
+    vec2 pixelSize = vec2(1) / textureSize(sampler2D(inTexture, textureSampler), 0);
+
+    for(int x = -1; x <= 1; x++){
+        for(int y = -1; y <= 1; y++){
+            vec3 c = texture(sampler2D(inTexture, textureSampler), uv + vec2(x, y) * pixelSize).rgb;
+            float luma  = computeLuma(c);
+            lumaMin     = min(lumaMin, luma);
+            lumaMax     = max(lumaMax, luma);
+        }
+    }
+
+    return lumaMax - lumaMin;
+}
+
+vec3 computeChromaticAberrationScale(vec2 uv){
+    float   localContrast   = computeLocalContrast(uv);
+    vec3    colorScales     = vec3(-1, 0, 1);
+    float   aberrationScale = 0.004;
+    vec3    maxScaleFactors = colorScales * aberrationScale;
+    float   factor          = clamp(localContrast, 0, 1);
+    return mix(vec3(0), maxScaleFactors, factor);
+}
+
+vec3 sampleColorChromaticAberration(vec2 uv){
+    vec2 toCenter       = (vec2(0.5) - uv);
+
+    vec3 scaleFactors = computeChromaticAberrationScale(uv);
+
+    float r = texture(sampler2D(inTexture, textureSampler), uv + toCenter * scaleFactors.r).r;
+    float g = texture(sampler2D(inTexture, textureSampler), uv + toCenter * scaleFactors.g).g;
+    float b = texture(sampler2D(inTexture, textureSampler), uv + toCenter * scaleFactors.b).b;
+    return vec3(r, g, b);
+}
+
+void main(){
+
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outImage)))){
+        return;
+    }
+    ivec2   textureRes  = textureSize(sampler2D(inTexture, textureSampler), 0);
+    ivec2   coord       = ivec2(gl_GlobalInvocationID.xy);
+    vec2    uv          = vec2(coord) / textureRes;
+    float   aspectRatio = float(textureRes.x) / textureRes.y;
+    uv                  = computeDistortedUV(uv, aspectRatio);
+
+    vec3 tonemapped    = sampleColorChromaticAberration(uv);
+    tonemapped          = applyGrain(coord, tonemapped);
+
+    vec3 gammaCorrected = pow(tonemapped, vec3(1.f / 2.2f));
+    imageStore(outImage, coord, vec4(gammaCorrected, 0.f));
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/tonemapping.comp b/projects/voxelization/resources/shaders/tonemapping.comp
index 8fa07d39ebb56eab857cdccb755a6558f5ae1ec3..ffadc9a71e207f97fec9a8815aa1c61bc709c369 100644
--- a/projects/voxelization/resources/shaders/tonemapping.comp
+++ b/projects/voxelization/resources/shaders/tonemapping.comp
@@ -1,18 +1,12 @@
 #version 440
 #extension GL_GOOGLE_include_directive : enable
 
-#include "luma.inc"
-
 layout(set=0, binding=0)        uniform texture2D   inTexture;
 layout(set=0, binding=1)        uniform sampler     textureSampler;
 layout(set=0, binding=2, rgba8) uniform image2D     outImage;
 
 layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
 
-layout( push_constant ) uniform constants{
-    float time;
-};
-
 // from: https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
 vec3 ACESFilm(vec3 x)
 {
@@ -24,112 +18,6 @@ vec3 ACESFilm(vec3 x)
     return clamp((x*(a*x+b))/(x*(c*x+d)+e), 0, 1);
 }
 
-// From Dave Hoskins: https://www.shadertoy.com/view/4djSRW.
-float hash(vec3 p3){
-    p3 = fract(p3 * 0.1031);
-    p3 += dot(p3,p3.yzx + 19.19);
-    return fract((p3.x + p3.y) * p3.z);
-}
-
-// From iq: https://www.shadertoy.com/view/4sfGzS.
-float noise(vec3 x){
-    vec3 i = floor(x);
-    vec3 f = fract(x);
-    f = f*f*(3.0-2.0*f);
-    return mix(mix(mix(hash(i+vec3(0, 0, 0)), 
-                       hash(i+vec3(1, 0, 0)),f.x),
-                   mix(hash(i+vec3(0, 1, 0)), 
-                       hash(i+vec3(1, 1, 0)),f.x),f.y),
-               mix(mix(hash(i+vec3(0, 0, 1)), 
-                       hash(i+vec3(1, 0, 1)),f.x),
-                   mix(hash(i+vec3(0, 1, 1)), 
-                       hash(i+vec3(1, 1, 1)),f.x),f.y),f.z);
-}
-
-// From: https://www.shadertoy.com/view/3sGSWVF
-// Slightly high-passed continuous value-noise.
-float grainSource(vec3 x, float strength, float pitch){
-    float center = noise(x);
-	float v1 = center - noise(vec3( 1, 0, 0)/pitch + x) + 0.5;
-	float v2 = center - noise(vec3( 0, 1, 0)/pitch + x) + 0.5;
-	float v3 = center - noise(vec3(-1, 0, 0)/pitch + x) + 0.5;
-	float v4 = center - noise(vec3( 0,-1, 0)/pitch + x) + 0.5;
-    
-	float total = (v1 + v2 + v3 + v4) / 4.0;
-	return mix(1, 0.5 + total, strength);
-}
-
-vec3 applyGrain(ivec2 uv, vec3 c){
-    float grainLift     = 0.6;
-    float grainStrength = 0.4;
-    float grainTimeFactor = 0.1;
-    
-    float timeColorOffset = 1.2;
-    vec3 grain = vec3(
-        grainSource(vec3(uv, floor(grainTimeFactor*time)),                   grainStrength, grainLift),
-        grainSource(vec3(uv, floor(grainTimeFactor*time + timeColorOffset)), grainStrength, grainLift),
-        grainSource(vec3(uv, floor(grainTimeFactor*time - timeColorOffset)), grainStrength, grainLift));
-    
-    return c * grain;
-}
-
-vec2 computeDistortedUV(vec2 uv, float aspectRatio){
-    uv          = uv * 2 - 1;
-    float   r2  = dot(uv, uv);
-    float   k1  = 0.02f;
-    
-    float maxR2     = dot(vec2(1), vec2(1));
-    float maxFactor = maxR2 * k1;
-    
-    // correction only needed for pincushion distortion
-    maxFactor       = min(maxFactor, 0);
-    
-    uv /= 1 + r2*k1;
-    
-    // correction to avoid going out of [-1, 1] range when using barrel distortion 
-    uv *= 1 + maxFactor;
-    
-    return uv * 0.5 + 0.5;
-}
-
-float computeLocalContrast(vec2 uv){
-    float lumaMin = 100;
-    float lumaMax = 0;
-    
-    vec2 pixelSize = vec2(1) / textureSize(sampler2D(inTexture, textureSampler), 0);
-    
-    for(int x = -1; x <= 1; x++){
-        for(int y = -1; y <= 1; y++){
-            vec3 c = texture(sampler2D(inTexture, textureSampler), uv + vec2(x, y) * pixelSize).rgb;
-            float luma  = computeLuma(c);
-            lumaMin     = min(lumaMin, luma);
-            lumaMax     = max(lumaMax, luma);
-        }
-    }
-    
-    return lumaMax - lumaMin;
-}
-
-vec3 computeChromaticAberrationScale(vec2 uv){
-    float   localContrast   = computeLocalContrast(uv);
-    vec3    colorScales     = vec3(-1, 0, 1);
-    float   aberrationScale = 0.004;
-    vec3    maxScaleFactors = colorScales * aberrationScale;
-    float   factor          = clamp(localContrast, 0, 1);
-    return mix(vec3(0), maxScaleFactors, factor);
-}
-
-vec3 sampleColorChromaticAberration(vec2 uv){
-    vec2 toCenter       = (vec2(0.5) - uv);
-    
-    vec3 scaleFactors = computeChromaticAberrationScale(uv);
-    
-    float r = texture(sampler2D(inTexture, textureSampler), uv + toCenter * scaleFactors.r).r;
-    float g = texture(sampler2D(inTexture, textureSampler), uv + toCenter * scaleFactors.g).g;
-    float b = texture(sampler2D(inTexture, textureSampler), uv + toCenter * scaleFactors.b).b;
-    return vec3(r, g, b);
-}
-
 void main(){
 
     if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outImage)))){
@@ -138,12 +26,9 @@ void main(){
     ivec2   textureRes  = textureSize(sampler2D(inTexture, textureSampler), 0);
     ivec2   coord       = ivec2(gl_GlobalInvocationID.xy);
     vec2    uv          = vec2(coord) / textureRes;
-    float   aspectRatio = float(textureRes.x) / textureRes.y;
-    uv                  = computeDistortedUV(uv, aspectRatio);
-    vec3 linearColor    = sampleColorChromaticAberration(uv);
+
+    vec3 linearColor    = texture(sampler2D(inTexture, textureSampler), uv).rgb;
     vec3 tonemapped     = ACESFilm(linearColor);
-    tonemapped          = applyGrain(coord, tonemapped);
-    
-    vec3 gammaCorrected = pow(tonemapped, vec3(1.f / 2.2f));
-    imageStore(outImage, coord, vec4(gammaCorrected, 0.f));
+
+    imageStore(outImage, coord, vec4(tonemapped, 0.f));
 }
\ No newline at end of file
diff --git a/projects/voxelization/src/Voxelization.cpp b/projects/voxelization/src/Voxelization.cpp
index bbf161ddeb0899a1ce61279b4c476fb19cb906d7..f7e03709c6423ef0e3c43251afb28e887b9be61f 100644
--- a/projects/voxelization/src/Voxelization.cpp
+++ b/projects/voxelization/src/Voxelization.cpp
@@ -119,10 +119,10 @@ Voxelization::Voxelization(
 	m_voxelizationPipe = m_corePtr->createGraphicsPipeline(voxelizationPipeConfig);
 
 	vkcv::DescriptorWrites voxelizationDescriptorWrites;
-	voxelizationDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
+	voxelizationDescriptorWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
 	voxelizationDescriptorWrites.uniformBufferWrites = { 
-		vkcv::UniformBufferDescriptorWrite(1, m_voxelInfoBuffer.getHandle()),
-		vkcv::UniformBufferDescriptorWrite(3, lightInfoBuffer)
+		vkcv::BufferDescriptorWrite(1, m_voxelInfoBuffer.getHandle()),
+		vkcv::BufferDescriptorWrite(3, lightInfoBuffer)
 	};
 	voxelizationDescriptorWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(4, shadowMap) };
 	voxelizationDescriptorWrites.samplerWrites      = { vkcv::SamplerDescriptorWrite(5, shadowSampler) };
@@ -180,7 +180,7 @@ Voxelization::Voxelization(
 		{ m_corePtr->getDescriptorSet(m_voxelResetDescriptorSet).layout });
 
 	vkcv::DescriptorWrites resetVoxelWrites;
-	resetVoxelWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
+	resetVoxelWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
 	m_corePtr->writeDescriptorSet(m_voxelResetDescriptorSet, resetVoxelWrites);
 
 	// buffer to image
@@ -192,7 +192,7 @@ Voxelization::Voxelization(
 		{ m_corePtr->getDescriptorSet(m_bufferToImageDescriptorSet).layout });
 
 	vkcv::DescriptorWrites bufferToImageDescriptorWrites;
-	bufferToImageDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
+	bufferToImageDescriptorWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
 	bufferToImageDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(1, m_voxelImageIntermediate.getHandle()) };
 	m_corePtr->writeDescriptorSet(m_bufferToImageDescriptorSet, bufferToImageDescriptorWrites);
 
@@ -205,11 +205,11 @@ Voxelization::Voxelization(
 		{ m_corePtr->getDescriptorSet(m_secondaryBounceDescriptorSet).layout });
 
 	vkcv::DescriptorWrites secondaryBounceDescriptorWrites;
-	secondaryBounceDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
+	secondaryBounceDescriptorWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
 	secondaryBounceDescriptorWrites.sampledImageWrites  = { vkcv::SampledImageDescriptorWrite(1, m_voxelImageIntermediate.getHandle()) };
 	secondaryBounceDescriptorWrites.samplerWrites       = { vkcv::SamplerDescriptorWrite(2, voxelSampler) };
 	secondaryBounceDescriptorWrites.storageImageWrites  = { vkcv::StorageImageDescriptorWrite(3, m_voxelImage.getHandle()) };
-	secondaryBounceDescriptorWrites.uniformBufferWrites = { vkcv::UniformBufferDescriptorWrite(4, m_voxelInfoBuffer.getHandle()) };
+	secondaryBounceDescriptorWrites.uniformBufferWrites = { vkcv::BufferDescriptorWrite(4, m_voxelInfoBuffer.getHandle()) };
 	m_corePtr->writeDescriptorSet(m_secondaryBounceDescriptorSet, secondaryBounceDescriptorWrites);
 }
 
@@ -331,7 +331,7 @@ void Voxelization::renderVoxelVisualisation(
 	voxelVisualisationDescriptorWrite.storageImageWrites =
 	{ vkcv::StorageImageDescriptorWrite(0, m_voxelImage.getHandle(), mipLevel) };
 	voxelVisualisationDescriptorWrite.uniformBufferWrites =
-	{ vkcv::UniformBufferDescriptorWrite(1, m_voxelInfoBuffer.getHandle()) };
+	{ vkcv::BufferDescriptorWrite(1, m_voxelInfoBuffer.getHandle()) };
 	m_corePtr->writeDescriptorSet(m_visualisationDescriptorSet, voxelVisualisationDescriptorWrite);
 
 	uint32_t drawVoxelCount = voxelCount / exp2(mipLevel);
diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp
index e50f6b186d2081894451e005bc0ffb97dad5afe4..5763b653e5db75e150f82a0c2c293dfd5d792de3 100644
--- a/projects/voxelization/src/main.cpp
+++ b/projects/voxelization/src/main.cpp
@@ -396,6 +396,7 @@ int main(int argc, const char** argv) {
 	}
 	
 	vkcv::ImageHandle swapBuffer = core.createImage(colorBufferFormat, windowWidth, windowHeight, 1, false, true).getHandle();
+	vkcv::ImageHandle swapBuffer2 = core.createImage(colorBufferFormat, windowWidth, windowHeight, 1, false, true).getHandle();
 
 	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
 
@@ -424,6 +425,18 @@ int main(int argc, const char** argv) {
 	vkcv::PipelineHandle tonemappingPipeline = core.createComputePipeline(
 		tonemappingProgram,
 		{ core.getDescriptorSet(tonemappingDescriptorSet).layout });
+	
+	// tonemapping compute shader
+	vkcv::ShaderProgram postEffectsProgram;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "resources/shaders/postEffects.comp",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		postEffectsProgram.addShader(shaderStage, path);
+	});
+	vkcv::DescriptorSetHandle postEffectsDescriptorSet = core.createDescriptorSet(
+			postEffectsProgram.getReflectedDescriptors()[0]);
+	vkcv::PipelineHandle postEffectsPipeline = core.createComputePipeline(
+			postEffectsProgram,
+			{ core.getDescriptorSet(postEffectsDescriptorSet).layout });
 
 	// resolve compute shader
 	vkcv::ShaderProgram resolveProgram;
@@ -514,10 +527,10 @@ int main(int argc, const char** argv) {
 	// write forward pass descriptor set
 	vkcv::DescriptorWrites forwardDescriptorWrites;
 	forwardDescriptorWrites.uniformBufferWrites = {
-		vkcv::UniformBufferDescriptorWrite(0, shadowMapping.getLightInfoBuffer()),
-		vkcv::UniformBufferDescriptorWrite(3, cameraPosBuffer.getHandle()),
-		vkcv::UniformBufferDescriptorWrite(6, voxelization.getVoxelInfoBufferHandle()),
-		vkcv::UniformBufferDescriptorWrite(7, volumetricSettingsBuffer.getHandle())};
+		vkcv::BufferDescriptorWrite(0, shadowMapping.getLightInfoBuffer()),
+		vkcv::BufferDescriptorWrite(3, cameraPosBuffer.getHandle()),
+		vkcv::BufferDescriptorWrite(6, voxelization.getVoxelInfoBufferHandle()),
+		vkcv::BufferDescriptorWrite(7, volumetricSettingsBuffer.getHandle())};
 	forwardDescriptorWrites.sampledImageWrites = {
 		vkcv::SampledImageDescriptorWrite(1, shadowMapping.getShadowMap()),
 		vkcv::SampledImageDescriptorWrite(4, voxelization.getVoxelImageHandle()) };
@@ -574,16 +587,41 @@ int main(int argc, const char** argv) {
 			fsrWidth = width;
 			fsrHeight = height;
 			
-			depthBuffer         = core.createImage(depthBufferFormat, fsrWidth, fsrHeight, 1, false, false, false, msaa).getHandle();
-			colorBuffer         = core.createImage(colorBufferFormat, fsrWidth, fsrHeight, 1, false, colorBufferRequiresStorage, true, msaa).getHandle();
+			depthBuffer         = core.createImage(
+					depthBufferFormat,
+					fsrWidth, fsrHeight, 1,
+					false, false, false,
+					msaa
+			).getHandle();
+			
+			colorBuffer         = core.createImage(
+					colorBufferFormat,
+					fsrWidth, fsrHeight, 1,
+					false, colorBufferRequiresStorage, true,
+					msaa
+			).getHandle();
 
 			if (usingMsaa) {
-				resolvedColorBuffer = core.createImage(colorBufferFormat, fsrWidth, fsrHeight, 1, false, true, true).getHandle();
+				resolvedColorBuffer = core.createImage(
+						colorBufferFormat,
+						fsrWidth, fsrHeight, 1,
+						false, true, true
+				).getHandle();
 			} else {
 				resolvedColorBuffer = colorBuffer;
 			}
 			
-			swapBuffer = core.createImage(colorBufferFormat, fsrWidth, fsrHeight, 1, false, true).getHandle();
+			swapBuffer = core.createImage(
+					colorBufferFormat,
+					fsrWidth, fsrHeight, 1,
+					false, true
+			).getHandle();
+			
+			swapBuffer2 = core.createImage(
+					colorBufferFormat,
+					swapchainWidth, swapchainHeight, 1,
+					false, true
+			).getHandle();
 			
 			bloomFlares.updateImageDimensions(swapchainWidth, swapchainHeight);
 		}
@@ -598,6 +636,14 @@ int main(int argc, const char** argv) {
 		tonemappingDescriptorWrites.storageImageWrites  = { vkcv::StorageImageDescriptorWrite(2, swapBuffer) };
 
 		core.writeDescriptorSet(tonemappingDescriptorSet, tonemappingDescriptorWrites);
+		
+		// update descriptor sets which use swapchain image
+		vkcv::DescriptorWrites postEffectsDescriptorWrites;
+		postEffectsDescriptorWrites.sampledImageWrites  = { vkcv::SampledImageDescriptorWrite(0, swapBuffer2) };
+		postEffectsDescriptorWrites.samplerWrites       = { vkcv::SamplerDescriptorWrite(1, colorSampler) };
+		postEffectsDescriptorWrites.storageImageWrites  = { vkcv::StorageImageDescriptorWrite(2, swapchainInput) };
+		
+		core.writeDescriptorSet(postEffectsDescriptorSet, postEffectsDescriptorWrites);
 
 		// update resolve descriptor, color images could be changed
 		vkcv::DescriptorWrites resolveDescriptorWrites;
@@ -699,11 +745,18 @@ int main(int argc, const char** argv) {
 			renderTargets);
 
 		const uint32_t fullscreenLocalGroupSize = 8;
-		const uint32_t fulsscreenDispatchCount[3] = {
-			static_cast<uint32_t>(glm::ceil(fsrWidth  / static_cast<float>(fullscreenLocalGroupSize))),
-			static_cast<uint32_t>(glm::ceil(fsrHeight / static_cast<float>(fullscreenLocalGroupSize))),
-			1
-		};
+		
+		uint32_t fulsscreenDispatchCount [3];
+		
+		fulsscreenDispatchCount[0] = static_cast<uint32_t>(
+				glm::ceil(fsrWidth  / static_cast<float>(fullscreenLocalGroupSize))
+		);
+		
+		fulsscreenDispatchCount[1] = static_cast<uint32_t>(
+				glm::ceil(fsrHeight / static_cast<float>(fullscreenLocalGroupSize))
+		);
+		
+		fulsscreenDispatchCount[2] = 1;
 
 		if (usingMsaa) {
 			if (msaaCustomResolve) {
@@ -732,12 +785,6 @@ int main(int argc, const char** argv) {
 
 		core.prepareImageForStorage(cmdStream, swapBuffer);
 		core.prepareImageForSampling(cmdStream, resolvedColorBuffer);
-
-		auto timeSinceStart = std::chrono::duration_cast<std::chrono::microseconds>(end - appStartTime);
-		float timeF         = static_cast<float>(timeSinceStart.count()) * 0.01f;
-
-		vkcv::PushConstants timePushConstants (sizeof(timeF));
-		timePushConstants.appendDrawcall(timeF);
 		
 		core.recordComputeDispatchToCmdStream(
 			cmdStream, 
@@ -746,21 +793,48 @@ int main(int argc, const char** argv) {
 			{ vkcv::DescriptorSetUsage(0, core.getDescriptorSet(
 					tonemappingDescriptorSet
 			).vulkanHandle) },
-			timePushConstants
+			vkcv::PushConstants(0)
 		);
 		
-		core.prepareImageForStorage(cmdStream, swapchainInput);
+		core.prepareImageForStorage(cmdStream, swapBuffer2);
 		core.prepareImageForSampling(cmdStream, swapBuffer);
 		
 		if (fsrFactor <= 1.0f) {
 			upscaling.setSharpness(0.0f);
 		}
 		
-		upscaling.recordUpscaling(cmdStream, swapBuffer, swapchainInput);
+		upscaling.recordUpscaling(cmdStream, swapBuffer, swapBuffer2);
 		
 		if (fsrFactor <= 1.0f) {
 			upscaling.setSharpness(rcasSharpness);
 		}
+		
+		core.prepareImageForStorage(cmdStream, swapchainInput);
+		core.prepareImageForSampling(cmdStream, swapBuffer2);
+		
+		auto timeSinceStart = std::chrono::duration_cast<std::chrono::microseconds>(end - appStartTime);
+		float timeF         = static_cast<float>(timeSinceStart.count()) * 0.01f;
+		
+		vkcv::PushConstants timePushConstants (sizeof(timeF));
+		timePushConstants.appendDrawcall(timeF);
+		
+		fulsscreenDispatchCount[0] = static_cast<uint32_t>(
+				glm::ceil(swapchainWidth  / static_cast<float>(fullscreenLocalGroupSize))
+		);
+		
+		fulsscreenDispatchCount[1] = static_cast<uint32_t>(
+				glm::ceil(swapchainHeight / static_cast<float>(fullscreenLocalGroupSize))
+		);
+		
+		core.recordComputeDispatchToCmdStream(
+				cmdStream,
+				postEffectsPipeline,
+				fulsscreenDispatchCount,
+				{ vkcv::DescriptorSetUsage(0, core.getDescriptorSet(
+						postEffectsDescriptorSet
+				).vulkanHandle) },
+				timePushConstants
+		);
 
 		// present and end
 		core.prepareSwapchainImageForPresent(cmdStream);
diff --git a/src/vkcv/DescriptorManager.cpp b/src/vkcv/DescriptorManager.cpp
index 8afd50a3ba8259a487a71e578f60459afa7192cf..0df359a15883847c132c429ed2945ac7624fb865 100644
--- a/src/vkcv/DescriptorManager.cpp
+++ b/src/vkcv/DescriptorManager.cpp
@@ -156,10 +156,15 @@ namespace vkcv
 		}
 
 		for (const auto& write : writes.uniformBufferWrites) {
+			const size_t size = bufferManager.getBufferSize(write.buffer);
+			const uint32_t offset = std::clamp<uint32_t>(write.offset, 0, size);
+			
 			const vk::DescriptorBufferInfo bufferInfo(
 				bufferManager.getBuffer(write.buffer),
-				static_cast<uint32_t>(0),
-				bufferManager.getBufferSize(write.buffer)
+				offset,
+				write.size == 0? size : std::min<uint32_t>(
+						write.size, size - offset
+				)
 			);
 			
 			bufferInfos.push_back(bufferInfo);
@@ -177,10 +182,15 @@ namespace vkcv
 		}
 
 		for (const auto& write : writes.storageBufferWrites) {
+			const size_t size = bufferManager.getBufferSize(write.buffer);
+			const uint32_t offset = std::clamp<uint32_t>(write.offset, 0, size);
+			
 			const vk::DescriptorBufferInfo bufferInfo(
 				bufferManager.getBuffer(write.buffer),
-				static_cast<uint32_t>(0),
-				bufferManager.getBufferSize(write.buffer)
+				offset,
+				write.size == 0? size : std::min<uint32_t>(
+						write.size, size - offset
+				)
 			);
 			
 			bufferInfos.push_back(bufferInfo);