diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6c1b95b31f666aef1ee103569876847fb700971f..634f61fb375f097116cea5ab5bd35f78ad98d933 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,5 +8,8 @@ set(BUILD_CLANG_FORMAT OFF CACHE INTERNAL "")
 set(BUILD_DOXYGEN_DOCS OFF CACHE INTERNAL "")
 add_subdirectory(framework)
 
+# Include add_project macro from framework
+include(framework/config/ext/Project.cmake)
+
 # Add demo projects
 add_subdirectory(demos)
\ No newline at end of file
diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8b5172cb6a4b4b63a428a5094845dc47ecd686f5 100644
--- a/demos/CMakeLists.txt
+++ b/demos/CMakeLists.txt
@@ -0,0 +1,2 @@
+
+add_subdirectory(NormalMapping)
diff --git a/demos/NormalMapping/.gitignore b/demos/NormalMapping/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..3a084ecbd0a05af872da9b25fff8eab9bee12f4c
--- /dev/null
+++ b/demos/NormalMapping/.gitignore
@@ -0,0 +1 @@
+NormalMapping
\ No newline at end of file
diff --git a/demos/NormalMapping/CMakeLists.txt b/demos/NormalMapping/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..28773eb129359adde177f2752720632036ddd164
--- /dev/null
+++ b/demos/NormalMapping/CMakeLists.txt
@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 3.16)
+project(NormalMapping)
+
+# 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(NormalMapping src/main.cpp)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(NormalMapping SYSTEM BEFORE PRIVATE ${vkcv_includes})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(NormalMapping ${vkcv_libraries})
diff --git a/demos/NormalMapping/shaders/NormalMapping.frag b/demos/NormalMapping/shaders/NormalMapping.frag
new file mode 100644
index 0000000000000000000000000000000000000000..87fef02cf33037c2efc8bd48ba50cd8feae5a70b
--- /dev/null
+++ b/demos/NormalMapping/shaders/NormalMapping.frag
@@ -0,0 +1,77 @@
+#version 450
+
+layout(set=0, binding=5) uniform texture2D	colorTexture;
+layout(set=0, binding=6) uniform sampler	colorSampler;
+
+layout(set=1, binding=0) uniform texture2D	normalTexture;
+
+struct Material {
+	vec3 diffuse;
+	float alpha;
+	vec3 specular;
+	float shininess;
+};
+
+layout(set=0, binding=1) uniform materialBuffer {
+	Material mat;
+};
+
+struct Light {
+	vec4 pos; // pos.w = 0 (dir light), pos.w = 1 (point light)
+	vec3 col;
+	float spot_exponent; 
+	vec3 spot_direction; 
+	float spot_cutoff; // no spotlight if cutoff = 0
+};
+
+layout(set=0, binding=2) uniform lightBuffer {
+	Light light;
+	vec3 lightAmbient;
+};
+
+layout(set=0, binding=4) uniform texturePropsBuffer {
+	int useColorTexture;
+};
+
+layout(location = 0) in vec3 passLightVector;
+layout(location = 1) in vec3 passEyeVector;
+layout(location = 2) in vec2 passUV;
+
+layout(location = 0) out vec4 fragmentColor;
+
+void main(){
+    
+    /***************** Diffuse ******************************************************/
+    // local normal, in tangent space
+    vec3 normal = texture(sampler2D(normalTexture, colorSampler), passUV).rgb * 2.0 - 1.0;
+
+    // direction of the light (from the fragment to the light) in tangent space
+    vec3 lightVector = normalize(passLightVector);
+
+    //compute the diffuse lighting factor
+    float cos_phi = max(dot(normal, lightVector), 0);
+
+    /***************** Specular *****************************************************/
+    // compute the normalized reflection vector using GLSL's built-in reflect() function
+    vec3 reflection = normalize(reflect(-lightVector, normal));
+
+    // eye vector in tangent space
+    vec3 eyeVector = normalize(passEyeVector);
+
+    // compute the specular lighting factor
+    float cos_psi_n = pow(max(dot(reflection, eyeVector), 0.0f), mat.shininess);
+
+    /***************** Material properties ******************************************/
+    vec3 diffuse_color;
+    if (useColorTexture != 0)
+        diffuse_color = texture(sampler2D(colorTexture, colorSampler), passUV).rgb;
+    else
+        diffuse_color = mat.diffuse;
+    
+    /***************** All together *************************************************/
+    fragmentColor.rgb = diffuse_color * lightAmbient;
+    fragmentColor.rgb += diffuse_color * cos_phi * light.col;
+    fragmentColor.rgb += mat.specular * cos_psi_n * light.col;
+    fragmentColor.a = 1.0;
+
+}
diff --git a/demos/NormalMapping/shaders/NormalMapping.vert b/demos/NormalMapping/shaders/NormalMapping.vert
new file mode 100644
index 0000000000000000000000000000000000000000..d4feaf494c4c402ecbf4bd1d6adffd41be665e85
--- /dev/null
+++ b/demos/NormalMapping/shaders/NormalMapping.vert
@@ -0,0 +1,69 @@
+#version 450
+
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec3 normal;
+layout(location = 2) in vec2 uv;
+layout(location = 3) in vec3 tangent;
+
+//vec3 tangent = vec3(0);
+
+layout(set=0, binding=0) uniform matrixBuffer {
+	mat4 modelMatrix;
+	mat4 viewMatrix;
+	mat4 projectionMatrix;
+};
+
+struct Light {
+	vec4 pos; // pos.w = 0 (dir light), pos.w = 1 (point light)
+	vec3 col;
+	float spot_exponent; 
+	vec3 spot_direction; 
+	float spot_cutoff; // no spotlight if cutoff = 0
+};
+
+layout(set=0, binding=2) uniform lightBuffer {
+	Light light;
+	vec3 lightAmbient;
+};
+
+layout(location = 0) out vec3 passLightVector;
+layout(location = 1) out vec3 passEyeVector;
+layout(location = 2) out vec2 passUV;
+
+void main(){
+    
+    gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1);
+
+    /*********** Calculate matrix that goes from camera space to tangent space **********/
+    mat4 normalMatrix = transpose(inverse(viewMatrix * modelMatrix));
+	vec3 normal_cs = normalize((normalMatrix * vec4(normal,0)).xyz);
+	vec3 tangent_cs = normalize((normalMatrix * vec4(tangent,0)).xyz);
+	vec3 bitangent_cs = cross(tangent_cs, normal_cs);
+
+    mat3 tbn = transpose(mat3(
+        tangent_cs,
+        bitangent_cs,
+        normal_cs
+    ));
+
+    /*********** Calculate position in camera space *************************************/
+	vec3 position_cs = (viewMatrix * modelMatrix * vec4(position, 1)).xyz;
+
+    /*********** Calculate light vector (tangent space) *********************************/
+    // light position in world coordinates
+	vec3 lightPosition_cs = (light.pos).xyz;
+
+    // calcluate vector that goes from the vertex to the light, in tangent space
+	passLightVector = tbn * normalize(lightPosition_cs - position_cs);
+
+    /*********** Calculate eye vector (tangent space) **********************************/
+    // calculate eye vector in camera space
+    vec3 eye_cs = normalize(-position_cs);
+
+    // calculate eye vector in tangent space
+    passEyeVector = tbn * eye_cs;
+
+    /*********** Pass uv of the vertex *************************************************/
+	// no special space for this one
+	passUV = uv;
+}
diff --git a/demos/NormalMapping/shaders/Phong.frag b/demos/NormalMapping/shaders/Phong.frag
new file mode 100644
index 0000000000000000000000000000000000000000..96f02aec96070a0a291c1d7ed79afc819545e9f7
--- /dev/null
+++ b/demos/NormalMapping/shaders/Phong.frag
@@ -0,0 +1,126 @@
+#version 450
+
+layout(location = 0) in vec3 passPosition;
+layout(location = 1) in vec3 passNormal;
+layout(location = 2) in vec2 passTcoord;
+
+layout(set=0, binding=0) uniform matrixBuffer {
+	mat4 modelMatrix;
+	mat4 viewMatrix;
+	mat4 projectionMatrix;
+};
+
+struct Material {
+	vec3 diffuse;
+	float alpha;
+	vec3 specular;
+	float shininess;
+};
+
+layout(set=0, binding=1) uniform materialBuffer {
+	Material mat;
+};
+
+struct Light {
+	vec4 pos; // pos.w = 0 (dir light), pos.w = 1 (point light)
+	vec3 col;
+	float spot_exponent; 
+	vec3 spot_direction; 
+	float spot_cutoff; // no spotlight if cutoff = 0
+};
+
+layout(set=0, binding=2) uniform lightBuffer {
+	Light light;
+	vec3 lightAmbient;
+};
+
+struct FogParams {
+	vec3 col; // Fog color
+	float density; // For exp and exp2 equation
+	float start; // This is only for linear fog
+	float end; // This is only for linear fog
+	int mode; // 1 = linear, 2 = exp, 3 = exp2
+};
+
+layout(set=0, binding=3) uniform fogBuffer {
+	FogParams fog;
+};
+
+layout(set=0, binding=4) uniform texturePropsBuffer {
+	int useColorTexture;
+};
+
+layout(set=0, binding=5) uniform texture2D	colorTexture;
+layout(set=0, binding=6) uniform sampler	colorSampler;
+
+layout(location = 0) out vec4 fragmentColor;
+
+float getFogFactor(FogParams fog, float z) { 
+	float f = 0.0; 
+	if (fog.mode == 1) { 
+		f = (fog.end-z)/(fog.end-fog.start); 
+	} else if (fog.mode == 2) {
+		f = exp(-fog.density*z); 
+	} else if(fog.mode == 3) {
+		f = exp(-pow(fog.density*z, 2.0));
+	}
+	
+	f = clamp(f, 0.0, 1.0); 
+	
+	return f; 
+}
+
+void main() { 
+	vec3 lightVector;
+	float spot;
+	vec3 diffuse_color;
+	float diffuse_alpha;
+
+	// Diffuse
+	vec3 light_camcoord = (light.pos).xyz;
+	if (light.pos.w > 0.001f) {
+		lightVector = normalize(light_camcoord - passPosition);
+	} else {
+		lightVector = normalize(light_camcoord);
+	}
+	
+	float cos_phi = max(dot(passNormal,lightVector), 0.0f);
+
+	// Specular
+	vec3 eye = normalize(-passPosition);
+	vec3 reflection = normalize(reflect(-lightVector, passNormal));
+	float cos_psi_n = pow(max(dot(reflection, eye), 0.0f), mat.shininess);
+
+	// Spotlight	
+	if (light.spot_cutoff < 0.001f) {
+		spot = 1.0;
+	} else {
+		float cos_phi_spot = max(dot(-lightVector, mat3(viewMatrix) * light.spot_direction), 0.0f);
+		if (cos_phi_spot >= cos(light.spot_cutoff)) {
+			spot = pow(cos_phi_spot, light.spot_exponent);
+		} else {
+			spot = 0.0f;
+		}
+	}
+
+	// Textures 
+	if (useColorTexture != 0) {
+		diffuse_color = texture(sampler2D(colorTexture, colorSampler), passTcoord).rgb;
+		diffuse_alpha = texture(sampler2D(colorTexture, colorSampler), passTcoord).a;
+	} else {
+		diffuse_color = mat.diffuse;
+		diffuse_alpha = mat.alpha;
+	}
+
+	// All together 
+	fragmentColor.rgb = diffuse_color * lightAmbient;
+	fragmentColor.rgb += spot * diffuse_color * cos_phi * light.col;
+	fragmentColor.rgb += spot * mat.specular * cos_psi_n * light.col;
+	fragmentColor.a = diffuse_alpha;
+
+	//Add fog
+	if (fog.mode != 0) {
+		float f = getFogFactor(fog, length(passPosition));
+		fragmentColor.rgb = f * fragmentColor.rgb + (1-f) * fog.col;
+	}
+}
diff --git a/demos/NormalMapping/shaders/Phong.vert b/demos/NormalMapping/shaders/Phong.vert
new file mode 100644
index 0000000000000000000000000000000000000000..0db98891e0a47d488a37fee8b392be7e81beb674
--- /dev/null
+++ b/demos/NormalMapping/shaders/Phong.vert
@@ -0,0 +1,29 @@
+#version 450
+
+layout(location = 0) in vec3 Position;
+layout(location = 1) in vec3 Normal;
+layout(location = 2) in vec2 Tcoord;
+
+layout(set=0, binding=0) uniform matrixBuffer {
+	mat4 modelMatrix;
+	mat4 viewMatrix;
+	mat4 projectionMatrix;
+};
+
+layout(location = 0) out vec3 passPosition;
+layout(location = 1) out vec3 passNormal;
+layout(location = 2) out vec2 passTcoord;
+
+void main() {
+	gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(Position, 1);
+
+	//transform the position correctly into view space
+	//and pass it to the fragment shader
+	passPosition = (viewMatrix * modelMatrix * vec4(Position, 1)).xyz;
+
+	//transform the normal correctly into view space 
+	//and pass it to the fragment shader
+	mat3 normalMatrix = mat3(transpose(inverse(viewMatrix * modelMatrix)));
+	passNormal = normalize(normalMatrix * Normal);
+	passTcoord = Tcoord;
+}
diff --git a/demos/NormalMapping/src/main.cpp b/demos/NormalMapping/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3af12d62e5f980c3c92aa59fd010ec4b4580ecb5
--- /dev/null
+++ b/demos/NormalMapping/src/main.cpp
@@ -0,0 +1,329 @@
+
+#include <iostream>
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Core.hpp>
+#include <vkcv/DescriptorWrites.hpp>
+#include <vkcv/Image.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/gui/GUI.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+
+struct Material {
+	glm::vec3 diffuse;
+	float alpha;
+	glm::vec3 specular;
+	float shininess;
+};
+
+struct Light {
+	glm::vec4 pos; // pos.w = 0 (dir light), pos.w = 1 (point light)
+	glm::vec3 col;
+	float spot_exponent;
+	glm::vec3 spot_direction;
+	float spot_cutoff; // no spotlight if cutoff = 0
+};
+
+struct LightProps {
+	Light light;
+	glm::vec3 lightAmbient;
+};
+
+struct FogParams {
+	glm::vec3 col; // Fog color
+	float density; // For exp and exp2 equation
+	float start; // This is only for linear fog
+	float end; // This is only for linear fog
+	int mode; // 1 = linear, 2 = exp, 3 = exp2
+};
+
+struct TextureProps {
+	int useColorTexture;
+};
+
+int main(int argc, const char** argv) {
+	const std::string applicationName = "Normal Mapping";
+	
+	vkcv::Core core = vkcv::Core::create(
+			applicationName,
+			VK_MAKE_VERSION(0, 0, 1),
+			{ vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eTransfer },
+			{ VK_KHR_SWAPCHAIN_EXTENSION_NAME }
+	);
+	
+	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);
+	
+	vkcv::geometry::Cuboid cube (glm::vec3(0.0f), 1.0f);
+	
+	vkcv::shader::GLSLCompiler compiler;
+	vkcv::ShaderProgram phongShaderProgram, normalMappingShaderProgram;
+	
+	compiler.compileProgram(
+			phongShaderProgram,
+			{
+				{ vkcv::ShaderStage::VERTEX, "shaders/Phong.vert" },
+				{ vkcv::ShaderStage::FRAGMENT, "shaders/Phong.frag" }
+			},
+			nullptr
+	);
+	
+	compiler.compileProgram(
+			normalMappingShaderProgram,
+			{
+					{ vkcv::ShaderStage::VERTEX, "shaders/NormalMapping.vert" },
+					{ vkcv::ShaderStage::FRAGMENT, "shaders/NormalMapping.frag" }
+			},
+			nullptr
+	);
+	
+	vkcv::PassHandle renderPass = core.createPass(vkcv::PassConfig(
+			{
+				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
+				)
+			}
+	));
+	
+	const vkcv::VertexLayout vertexLayoutPhong {
+			vkcv::createVertexBindings(phongShaderProgram.getVertexAttachments())
+	};
+	
+	const vkcv::VertexLayout vertexLayoutNormalMapping {
+			vkcv::createVertexBindings(normalMappingShaderProgram.getVertexAttachments())
+	};
+	
+	vkcv::DescriptorBindings descriptorBindings0 = (
+			phongShaderProgram.getReflectedDescriptors().at(0)
+	);
+	
+	descriptorBindings0.at(2).shaderStages |= vkcv::ShaderStage::VERTEX;
+	
+	auto descriptorSetLayout0 = core.createDescriptorSetLayout(descriptorBindings0);
+	auto descriptorSet0 = core.createDescriptorSet(descriptorSetLayout0);
+	
+	const vkcv::DescriptorBindings& descriptorBindings1 = (
+			normalMappingShaderProgram.getReflectedDescriptors().at(1)
+	);
+	
+	auto descriptorSetLayout1 = core.createDescriptorSetLayout(descriptorBindings1);
+	auto descriptorSet1 = core.createDescriptorSet(descriptorSetLayout1);
+	
+	vkcv::GraphicsPipelineHandle phongPipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					phongShaderProgram,
+					renderPass,
+					vertexLayoutPhong,
+					{ descriptorSetLayout0 }
+			)
+	);
+	
+	vkcv::GraphicsPipelineHandle normalMappingPipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					normalMappingShaderProgram,
+					renderPass,
+					vertexLayoutNormalMapping,
+					{
+						descriptorSetLayout0,
+						descriptorSetLayout1
+					}
+			)
+	);
+	
+	auto matrixBuffer = vkcv::buffer<glm::mat4>(
+			core,
+			vkcv::BufferType::UNIFORM,
+			3,
+			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;
+	material.specular = glm::vec3(1.0f);
+	material.shininess = 120.0f;
+	materialBuffer.fill(&material, 1);
+	
+	auto lightBuffer = vkcv::buffer<LightProps>(
+			core,
+			vkcv::BufferType::UNIFORM,
+			1
+	);
+	
+	LightProps lightProps;
+	lightProps.light.pos = glm::vec4(0.0f, 1.0f, -10.0f, 1.0f);
+	lightProps.light.col = glm::vec3(0.75f);
+	lightProps.light.spot_exponent = 1.0f;
+	lightProps.light.spot_direction = glm::vec3(0.0f);
+	lightProps.light.spot_cutoff = 0.0f;
+	lightProps.lightAmbient = glm::vec3(0.2f);
+	lightBuffer.fill(&lightProps, 1);
+	
+	auto fogBuffer = vkcv::buffer<FogParams>(
+			core,
+			vkcv::BufferType::UNIFORM,
+			1
+	);
+	
+	FogParams fogParams;
+	fogParams.col = glm::vec3(1.0f);
+	fogParams.density = 1.0f;
+	fogParams.start = 1.0f;
+	fogParams.end = 10.0f;
+	fogParams.mode = 0;
+	fogBuffer.fill(&fogParams, 1);
+	
+	auto texturePropsBuffer = vkcv::buffer<TextureProps>(
+			core,
+			vkcv::BufferType::UNIFORM,
+			1,
+			vkcv::BufferMemoryType::HOST_VISIBLE
+	);
+	
+	vkcv::asset::Texture diffusemap = vkcv::asset::loadTexture("../../resources/normalmapping/diffusemap.png");
+	vkcv::asset::Texture normalmap = vkcv::asset::loadTexture("../../resources/normalmapping/normalmap.png");
+	
+	vkcv::Image diffusemapImage = vkcv::image(core, vk::Format::eR8G8B8A8Srgb, diffusemap.w, diffusemap.h);
+	diffusemapImage.fill(diffusemap.data.data());
+	
+	vkcv::Image normalmapImage = vkcv::image(core, vk::Format::eR8G8B8A8Srgb, normalmap.w, normalmap.h);
+	normalmapImage.fill(normalmap.data.data());
+	
+	vkcv::SamplerHandle sampler = vkcv::samplerLinear(core);
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeUniformBuffer(0, matrixBuffer.getHandle());
+		writes.writeUniformBuffer(1, materialBuffer.getHandle());
+		writes.writeUniformBuffer(2, lightBuffer.getHandle());
+		writes.writeUniformBuffer(3, fogBuffer.getHandle());
+		writes.writeUniformBuffer(4, texturePropsBuffer.getHandle());
+		writes.writeSampledImage(5, diffusemapImage.getHandle());
+		writes.writeSampler(6, sampler);
+		core.writeDescriptorSet(descriptorSet0, writes);
+	}
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeSampledImage(0, normalmapImage.getHandle());
+		core.writeDescriptorSet(descriptorSet1, writes);
+	}
+	
+	vkcv::gui::GUI gui (core, windowHandle);
+	
+	bool useColorTexture = false;
+	bool useNormalMappingShader = false;
+	
+	const auto vertexData = cube.generateVertexData(core);
+	
+	vkcv::InstanceDrawcall drawcallPhong (vertexData);
+	drawcallPhong.useDescriptorSet(0, descriptorSet0);
+	
+	vkcv::InstanceDrawcall drawcallNormalMapping (vertexData);
+	drawcallNormalMapping.useDescriptorSet(0, descriptorSet0);
+	drawcallNormalMapping.useDescriptorSet(1, descriptorSet1);
+	
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+	
+	vkcv::ImageHandle depthBuffer;
+	
+	glm::mat4* mvp = matrixBuffer.map();
+	TextureProps* textureProps = texturePropsBuffer.map();
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle,
+				 double t,
+				 double dt,
+				 uint32_t swapchainWidth,
+				 uint32_t swapchainHeight
+	) {
+		if ((!depthBuffer) ||
+			(swapchainWidth != core.getImageWidth(depthBuffer)) ||
+			(swapchainHeight != core.getImageHeight(depthBuffer))) {
+			depthBuffer = core.createImage(
+					vk::Format::eD32Sfloat,
+					swapchainWidth,
+					swapchainHeight
+			);
+		}
+		
+		cameraManager.update(dt);
+		
+		mvp[0] = glm::rotate(glm::identity<glm::mat4>(), glm::pi<float>() / 2, glm::vec3(1, 0, 0));
+		mvp[1] = cameraManager.getActiveCamera().getView();
+		mvp[2] = cameraManager.getActiveCamera().getProjection();
+		
+		textureProps->useColorTexture = useColorTexture? 1 : 0;
+		
+		if (useColorTexture) {
+			material.specular = glm::vec3(0.2f);
+			materialBuffer.fill(&material, 1);
+			
+			lightProps.lightAmbient = glm::vec3(0.6f);
+			lightBuffer.fill(&lightProps, 1);
+		} else {
+			material.specular = glm::vec3(1.0f);
+			materialBuffer.fill(&material, 1);
+			
+			lightProps.lightAmbient = glm::vec3(0.2f);
+			lightBuffer.fill(&lightProps, 1);
+		}
+		
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+		
+		core.prepareImageForSampling(cmdStream, diffusemapImage.getHandle());
+		
+		if (useNormalMappingShader) {
+			core.prepareImageForSampling(cmdStream, normalmapImage.getHandle());
+		}
+		
+		core.recordDrawcallsToCmdStream(
+				cmdStream,
+				useNormalMappingShader? normalMappingPipeline : phongPipeline,
+				vkcv::PushConstants(0),
+				{
+					useNormalMappingShader? drawcallNormalMapping : drawcallPhong
+				},
+				{
+					swapchainInput,
+					depthBuffer
+				},
+				windowHandle
+		);
+		
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+		
+		gui.beginGUI();
+		ImGui::Begin("Settings");
+		ImGui::Checkbox("Use color texture", &useColorTexture);
+		ImGui::Checkbox("Use normal mapping shader", &useNormalMappingShader);
+		ImGui::End();
+		gui.endGUI();
+	});
+	
+	matrixBuffer.unmap();
+	texturePropsBuffer.unmap();
+	return 0;
+}
\ No newline at end of file
diff --git a/framework b/framework
index a9250ba29e90881676696744403c9de2d84fde90..21562cea94ff11677477431f8d958c0d6577a415 160000
--- a/framework
+++ b/framework
@@ -1 +1 @@
-Subproject commit a9250ba29e90881676696744403c9de2d84fde90
+Subproject commit 21562cea94ff11677477431f8d958c0d6577a415
diff --git a/resources/brick.png b/resources/brick.png
new file mode 100644
index 0000000000000000000000000000000000000000..35cfcc7f23064ed0edb39fbaeaaa96e09a055f52
--- /dev/null
+++ b/resources/brick.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5cd9331880751f65acd5e5db956050592e5a88c2117fab482b78ee5c7bcf3d86
+size 541473
diff --git a/resources/cubeMap/negx.jpg b/resources/cubeMap/negx.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..62ca14156fecccb51e325cc244a339c662296e9c
--- /dev/null
+++ b/resources/cubeMap/negx.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5f23a9d19b5c628a136af9c8f6110cba99fa2d9a18eb85ddd2cf3c8d431e7bd1
+size 688017
diff --git a/resources/cubeMap/negy.jpg b/resources/cubeMap/negy.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c7a9f9458bf351ca8c3ad6cf7f7bd174a12d5be1
--- /dev/null
+++ b/resources/cubeMap/negy.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:96512e372e814ab364bcd19a0d1ce017eb54073d414b2f1f89788244f38fb10e
+size 921906
diff --git a/resources/cubeMap/negz.jpg b/resources/cubeMap/negz.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..85849d1758ff02b75ad36d49091e7bceaf4fd19c
--- /dev/null
+++ b/resources/cubeMap/negz.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6e5f526f74cc36a4bcb00e668b0f07cfaf78e7222ea8408ef835b3654d1ea2b4
+size 784523
diff --git a/resources/cubeMap/posx.jpg b/resources/cubeMap/posx.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..000d1fc58e3f3c815e70a0076b8e1f53fd565513
--- /dev/null
+++ b/resources/cubeMap/posx.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7f1d8ffb8577ae0cd3e5cec0351c92e24fbd20b6c06d4cde65ce6bf731c32ae0
+size 873574
diff --git a/resources/cubeMap/posy.jpg b/resources/cubeMap/posy.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..64a930c58d5996b7c0f685aaf6864a4b2398db26
--- /dev/null
+++ b/resources/cubeMap/posy.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:22fcf327b37c8e8e37a1c1a4d5f0a7193a07a57c0a57718b5e4fff873695fd39
+size 374053
diff --git a/resources/cubeMap/posz.jpg b/resources/cubeMap/posz.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4a6f37c3573b897db93527807e80f79a2c2e4dbe
--- /dev/null
+++ b/resources/cubeMap/posz.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:091cb76445227ec9c4532ccf478f6437269512a6f954e70515da8dffc095fe01
+size 737334
diff --git a/resources/cubeMap/readme.txt b/resources/cubeMap/readme.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8b404c2736a266c27de41a7d4383493fcf91c8f9
--- /dev/null
+++ b/resources/cubeMap/readme.txt
@@ -0,0 +1,13 @@
+Author
+======
+
+This is the work of Emil Persson, aka Humus.
+http://www.humus.name
+
+
+
+License
+=======
+
+This work is licensed under a Creative Commons Attribution 3.0 Unported License.
+http://creativecommons.org/licenses/by/3.0/
diff --git a/resources/cv_logo.png b/resources/cv_logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..6e4c0b853e323af0e2067911cfaaa015f712f0f0
--- /dev/null
+++ b/resources/cv_logo.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:16f21167e42d71190eed6712992211aa1627f4a0d1b00204ab74435383399a7d
+size 8943
diff --git a/resources/normalmapping/diffusemap.png b/resources/normalmapping/diffusemap.png
new file mode 100644
index 0000000000000000000000000000000000000000..3004d26260eafb12b54036805ef85670f258f6b4
--- /dev/null
+++ b/resources/normalmapping/diffusemap.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:56dbe1415de53f9b8fcaf246b9423d8781da025d4db067de1d1a701846e75f9b
+size 1419878
diff --git a/resources/normalmapping/normalmap.png b/resources/normalmapping/normalmap.png
new file mode 100644
index 0000000000000000000000000000000000000000..a9675d3fcaf6fbb0401a8820345a0dcb946ae034
--- /dev/null
+++ b/resources/normalmapping/normalmap.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0bf7f32ae3f85aa7790e6a3dd4667114e4ad86a01ad0a163ccec42cf12df14f0
+size 2562138
diff --git a/resources/wheel.png b/resources/wheel.png
new file mode 100644
index 0000000000000000000000000000000000000000..b17f62ce781447022af99b3d5c44943f44cb2a64
--- /dev/null
+++ b/resources/wheel.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c2837611fd66adac3fd224a7819db43cc3cf4d33b7dc13f024d5ff58f29a3308
+size 4152