From c94a4178f17e7c1edecb3d4e06e5cc521f20d32d Mon Sep 17 00:00:00 2001
From: Tobias Frisch <tfrisch@uni-koblenz.de>
Date: Fri, 14 Oct 2022 18:19:34 +0200
Subject: [PATCH] Add screen-space ambient occlusion demo

Signed-off-by: Tobias Frisch <tfrisch@uni-koblenz.de>
---
 demos/CMakeLists.txt                     |   1 +
 demos/SSAO/.gitignore                    |   1 +
 demos/SSAO/CMakeLists.txt                |  15 +
 demos/SSAO/shaders/GBuffer.frag          |  37 ++
 demos/SSAO/shaders/GBuffer.vert          |  27 +
 demos/SSAO/shaders/ambientOcclusion.frag |  84 +++
 demos/SSAO/shaders/blur1D.frag           |  33 ++
 demos/SSAO/shaders/finalCompositing.frag |  37 ++
 demos/SSAO/shaders/lighting.frag         |  53 ++
 demos/SSAO/shaders/lighting.vert         |  33 ++
 demos/SSAO/shaders/screenFill.vert       |  14 +
 demos/SSAO/src/main.cpp                  | 701 +++++++++++++++++++++++
 framework                                |   2 +-
 13 files changed, 1037 insertions(+), 1 deletion(-)
 create mode 100644 demos/SSAO/.gitignore
 create mode 100644 demos/SSAO/CMakeLists.txt
 create mode 100644 demos/SSAO/shaders/GBuffer.frag
 create mode 100644 demos/SSAO/shaders/GBuffer.vert
 create mode 100644 demos/SSAO/shaders/ambientOcclusion.frag
 create mode 100644 demos/SSAO/shaders/blur1D.frag
 create mode 100644 demos/SSAO/shaders/finalCompositing.frag
 create mode 100644 demos/SSAO/shaders/lighting.frag
 create mode 100644 demos/SSAO/shaders/lighting.vert
 create mode 100644 demos/SSAO/shaders/screenFill.vert
 create mode 100644 demos/SSAO/src/main.cpp

diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt
index 248a547..ed9ad7d 100644
--- a/demos/CMakeLists.txt
+++ b/demos/CMakeLists.txt
@@ -2,3 +2,4 @@
 add_subdirectory(CubeMapping)
 add_subdirectory(InstancingDemo)
 add_subdirectory(NormalMapping)
+add_subdirectory(SSAO)
diff --git a/demos/SSAO/.gitignore b/demos/SSAO/.gitignore
new file mode 100644
index 0000000..a6dcbba
--- /dev/null
+++ b/demos/SSAO/.gitignore
@@ -0,0 +1 @@
+SSAO
\ No newline at end of file
diff --git a/demos/SSAO/CMakeLists.txt b/demos/SSAO/CMakeLists.txt
new file mode 100644
index 0000000..964bc93
--- /dev/null
+++ b/demos/SSAO/CMakeLists.txt
@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 3.16)
+project(SSAO)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# adding source files to the project
+add_project(SSAO src/main.cpp)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(SSAO SYSTEM BEFORE PRIVATE ${vkcv_includes})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(SSAO ${vkcv_libraries})
diff --git a/demos/SSAO/shaders/GBuffer.frag b/demos/SSAO/shaders/GBuffer.frag
new file mode 100644
index 0000000..ff16f61
--- /dev/null
+++ b/demos/SSAO/shaders/GBuffer.frag
@@ -0,0 +1,37 @@
+#version 450
+
+//incoming data for the single textures
+layout(location = 0) in vec4 passPosition;
+layout(location = 1) in vec3 passNormal;
+layout(location = 2) in vec2 passUVCoord;
+
+struct Material {
+	vec3 diffuse;
+	float alpha;
+};
+
+layout(set=0, binding=1) uniform materialBuffer {
+	Material mat;
+};
+
+layout(set=0, binding=2) uniform texturePropsBuffer {
+	int useColorTexture;
+};
+
+layout(set=0, binding=3) uniform texture2D colorTexture;
+layout(set=0, binding=4) uniform sampler colorSampler;
+
+//writable textures for deferred screen space calculations
+layout(location = 0) out vec4 positionOutput;
+layout(location = 1) out vec4 normalOutput;
+layout(location = 2) out vec4 colorOutput;
+ 
+void main() {
+    positionOutput = passPosition;
+    normalOutput = vec4(normalize(passNormal), 1);
+	colorOutput = vec4(mat.diffuse, mat.alpha);
+
+	if (useColorTexture != 0) {
+		colorOutput = texture(sampler2D(colorTexture, colorSampler), passUVCoord);
+	}
+}
diff --git a/demos/SSAO/shaders/GBuffer.vert b/demos/SSAO/shaders/GBuffer.vert
new file mode 100644
index 0000000..3d6a665
--- /dev/null
+++ b/demos/SSAO/shaders/GBuffer.vert
@@ -0,0 +1,27 @@
+#version 450
+
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec3 normal;
+layout(location = 2) in vec2 uv;
+
+layout(set=0, binding=0) uniform matrixBuffer {
+    mat4 viewMatrix;
+    mat4 projectionMatrix;
+};
+
+layout( push_constant ) uniform constants {
+    mat4 modelMatrix;
+};
+
+layout(location = 0) out vec4 passPosition;
+layout(location = 1) out vec3 passNormal;
+layout(location = 2) out vec2 passUVCoord;
+
+void main() {
+    passUVCoord = vec2(uv.x, 1.0f - uv.y);
+
+    passPosition = viewMatrix * modelMatrix * vec4(position, 1);
+    gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1);
+
+    passNormal = vec3(transpose(inverse(viewMatrix * modelMatrix)) * vec4(normal, 0));
+}
diff --git a/demos/SSAO/shaders/ambientOcclusion.frag b/demos/SSAO/shaders/ambientOcclusion.frag
new file mode 100644
index 0000000..820a59e
--- /dev/null
+++ b/demos/SSAO/shaders/ambientOcclusion.frag
@@ -0,0 +1,84 @@
+#version 450
+
+layout(location = 0) in vec2 passUVCoord;
+
+layout(set=0, binding=0) uniform matrixBuffer {
+	mat4 viewMatrix;
+	mat4 projectionMatrix;
+};
+
+layout(set=0, binding=1) uniform texture2D positionTexture;
+layout(set=0, binding=2) uniform texture2D normalTexture;
+layout(set=0, binding=3) uniform sampler texSampler;
+
+layout( push_constant ) uniform constants {
+	float radius;
+	float quality;
+};
+
+layout(location = 0) out vec4 fragmentColor;
+
+float rand(vec2 co){
+    return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
+}
+
+vec3 rand3(int i, vec3 v, vec2 v2){
+	float x = rand(vec2(v.x + i*3+0, v.x + ((i*3+0)*1531)%113) + v2*19789)*2 - 1.0;
+	float y = rand(vec2(v.y + i*3+1, v.y + ((i*3+1)*1531)%113) + v2*19789)*2 - 1.0;
+	float z = rand(vec2(v.z + i*3+2, v.z + ((i*3+2)*1531)%113) + v2*19789)*2 - 1.0;
+	return vec3(x,y,z);
+}
+
+void main() {
+	vec3 pSphere[16] = vec3[](
+			vec3(0.53812504, 0.18565957, -0.43192),
+			vec3(0.13790712, 0.24864247, 0.44301823),
+			vec3(0.33715037, 0.56794053, -0.005789503),
+			vec3(-0.6999805, -0.04511441, -0.0019965635),
+			vec3(0.06896307, -0.15983082, -0.85477847),
+			vec3(0.056099437, 0.006954967, -0.1843352),
+			vec3(-0.014653638, 0.14027752, 0.0762037),
+			vec3(0.010019933, -0.1924225, -0.034443386),
+			vec3(-0.35775623, -0.5301969, -0.43581226),
+			vec3(-0.3169221, 0.106360726, 0.015860917),
+			vec3(0.010350345, -0.58698344, 0.0046293875),
+			vec3(-0.08972908, -0.49408212, 0.3287904),
+			vec3(0.7119986, -0.0154690035, -0.09183723),
+			vec3(-0.053382345, 0.059675813, -0.5411899),
+			vec3(0.035267662, -0.063188605, 0.54602677),
+			vec3(-0.47761092, 0.2847911, -0.0271716)
+	);
+
+	vec4 position = texture(sampler2D(positionTexture, texSampler), passUVCoord);
+	vec3 pos = position.xyz;
+	vec3 normal = normalize(texture(sampler2D(normalTexture, texSampler), passUVCoord).rgb);// + vec3(0,0,0.00001));
+	float d = pos.z + 0.001;
+	
+	fragmentColor = vec4(0.0, 0.0, 0.0, 0.0);
+	if (position.w < 0.5) {
+		return;
+	}
+	
+	float occ = 1.0;
+	int j=0;
+
+	for (int i = 0; i < quality; i++) {
+		vec3 v = rand3(i + j, pSphere[i%16], passUVCoord);
+		v = sign(dot(v, normal)) * v;
+		v = pos + v * radius;
+		
+		vec4 diff = projectionMatrix * vec4(v, 1);
+		diff = (diff / diff.w) * 0.5f + 0.5f;
+		
+		vec4 td = texture(sampler2D(positionTexture, texSampler), diff.xy);
+		if (td.w < 0.5) {
+			continue;
+		}
+		
+		if (td.z > v.z) {
+			occ -= 1.0 / quality;
+		}
+	}
+
+	fragmentColor = vec4(vec3(1.0f - occ), 1.0);
+}
diff --git a/demos/SSAO/shaders/blur1D.frag b/demos/SSAO/shaders/blur1D.frag
new file mode 100644
index 0000000..5c61426
--- /dev/null
+++ b/demos/SSAO/shaders/blur1D.frag
@@ -0,0 +1,33 @@
+#version 450
+
+layout(location = 0) in vec2 passUVCoord;
+
+layout(set=0, binding=0) uniform texture2D colorTexture;
+layout(set=0, binding=1) uniform sampler colorSampler;
+
+layout( push_constant ) uniform constants {
+	float yAxis;
+};
+
+layout(location = 0) out vec4 fragmentColor;
+
+void main() {
+	vec4 weights = vec4(2.0, 3.0, 2.0, 1.0);
+    
+	vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);
+	vec2 diff = vec2(1.0, 0.0);
+
+	if (yAxis > 0.5) {
+		diff = vec2(0.0, 1.0);
+	}
+
+	diff *= (1.0 / 800.0);
+	
+	for(int i=0; i<4; i++)
+	{
+		sum += weights[i] * texture(sampler2D(colorTexture, colorSampler), passUVCoord + i*diff);
+		sum += weights[i] * texture(sampler2D(colorTexture, colorSampler), passUVCoord - i*diff);
+	}
+	
+	fragmentColor = sum / 16.0;
+}
\ No newline at end of file
diff --git a/demos/SSAO/shaders/finalCompositing.frag b/demos/SSAO/shaders/finalCompositing.frag
new file mode 100644
index 0000000..d380540
--- /dev/null
+++ b/demos/SSAO/shaders/finalCompositing.frag
@@ -0,0 +1,37 @@
+#version 450
+
+layout(location = 0) in vec2 passUVCoord;
+
+layout(set=0, binding=0) uniform texture2D lightTexture;
+layout(set=0, binding=1) uniform texture2D colorTexture;
+layout(set=0, binding=2) uniform texture2D ssaoTexture;
+layout(set=0, binding=3) uniform sampler texSampler;
+
+layout( push_constant ) uniform constants {
+	float useLight;
+	float useSSAO;
+};
+
+layout(location = 0) out vec4 fragmentColor;
+
+void main() {
+    vec4 light = texture(sampler2D(lightTexture, texSampler), passUVCoord);
+    vec4 color = texture(sampler2D(colorTexture, texSampler), passUVCoord);
+    vec4 ssao = texture(sampler2D(ssaoTexture, texSampler), passUVCoord);
+
+    fragmentColor = color;
+
+	if (useLight > 0.5) {
+		fragmentColor *= light;
+	}
+
+	if (color.a < 0.5) {
+		fragmentColor = vec4(0.4,0.4,0.4,1.0);
+	}
+
+	if (useSSAO > 0.5) {
+		fragmentColor *= (ssao * 0.7 + 0.3);
+	}
+
+    //fragmentColor = color;
+}
\ No newline at end of file
diff --git a/demos/SSAO/shaders/lighting.frag b/demos/SSAO/shaders/lighting.frag
new file mode 100644
index 0000000..f2f5b8d
--- /dev/null
+++ b/demos/SSAO/shaders/lighting.frag
@@ -0,0 +1,53 @@
+#version 450
+
+layout(location = 0) in vec4 passPosition;
+layout(location = 1) in vec4 midPosition;
+
+layout(location = 2) in vec4 color;
+
+layout(set=0, binding=0) uniform matrixBuffer {
+	mat4 viewMatrix;
+	mat4 projectionMatrix;
+};
+
+layout(set=0, binding=1) uniform texture2D positionTexture;
+layout(set=0, binding=2) uniform texture2D normalTexture;
+layout(set=0, binding=3) uniform sampler texSampler;
+
+layout(set=0, binding=4) uniform resolutionBuffer {
+	vec2 resolution;
+};
+
+layout(location = 0) out vec4 lightingOutput;
+
+void main() {
+	vec2 uv = gl_FragCoord.xy / resolution;
+    vec4 pos = texture(sampler2D(positionTexture, texSampler), uv);
+	vec3 nor = texture(sampler2D(normalTexture, texSampler), uv).xyz;
+	
+	if (pos.w < 0.5) {
+		discard;
+	}
+	
+	float diffuseStrength = max(2.0f - length(pos - midPosition), 0.0f);
+	float specularStrength = clamp(diffuseStrength * diffuseStrength, 0.0f, 1.0f);
+
+	if (diffuseStrength < 0) {
+		discard;
+	}
+
+    vec3 nPosToLight = normalize(vec3(midPosition.xyz - pos.xyz));
+
+    vec3  reflection = normalize(reflect(-nPosToLight,nor.xyz));
+    float diffuse = max(dot(nor.xyz, nPosToLight), 0);
+    float specular = pow(max(dot(reflection, -normalize(pos.xyz)),0),50);
+	
+	lightingOutput = vec4(passPosition.xyz - midPosition.xyz, 1.0);
+	lightingOutput = vec4(nor.xyz, 1.0);
+	
+    lightingOutput = color * (
+		diffuseStrength * vec4(vec3(diffuse), 1) * 0.7 +
+		specularStrength * vec4(1) * specular
+	);
+}
+
diff --git a/demos/SSAO/shaders/lighting.vert b/demos/SSAO/shaders/lighting.vert
new file mode 100644
index 0000000..8e27435
--- /dev/null
+++ b/demos/SSAO/shaders/lighting.vert
@@ -0,0 +1,33 @@
+#version 450
+
+layout(location = 0) in vec3 position;
+
+layout(set=0, binding=0) uniform matrixBuffer {
+	mat4 viewMatrix;
+	mat4 projectionMatrix;
+};
+
+layout( push_constant ) uniform constants {
+	mat4 modelMatrix;
+};
+
+layout(location = 0) out vec4 passPosition;
+layout(location = 1) out vec4 midPosition;
+
+layout(location = 2) out vec4 color;
+
+void main() {
+	vec3 c = (modelMatrix * vec4(0,0,0,1)).xyz;
+	float x = c.x + 2;
+	float y = c.y + 2;
+	float z = c.z + 2;
+	c = vec3(x * 2.3 + y * 5.6 + z * 4.4, x * 3.1 + y * 7.2 + z, x * 4.3 + y + z * 6.9);
+	c = sin(c);
+	c = c / 2.0 + 1.0;
+	c = normalize(c);
+	color = vec4(c, 1.0);
+
+    passPosition = viewMatrix * modelMatrix * vec4(position, 1);
+    midPosition = viewMatrix * modelMatrix * vec4(0,0,0,1);
+    gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1);
+}
\ No newline at end of file
diff --git a/demos/SSAO/shaders/screenFill.vert b/demos/SSAO/shaders/screenFill.vert
new file mode 100644
index 0000000..e32d3ef
--- /dev/null
+++ b/demos/SSAO/shaders/screenFill.vert
@@ -0,0 +1,14 @@
+#version 450
+
+vec2 positions[3] = {
+    vec2(0, 0),
+    vec2(0, 2),
+    vec2(2, 0)
+};
+
+layout(location = 0) out vec2 passUVCoord;
+
+void main() {
+    gl_Position = vec4(positions[gl_VertexIndex] * 2.0f - 1.0f, 1, 1);
+    passUVCoord = positions[gl_VertexIndex];
+}
\ No newline at end of file
diff --git a/demos/SSAO/src/main.cpp b/demos/SSAO/src/main.cpp
new file mode 100644
index 0000000..9f136eb
--- /dev/null
+++ b/demos/SSAO/src/main.cpp
@@ -0,0 +1,701 @@
+
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Core.hpp>
+#include <vkcv/DescriptorWrites.hpp>
+#include <vkcv/Image.hpp>
+#include <vkcv/Pass.hpp>
+#include <vkcv/Sampler.hpp>
+#include <vkcv/Window.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/camera/CameraManager.hpp>
+#include <vkcv/geometry/Cuboid.hpp>
+#include <vkcv/geometry/Sphere.hpp>
+#include <vkcv/gui/GUI.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+
+struct Material {
+	glm::vec3 diffuse;
+	float alpha;
+};
+
+struct TextureProps {
+	int useColorTexture;
+};
+
+int main(int argc, const char** argv) {
+	const std::string applicationName = "SSAO";
+	
+	vkcv::Features features;
+	features.requireExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+	features.requireExtensionFeature<vk::PhysicalDeviceShaderTerminateInvocationFeatures>(
+			VK_KHR_SHADER_TERMINATE_INVOCATION_EXTENSION_NAME,
+			[](vk::PhysicalDeviceShaderTerminateInvocationFeatures& features) {
+				features.setShaderTerminateInvocation(true);
+			}
+	);
+	
+	vkcv::Core core = vkcv::Core::create(
+			applicationName,
+			VK_MAKE_VERSION(0, 0, 1),
+			{ vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eTransfer },
+			features
+	);
+	
+	vkcv::WindowHandle windowHandle = core.createWindow(applicationName, 800, 800, true);
+	vkcv::Window& window = core.getWindow(windowHandle);
+	
+	vkcv::camera::CameraManager cameraManager (window);
+	auto camHandle = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+	
+	cameraManager.getCamera(camHandle).setNearFar(0.1f, 100.0f);
+	
+	vkcv::geometry::Cuboid plane (glm::vec3(0.0f), glm::vec3(1.0f, 0.01f, 1.0f));
+	
+	vkcv::geometry::Sphere sphere (glm::vec3(0.0f), 1.0f);
+	sphere.setResolution(20);
+	
+	vkcv::shader::GLSLCompiler compiler;
+	vkcv::ShaderProgram shaderProgram, lightProgram, aoProgram, blurProgram, compositeProgram;
+	
+	compiler.compileProgram(
+			shaderProgram,
+			{
+					{ vkcv::ShaderStage::VERTEX, "shaders/GBuffer.vert" },
+					{ vkcv::ShaderStage::FRAGMENT, "shaders/GBuffer.frag" }
+			},
+			nullptr
+	);
+	
+	compiler.compileProgram(
+			lightProgram,
+			{
+					{ vkcv::ShaderStage::VERTEX, "shaders/lighting.vert" },
+					{ vkcv::ShaderStage::FRAGMENT, "shaders/lighting.frag" }
+			},
+			nullptr
+	);
+	
+	compiler.compileProgram(
+			aoProgram,
+			{
+					{ vkcv::ShaderStage::VERTEX, "shaders/screenFill.vert" },
+					{ vkcv::ShaderStage::FRAGMENT, "shaders/ambientOcclusion.frag" }
+			},
+			nullptr
+	);
+	
+	compiler.compileProgram(
+			blurProgram,
+			{
+					{ vkcv::ShaderStage::VERTEX, "shaders/screenFill.vert" },
+					{ vkcv::ShaderStage::FRAGMENT, "shaders/blur1D.frag" }
+			},
+			nullptr
+	);
+	
+	compiler.compileProgram(
+			compositeProgram,
+			{
+					{ vkcv::ShaderStage::VERTEX, "shaders/screenFill.vert" },
+					{ vkcv::ShaderStage::FRAGMENT, "shaders/finalCompositing.frag" }
+			},
+			nullptr
+	);
+	
+	const vkcv::PassConfig gPassConfig ({
+		vkcv::AttachmentDescription(
+				vk::Format::eR32G32B32A32Sfloat,
+				vkcv::AttachmentOperation::CLEAR,
+				vkcv::AttachmentOperation::STORE,
+				vk::ClearValue(vk::ClearColorValue(std::array<float, 4>{
+						0.0f, 0.0f, 0.0f, 0.0f
+				}))
+		),
+		vkcv::AttachmentDescription(
+				vk::Format::eR32G32B32A32Sfloat,
+				vkcv::AttachmentOperation::CLEAR,
+				vkcv::AttachmentOperation::STORE,
+				vk::ClearValue(vk::ClearColorValue(std::array<float, 4>{
+						0.5f, 0.5f, 0.5f, 0.5f
+				}))
+		),
+		vkcv::AttachmentDescription(
+				core.getSwapchainFormat(window.getSwapchain()),
+				vkcv::AttachmentOperation::CLEAR,
+				vkcv::AttachmentOperation::STORE,
+				vk::ClearValue(vk::ClearColorValue(std::array<float, 4>{
+						1.0f, 1.0f, 1.0f, 1.0f
+				}))
+		),
+		vkcv::AttachmentDescription(
+				vk::Format::eD32Sfloat,
+				vkcv::AttachmentOperation::CLEAR,
+				vkcv::AttachmentOperation::STORE
+		)
+	});
+	
+	vkcv::PassHandle gBufferPass = core.createPass(gPassConfig);
+	
+	vkcv::PassHandle gBufferPositionPass = core.createPass(vkcv::PassConfig(
+			{
+					vkcv::AttachmentDescription(
+							core.getSwapchainFormat(window.getSwapchain()),
+							vkcv::AttachmentOperation::CLEAR,
+							vkcv::AttachmentOperation::STORE,
+							vk::ClearValue(vk::ClearColorValue(std::array<float, 4>{
+									0.0f, 0.0f, 0.0f, 0.0f
+							}))
+					),
+					gPassConfig.getAttachments()[1],
+					gPassConfig.getAttachments()[2],
+					gPassConfig.getAttachments()[3]
+			}
+	));
+	
+	vkcv::PassHandle gBufferNormalPass = core.createPass(vkcv::PassConfig(
+			{
+					gPassConfig.getAttachments()[0],
+					vkcv::AttachmentDescription(
+							core.getSwapchainFormat(window.getSwapchain()),
+							vkcv::AttachmentOperation::CLEAR,
+							vkcv::AttachmentOperation::STORE,
+							vk::ClearValue(vk::ClearColorValue(std::array<float, 4>{
+									0.5f, 0.5f, 0.5f, 0.5f
+							}))
+					),
+					gPassConfig.getAttachments()[2],
+					gPassConfig.getAttachments()[3]
+			}
+	));
+	
+	vkcv::PassHandle simplePass = vkcv::passSwapchain(core, window.getSwapchain(), {
+		vk::Format::eUndefined
+	});
+	
+	const vkcv::VertexLayout vertexLayout {
+			vkcv::createVertexBindings(shaderProgram.getVertexAttachments())
+	};
+	
+	const vkcv::DescriptorBindings descriptorBindings = (
+			shaderProgram.getReflectedDescriptors().at(0)
+	);
+	
+	auto descriptorSetLayout = core.createDescriptorSetLayout(descriptorBindings);
+	auto descriptorSet = core.createDescriptorSet(descriptorSetLayout);
+	
+	vkcv::GraphicsPipelineHandle gBufferPipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					shaderProgram,
+					gBufferPass,
+					vertexLayout,
+					{ descriptorSetLayout }
+			)
+	);
+	
+	vkcv::GraphicsPipelineHandle gBufferPosPipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					shaderProgram,
+					gBufferPositionPass,
+					vertexLayout,
+					{ descriptorSetLayout }
+			)
+	);
+	
+	vkcv::GraphicsPipelineHandle gBufferNorPipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					shaderProgram,
+					gBufferNormalPass,
+					vertexLayout,
+					{ descriptorSetLayout }
+			)
+	);
+	
+	const vkcv::VertexLayout lightVertexLayout {
+			vkcv::createVertexBindings(lightProgram.getVertexAttachments())
+	};
+	
+	const vkcv::DescriptorBindings lightDescSetBindings = (
+			lightProgram.getReflectedDescriptors().at(0)
+	);
+	
+	auto lightDescSetLayout = core.createDescriptorSetLayout(lightDescSetBindings);
+	auto lightDescSet = core.createDescriptorSet(lightDescSetLayout);
+	
+	vkcv::GraphicsPipelineConfig lightPipelineConfig (
+			lightProgram,
+			simplePass,
+			lightVertexLayout,
+			{ lightDescSetLayout }
+	);
+	
+	lightPipelineConfig.setBlendMode(vkcv::BlendMode::Additive);
+	
+	vkcv::GraphicsPipelineHandle lightPipeline = core.createGraphicsPipeline(
+			lightPipelineConfig
+	);
+	
+	const vkcv::DescriptorBindings aoDescSetBindings = (
+			aoProgram.getReflectedDescriptors().at(0)
+	);
+	
+	auto aoDescSetLayout = core.createDescriptorSetLayout(aoDescSetBindings);
+	auto aoDescSet = core.createDescriptorSet(aoDescSetLayout);
+	
+	vkcv::GraphicsPipelineHandle aoPipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					aoProgram,
+					simplePass,
+					{},
+					{ aoDescSetLayout }
+			)
+	);
+	
+	const vkcv::DescriptorBindings blurDescSetBindings = (
+			blurProgram.getReflectedDescriptors().at(0)
+	);
+	
+	auto blurDescSetLayout = core.createDescriptorSetLayout(blurDescSetBindings);
+	auto blurDescSet = core.createDescriptorSet(blurDescSetLayout);
+	
+	vkcv::GraphicsPipelineHandle blurPipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					blurProgram,
+					simplePass,
+					{},
+					{ blurDescSetLayout }
+			)
+	);
+	
+	const vkcv::DescriptorBindings compositeDescSetBindings = (
+			compositeProgram.getReflectedDescriptors().at(0)
+	);
+	
+	auto compositeDescSetLayout = core.createDescriptorSetLayout(compositeDescSetBindings);
+	auto compositeDescSet = core.createDescriptorSet(compositeDescSetLayout);
+	
+	vkcv::GraphicsPipelineHandle compositePipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					compositeProgram,
+					simplePass,
+					{},
+					{ compositeDescSetLayout }
+			)
+	);
+	
+	auto matrixBuffer = vkcv::buffer<glm::mat4>(
+			core,
+			vkcv::BufferType::UNIFORM,
+			2,
+			vkcv::BufferMemoryType::HOST_VISIBLE
+	);
+	
+	auto materialBuffer = vkcv::buffer<Material>(
+			core,
+			vkcv::BufferType::UNIFORM,
+			1
+	);
+	
+	Material material;
+	material.diffuse = glm::vec3(1.0f, 0.0f, 0.0f);
+	material.alpha = 1.0f;
+	materialBuffer.fill(&material, 1);
+	
+	auto texturePropsBuffer = vkcv::buffer<TextureProps>(
+			core,
+			vkcv::BufferType::UNIFORM,
+			1,
+			vkcv::BufferMemoryType::HOST_VISIBLE
+	);
+	
+	vkcv::asset::Texture cv_logo = vkcv::asset::loadTexture("../../resources/cv_logo.png");
+	
+	vkcv::Image cv_logoImage = vkcv::image(core, vk::Format::eR8G8B8A8Unorm, cv_logo.w, cv_logo.h);
+	cv_logoImage.fill(cv_logo.data.data());
+	
+	vkcv::SamplerHandle sampler = vkcv::samplerLinear(core);
+	
+	auto resolutionBuffer = vkcv::buffer<glm::vec2>(
+			core,
+			vkcv::BufferType::UNIFORM,
+			1,
+			vkcv::BufferMemoryType::HOST_VISIBLE
+	);
+	
+	int sizeX = 7, sizeZ = 7;
+	vkcv::PushConstants pushConstants = vkcv::pushConstants<glm::mat4>();
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeUniformBuffer(0, matrixBuffer.getHandle());
+		writes.writeUniformBuffer(1, materialBuffer.getHandle());
+		writes.writeUniformBuffer(2, texturePropsBuffer.getHandle());
+		writes.writeSampledImage(3, cv_logoImage.getHandle());
+		writes.writeSampler(4, sampler);
+		core.writeDescriptorSet(descriptorSet, writes);
+	}
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeUniformBuffer(0, matrixBuffer.getHandle());
+		writes.writeSampler(3, sampler);
+		core.writeDescriptorSet(aoDescSet, writes);
+		
+		writes.writeUniformBuffer(4, resolutionBuffer.getHandle());
+		core.writeDescriptorSet(lightDescSet, writes);
+	}
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeSampler(1, sampler);
+		core.writeDescriptorSet(blurDescSet, writes);
+	}
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeSampler(3, sampler);
+		core.writeDescriptorSet(compositeDescSet, writes);
+	}
+	
+	vkcv::gui::GUI gui (core, windowHandle);
+	
+	bool useColorTexture = true;
+	int showBuffer = 0;
+	bool useBlurPass = false;
+	
+	const std::vector<const char*> showBufferNames = {
+			"Composition",
+			"Position",
+			"Normal",
+			"Color",
+			"Light",
+			"Ambient Occlusion"
+	};
+	
+	float ssaoRadius = 0.2f;
+	float ssaoQuality = 16.0f;
+	
+	bool useLighting = true;
+	bool useSSAO = true;
+	
+	vkcv::InstanceDrawcall planeDrawcall (plane.generateVertexData(core));
+	planeDrawcall.useDescriptorSet(0, descriptorSet);
+	
+	vkcv::InstanceDrawcall sphereDrawcall (sphere.generateVertexData(core));
+	sphereDrawcall.useDescriptorSet(0, descriptorSet);
+	
+	std::vector<vkcv::InstanceDrawcall> lightDrawcalls;
+	lightDrawcalls.reserve(100);
+	
+	sphere.setRadius(3.0f);
+	vkcv::VertexData lightSphereData = sphere.generateVertexData(core);
+	
+	for (size_t i = 0; i < 100; i++) {
+		vkcv::InstanceDrawcall lightDrawcall (lightSphereData);
+		lightDrawcall.useDescriptorSet(0, lightDescSet);
+		lightDrawcalls.push_back(lightDrawcall);
+	}
+	
+	vkcv::VertexData simpleVertexData;
+	simpleVertexData.setCount(3);
+	
+	vkcv::InstanceDrawcall aoDrawcall (simpleVertexData);
+	aoDrawcall.useDescriptorSet(0, aoDescSet);
+	
+	vkcv::InstanceDrawcall blurDrawcall (simpleVertexData);
+	blurDrawcall.useDescriptorSet(0, blurDescSet);
+	
+	vkcv::InstanceDrawcall compositeDrawcall (simpleVertexData);
+	compositeDrawcall.useDescriptorSet(0, compositeDescSet);
+	
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+	
+	vkcv::ImageHandle positionBuffer;
+	vkcv::ImageHandle normalBuffer;
+	vkcv::ImageHandle colorBuffer;
+	vkcv::ImageHandle depthBuffer;
+	
+	vkcv::ImageHandle lightBuffer;
+	vkcv::ImageHandle aoBuffer;
+	vkcv::ImageHandle blurBuffer;
+	
+	glm::mat4* mvp = matrixBuffer.map();
+	TextureProps* textureProps = texturePropsBuffer.map();
+	glm::vec2* res = resolutionBuffer.map();
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle,
+				 double t,
+				 double dt,
+				 uint32_t swapchainWidth,
+				 uint32_t swapchainHeight
+	) {
+		vkcv::ImageConfig bufferConfig (
+				swapchainWidth,
+				swapchainHeight
+		);
+		
+		bufferConfig.setSupportingColorAttachment(true);
+		
+		if ((!positionBuffer) ||
+			(swapchainWidth != core.getImageWidth(positionBuffer)) ||
+			(swapchainHeight != core.getImageHeight(positionBuffer))) {
+			positionBuffer = core.createImage(
+					vk::Format::eR32G32B32A32Sfloat,
+					bufferConfig
+			);
+			
+			vkcv::DescriptorWrites writes;
+			writes.writeSampledImage(1, positionBuffer);
+			core.writeDescriptorSet(lightDescSet, writes);
+			core.writeDescriptorSet(aoDescSet, writes);
+		}
+		
+		if ((!normalBuffer) ||
+			(swapchainWidth != core.getImageWidth(normalBuffer)) ||
+			(swapchainHeight != core.getImageHeight(normalBuffer))) {
+			normalBuffer = core.createImage(
+					vk::Format::eR32G32B32A32Sfloat,
+					bufferConfig
+			);
+			
+			vkcv::DescriptorWrites writes;
+			writes.writeSampledImage(2, normalBuffer);
+			core.writeDescriptorSet(lightDescSet, writes);
+			core.writeDescriptorSet(aoDescSet, writes);
+		}
+		
+		if ((!colorBuffer) ||
+			(swapchainWidth != core.getImageWidth(colorBuffer)) ||
+			(swapchainHeight != core.getImageHeight(colorBuffer))) {
+			colorBuffer = core.createImage(
+					core.getSwapchainFormat(core.getWindow(windowHandle).getSwapchain()),
+					bufferConfig
+			);
+			
+			vkcv::DescriptorWrites writes;
+			writes.writeSampledImage(1, colorBuffer);
+			core.writeDescriptorSet(compositeDescSet, writes);
+		}
+		
+		if ((!depthBuffer) ||
+			(swapchainWidth != core.getImageWidth(depthBuffer)) ||
+			(swapchainHeight != core.getImageHeight(depthBuffer))) {
+			depthBuffer = core.createImage(
+					vk::Format::eD32Sfloat,
+					vkcv::ImageConfig(
+							swapchainWidth,
+							swapchainHeight
+					)
+			);
+		}
+		
+		if ((!lightBuffer) ||
+			(swapchainWidth != core.getImageWidth(lightBuffer)) ||
+			(swapchainHeight != core.getImageHeight(lightBuffer))) {
+			lightBuffer = core.createImage(
+					core.getSwapchainFormat(core.getWindow(windowHandle).getSwapchain()),
+					bufferConfig
+			);
+			
+			vkcv::DescriptorWrites writes;
+			writes.writeSampledImage(0, lightBuffer);
+			core.writeDescriptorSet(compositeDescSet, writes);
+		}
+		
+		if ((!aoBuffer) ||
+			(swapchainWidth != core.getImageWidth(aoBuffer)) ||
+			(swapchainHeight != core.getImageHeight(aoBuffer))) {
+			aoBuffer = core.createImage(
+					core.getSwapchainFormat(core.getWindow(windowHandle).getSwapchain()),
+					bufferConfig
+			);
+			
+			vkcv::DescriptorWrites writes;
+			writes.writeSampledImage(2, aoBuffer);
+			core.writeDescriptorSet(compositeDescSet, writes);
+		}
+		
+		if ((!blurBuffer) ||
+			(swapchainWidth != core.getImageWidth(blurBuffer)) ||
+			(swapchainHeight != core.getImageHeight(blurBuffer))) {
+			blurBuffer = core.createImage(
+					core.getSwapchainFormat(core.getWindow(windowHandle).getSwapchain()),
+					bufferConfig
+			);
+			
+			vkcv::DescriptorWrites writes;
+			writes.writeSampledImage(0, blurBuffer);
+			core.writeDescriptorSet(blurDescSet, writes);
+		}
+		
+		cameraManager.update(dt);
+		
+		mvp[0] = cameraManager.getActiveCamera().getView();
+		mvp[1] = cameraManager.getActiveCamera().getProjection();
+		
+		textureProps->useColorTexture = useColorTexture? 1 : 0;
+		
+		res[0] = glm::vec2(swapchainWidth, swapchainHeight);
+		
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+		
+		std::vector<vkcv::InstanceDrawcall> drawcalls;
+		drawcalls.reserve(sizeX * sizeZ + 1);
+		drawcalls.push_back(planeDrawcall);
+		
+		pushConstants.clear();
+		pushConstants.appendDrawcall(glm::scale(glm::translate(
+				glm::identity<glm::mat4>(),
+				glm::vec3(0, -2.5f, 0)
+		), glm::vec3(sizeX + 4, 1.0f, sizeZ + 4)));
+		
+		for (size_t i = 0; i < sizeX; i++) {
+			for (size_t j = 0; j < sizeZ; j++) {
+				drawcalls.push_back(sphereDrawcall);
+				
+				pushConstants.appendDrawcall(glm::scale(glm::translate(
+						glm::identity<glm::mat4>(),
+						glm::vec3(
+								(i * 1.2f) - (sizeX - 1) * 0.6f,
+								-2.0f,
+								(j * 1.2f) - (sizeZ - 1) * 0.6f
+						)
+				), glm::vec3(0.5)));
+			}
+		}
+		
+		const vkcv::ImageHandle targetImage = (
+				useBlurPass?
+				blurBuffer :
+				swapchainInput
+		);
+		
+		core.recordDrawcallsToCmdStream(
+				cmdStream,
+				(showBuffer == 1?
+					gBufferPosPipeline : (showBuffer == 2?
+					gBufferNorPipeline : gBufferPipeline)
+				),
+				pushConstants,
+				drawcalls,
+				{
+					showBuffer == 1? targetImage : positionBuffer,
+					showBuffer == 2? targetImage : normalBuffer,
+					showBuffer == 3? targetImage : colorBuffer,
+					depthBuffer
+				},
+				windowHandle
+		);
+		
+		if (((showBuffer == 0) && (useLighting)) || (showBuffer == 4)) {
+			core.prepareImageForSampling(cmdStream, positionBuffer);
+			core.prepareImageForSampling(cmdStream, normalBuffer);
+			
+			pushConstants.clear();
+			
+			const glm::mat4 lightMatrix = glm::rotate<float>(
+					glm::identity<glm::mat4>(),
+					glm::radians<float>(10.0f * static_cast<float>(t)),
+					glm::vec3(0.0, 1.0, 0.0)
+			);
+			
+			for (size_t i = 0; i < lightDrawcalls.size(); i++) {
+				float x = static_cast<float>(i % 10) - 5.0f;
+				float y = static_cast<float>((i / 10) % 10) - 5.0f;
+				
+				pushConstants.appendDrawcall(glm::translate(
+						lightMatrix,
+						glm::vec3(
+								x * 1.2f,
+								-1.0f,
+								y * 1.2f
+						)
+				));
+			}
+			
+			core.recordDrawcallsToCmdStream(
+					cmdStream,
+					lightPipeline,
+					pushConstants,
+					lightDrawcalls,
+					{
+						showBuffer == 4? targetImage : lightBuffer
+					},
+					windowHandle
+			);
+		}
+		
+		if (((showBuffer == 0) && (useSSAO)) || (showBuffer == 5)) {
+			core.prepareImageForSampling(cmdStream, positionBuffer);
+			core.prepareImageForSampling(cmdStream, normalBuffer);
+			
+			core.recordDrawcallsToCmdStream(
+					cmdStream,
+					aoPipeline,
+					vkcv::pushConstants<glm::vec2>(glm::vec2(ssaoRadius, ssaoQuality)),
+					{ aoDrawcall },
+					{
+						showBuffer == 5? targetImage : (useBlurPass? blurBuffer : aoBuffer)
+					},
+					windowHandle
+			);
+		}
+		
+		if (useBlurPass) {
+			core.prepareImageForSampling(cmdStream, blurBuffer);
+			
+			core.recordDrawcallsToCmdStream(
+					cmdStream,
+					blurPipeline,
+					vkcv::pushConstants<float>(0.0f),
+					{ blurDrawcall },
+					{
+							showBuffer == 0? aoBuffer : swapchainInput
+					},
+					windowHandle
+			);
+		}
+		
+		if (showBuffer == 0) {
+			core.prepareImageForSampling(cmdStream, lightBuffer);
+			core.prepareImageForSampling(cmdStream, colorBuffer);
+			core.prepareImageForSampling(cmdStream, aoBuffer);
+			
+			core.recordDrawcallsToCmdStream(
+					cmdStream,
+					compositePipeline,
+					vkcv::pushConstants<glm::vec2>(
+							glm::vec2(useLighting? 1 : 0, useSSAO? 1 : 0)
+					),
+					{ compositeDrawcall },
+					{
+							swapchainInput
+					},
+					windowHandle
+			);
+		}
+		
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+		
+		gui.beginGUI();
+		ImGui::Begin("Settings");
+		ImGui::SliderInt("X-Axis", &sizeX, 1, 100);
+		ImGui::SliderInt("Z-Axis", &sizeZ, 1, 100);
+		ImGui::Spacing();
+		ImGui::Combo("Show buffer", &showBuffer, showBufferNames.data(),
+					 static_cast<int>(showBufferNames.size()));
+		ImGui::Checkbox("Use color texture", &useColorTexture);
+		ImGui::Checkbox("Use blur pass", &useBlurPass);
+		ImGui::SliderFloat("SSAO radius", &ssaoRadius, 0.0f, 1.0f);
+		ImGui::SliderFloat("SSAO quality", &ssaoQuality, 1.0f, 100.0f);
+		ImGui::Checkbox("Composite lighting", &useLighting);
+		ImGui::Checkbox("Composite SSAO", &useSSAO);
+		ImGui::End();
+		gui.endGUI();
+	});
+	
+	matrixBuffer.unmap();
+	texturePropsBuffer.unmap();
+	resolutionBuffer.unmap();
+	return 0;
+}
diff --git a/framework b/framework
index 393809b..b022602 160000
--- a/framework
+++ b/framework
@@ -1 +1 @@
-Subproject commit 393809b4e6e8c0e5fa79e8501ceb3cc31462c9fd
+Subproject commit b022602d1914f87174533b5fb1a302caf8f57343
-- 
GitLab