diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index 3ff2d925bff188a585f6bd3047ba0e7b2ca594c3..ab0d2aca0eccbd3c6adfea191cefa2c63623b0b9 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -186,9 +186,9 @@ namespace vkcv
 		/**
 		 * @brief render a beautiful triangle
 		*/
-		void renderTriangle(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle,
+		void renderMesh(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle,
 			const int width, const int height, const size_t pushConstantSize, const void* pushConstantData, 
-			const BufferHandle vertexBuffer);
+			const BufferHandle vertexBuffer, const BufferHandle indexBuffer, const size_t indexCount);
 
 		/**
 		 * @brief end recording and present image
diff --git a/projects/first_mesh/CMakeLists.txt b/projects/first_mesh/CMakeLists.txt
index ae9c5604cb83c6f3a16773e896521117460839f7..eb0f028db38707272f9fbcf61662633f2868eedc 100644
--- a/projects/first_mesh/CMakeLists.txt
+++ b/projects/first_mesh/CMakeLists.txt
@@ -22,7 +22,7 @@ if(MSVC)
 endif()
 
 # including headers of dependencies and the VkCV framework
-target_include_directories(first_mesh SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include})
+target_include_directories(first_mesh SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include})
 
 # linking with libraries from all dependencies and the VkCV framework
-target_link_libraries(first_mesh vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries})
+target_link_libraries(first_mesh vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera)
diff --git a/projects/first_mesh/resources/shaders/compile.bat b/projects/first_mesh/resources/shaders/compile.bat
new file mode 100644
index 0000000000000000000000000000000000000000..b4521235c40fe5fb163bab874560c2f219b7517f
--- /dev/null
+++ b/projects/first_mesh/resources/shaders/compile.bat
@@ -0,0 +1,3 @@
+%VULKAN_SDK%\Bin32\glslc.exe shader.vert -o vert.spv
+%VULKAN_SDK%\Bin32\glslc.exe shader.frag -o frag.spv
+pause
\ No newline at end of file
diff --git a/projects/first_mesh/resources/shaders/frag.spv b/projects/first_mesh/resources/shaders/frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..cb13e606fc0041e24ff6a63c0ec7dcca466732aa
Binary files /dev/null and b/projects/first_mesh/resources/shaders/frag.spv differ
diff --git a/projects/first_mesh/resources/shaders/shader.frag b/projects/first_mesh/resources/shaders/shader.frag
new file mode 100644
index 0000000000000000000000000000000000000000..d26446a73020111695aa2c86166205796dfa5e44
--- /dev/null
+++ b/projects/first_mesh/resources/shaders/shader.frag
@@ -0,0 +1,9 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 fragColor;
+layout(location = 0) out vec4 outColor;
+
+void main()	{
+	outColor = vec4(fragColor, 1.0);
+}
\ No newline at end of file
diff --git a/projects/first_mesh/resources/shaders/shader.vert b/projects/first_mesh/resources/shaders/shader.vert
new file mode 100644
index 0000000000000000000000000000000000000000..e6c0e3d6bafda35e90a6bfd849a211e2b6de0f9c
--- /dev/null
+++ b/projects/first_mesh/resources/shaders/shader.vert
@@ -0,0 +1,27 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 position;
+
+layout(location = 0) out vec3 fragColor;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+void main()	{
+    vec3 positions[3] = {
+        vec3(-0.5, 0.5, -1),
+        vec3( 0.5, 0.5, -1),
+        vec3(0, -0.5, -1)
+    };
+    
+    vec3 colors[3] = {
+        vec3(1, 0, 0),
+        vec3(0, 1, 0),
+        vec3(0, 0, 1)
+    };
+
+	gl_Position = mvp * vec4(position, 1.0);
+	fragColor = colors[gl_VertexIndex % 3];
+}
\ No newline at end of file
diff --git a/projects/first_mesh/resources/shaders/vert.spv b/projects/first_mesh/resources/shaders/vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..74722f38ff0584d51d4a966c748b69a4d3f1d3cb
Binary files /dev/null and b/projects/first_mesh/resources/shaders/vert.spv differ
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index b37429e1f7080b70aaab514125f86fbe03f329b5..2b5c783b48abbda4d7c3d7e94e1194b87ce1a304 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -1,76 +1,103 @@
 #include <iostream>
-#include <stdio.h>
+#include <vkcv/Core.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
 #include <vkcv/asset/asset_loader.hpp>
 
 
 int main(int argc, const char** argv) {
+	const char* applicationName = "First Mesh";
+
+	const int windowWidth = 800;
+	const int windowHeight = 600;
+	vkcv::Window window = vkcv::Window::create(
+		applicationName,
+		windowWidth,
+		windowHeight,
+		false
+	);
+
+	vkcv::CameraManager cameraManager(window, windowWidth, windowHeight);
+
+	window.initEvents();
+
+	vkcv::Core core = vkcv::Core::create(
+		window,
+		applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
+		{},
+		{ "VK_KHR_swapchain" }
+	);
+
 	vkcv::asset::Mesh mesh;
-	
-	const char *path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
+
+	const char* path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
 	int result = vkcv::asset::loadMesh(path, mesh);
-	
+
 	if (result == 1) {
 		std::cout << "Mesh loading successful!" << std::endl;
-	} else {
+	}
+	else {
 		std::cout << "Mesh loading failed: " << result << std::endl;
 		return 1;
 	}
 
-	/* Demonstration of how to use the vkcv::asset::Mesh struct. */
-	const char *primitive_modes[] = {
-		"points", "lines", "line loop", "line strip", "triangles",
-		"triangle strip", "triangle fan"
-	};
-	const char *primitive_types[] = {
-		"unknown", "position", "normal", "texcoord0"
-	};
-	printf("Mesh %s (%s) has %lu vertex group(s) and %lu material(s):\n",
-			mesh.name.c_str(), path, mesh.vertexGroups.size(),
-			mesh.materials.size());
-	for (size_t i = 0; i < mesh.vertexGroups.size(); i++) {
-		printf("--- vertex group %lu ---\n", i);
-		const auto &vg = mesh.vertexGroups[i];
-		printf("primitive mode: %d (%s)\n", vg.mode,
-				primitive_modes[vg.mode]);
-		printf("index buffer: %lu bytes for %lu indices ",
-				vg.indexBuffer.data.size(), vg.numIndices);
-		const auto itype = vg.indexBuffer.type;
-		printf("(%s @ %p)\n",
-				itype == vkcv::asset::UINT32 ? "UINT32" :
-				itype == vkcv::asset::UINT16 ? "UINT16" :
-				"UINT8", vg.indexBuffer.data.data());
-		printf("\tindices: ");
-		const size_t n = vg.numIndices;
-		if (vg.indexBuffer.type == vkcv::asset::UINT32) {
-			uint32_t *idx = (uint32_t*)vg.indexBuffer.data.data();
-			for (size_t j = 0; j < n; j++) printf("%u ", idx[j]);
-		} else
-		if (vg.indexBuffer.type == vkcv::asset::UINT16) {
-			uint16_t *idx = (uint16_t*)vg.indexBuffer.data.data();
-			for (size_t j = 0; j < n; j++) printf("%u ", idx[j]);
-		} else
-		if (vg.indexBuffer.type == vkcv::asset::UINT8) {
-			uint8_t *idx = (uint8_t*)vg.indexBuffer.data.data();
-			for (size_t j = 0; j < n; j++) printf("%u ", idx[j]);
-		} else {
-			fprintf(stderr, "ERROR Invalid IndexType: %d\n",
-					vg.indexBuffer.type);
-			return 0;
-		}
-		printf("\n");
-		printf("vertex buffer: %lu bytes for %lu vertices with %lu "
-				"attributes (starting at %p)\n",
-				vg.vertexBuffer.data.size(), vg.numVertices,
-				vg.vertexBuffer.attributes.size(),
-				vg.vertexBuffer.data.data());
-		printf("attributes:\toffset\tlength\tstride\tcomponents\n");
-		for (const auto att : vg.vertexBuffer.attributes) {
-			printf("%11s\t%u\t%u\t%u\t%hhux%hu\n",
-					primitive_types[att.type],
-					att.offset, att.length, att.stride,
-					att.componentCount, att.componentType);
-		}
+	assert(mesh.vertexGroups.size() > 0);
+	const size_t vertexBufferSize = mesh.vertexGroups[0].vertexBuffer.data.size();
+	auto vertexBuffer = core.createBuffer<uint8_t>(vkcv::BufferType::VERTEX, vertexBufferSize, vkcv::BufferMemoryType::DEVICE_LOCAL);
+	vertexBuffer.fill(mesh.vertexGroups[0].vertexBuffer.data.data(), vertexBufferSize);
+
+	const size_t indexBufferSize = mesh.vertexGroups[0].indexBuffer.data.size();
+	auto indexBuffer = core.createBuffer<uint8_t>(vkcv::BufferType::INDEX, indexBufferSize, vkcv::BufferMemoryType::DEVICE_LOCAL);
+	indexBuffer.fill(mesh.vertexGroups[0].indexBuffer.data.data(), indexBufferSize);
+
+	// an example attachment for passes that output to the window
+	const vkcv::AttachmentDescription present_color_attachment(
+		vkcv::AttachmentLayout::UNDEFINED,
+		vkcv::AttachmentLayout::COLOR_ATTACHMENT,
+		vkcv::AttachmentLayout::PRESENTATION,
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		core.getSwapchainImageFormat());
+
+	vkcv::PassConfig trianglePassDefinition({ present_color_attachment });
+	vkcv::PassHandle trianglePass = core.createPass(trianglePassDefinition);
+
+	if (trianglePass.id == 0)
+	{
+		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	vkcv::ShaderProgram triangleShaderProgram{};
+	triangleShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
+	triangleShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
+	triangleShaderProgram.reflectShader(vkcv::ShaderStage::VERTEX);
+	triangleShaderProgram.reflectShader(vkcv::ShaderStage::FRAGMENT);
+
+	const vkcv::PipelineConfig trianglePipelineDefinition(triangleShaderProgram, windowWidth, windowHeight, trianglePass);
+	vkcv::PipelineHandle trianglePipeline = core.createGraphicsPipeline(trianglePipelineDefinition);
+	if (trianglePipeline.id == 0)
+	{
+		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	auto start = std::chrono::system_clock::now();
+	while (window.isWindowOpen())
+	{
+		core.beginFrame();
+		window.pollEvents();
+		auto end = std::chrono::system_clock::now();
+		auto deltatime = end - start;
+		start = end;
+		cameraManager.getCamera().updateView(std::chrono::duration<double>(deltatime).count());
+		const glm::mat4 mvp = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
+
+		core.renderMesh(trianglePass, trianglePipeline, windowWidth, windowHeight, sizeof(mvp), &mvp, vertexBuffer.getHandle(), indexBuffer.getHandle(), mesh.vertexGroups[0].numIndices);
+		core.endFrame();
 	}
-	
 	return 0;
 }
diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp
index 6261b4d8871e0ac6c2581eb97126a94adf100f5a..0e21f4728bab2044c3910a48d9a23c0ad921607a 100644
--- a/projects/first_triangle/src/main.cpp
+++ b/projects/first_triangle/src/main.cpp
@@ -40,16 +40,19 @@ int main(int argc, const char** argv) {
 	
 	const size_t n = 5027;
 	
-	auto vertexBuffer = core.createBuffer<vec3>(vkcv::BufferType::VERTEX, n, vkcv::BufferMemoryType::DEVICE_LOCAL);
+	auto testBuffer = core.createBuffer<vec3>(vkcv::BufferType::VERTEX, n, vkcv::BufferMemoryType::DEVICE_LOCAL);
 	vec3 vec_data[n];
 
 	for (size_t i = 0; i < n; i++) {
 		vec_data[i] = { 42, static_cast<float>(i), 7 };
 	}
 
-	vertexBuffer.fill(vec_data);
+	testBuffer.fill(vec_data);
+
+	auto triangleIndexBuffer = core.createBuffer<uint16_t>(vkcv::BufferType::INDEX, n, vkcv::BufferMemoryType::DEVICE_LOCAL);
+	uint16_t indices[3] = { 0, 1, 2 };
+	triangleIndexBuffer.fill(&indices[0], sizeof(indices));
 
-	
 	/*vec3* m = buffer.map();
 	m[0] = { 0, 0, 0 };
 	m[1] = { 0, 0, 0 };
@@ -143,7 +146,7 @@ int main(int argc, const char** argv) {
         cameraManager.getCamera().updateView(std::chrono::duration<double>(deltatime).count());
 		const glm::mat4 mvp = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
 
-	    core.renderTriangle(trianglePass, trianglePipeline, windowWidth, windowHeight, sizeof(mvp), &mvp, vertexBuffer.getHandle());
+	    core.renderMesh(trianglePass, trianglePipeline, windowWidth, windowHeight, sizeof(mvp), &mvp, testBuffer.getHandle(), triangleIndexBuffer.getHandle(), 3);
 	    core.endFrame();
 	}
 	return 0;
diff --git a/src/vkcv/BufferManager.cpp b/src/vkcv/BufferManager.cpp
index e9b5112888782ae5be373f8a63777b7fc3503713..bb3cf0699cc59d22832a0563f1b95bf942b02265 100644
--- a/src/vkcv/BufferManager.cpp
+++ b/src/vkcv/BufferManager.cpp
@@ -69,6 +69,9 @@ namespace vkcv {
 			case BufferType::STAGING:
 				usageFlags = vk::BufferUsageFlagBits::eTransferSrc;
 				break;
+			case BufferType::INDEX:
+				usageFlags = vk::BufferUsageFlagBits::eIndexBuffer;
+				break;
 			default:
 				// TODO: maybe an issue
 				break;
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index bac12602871a7605cba1d808fde1b21d03ade9bc..13800254d78cb679c81d58a003089aa961986937 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -162,9 +162,9 @@ namespace vkcv
 		destroyTemporaryFramebuffers();
 	}
 
-	void Core::renderTriangle(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle, 
+	void Core::renderMesh(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle, 
 		const int width, const int height, const size_t pushConstantSize, const void *pushConstantData,
-		const BufferHandle vertexBuffer) {
+		const BufferHandle vertexBuffer, const BufferHandle indexBuffer, const size_t indexCount) {
 
 		if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) {
 			return;
@@ -175,7 +175,8 @@ namespace vkcv
 		const vk::Pipeline pipeline		= m_PipelineManager->getVkPipeline(pipelineHandle);
         const vk::PipelineLayout pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle);
 		const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height));
-		const vk::Buffer vulkanVertexBuffer = m_BufferManager->getBuffer(vertexBuffer);
+		const vk::Buffer vulkanVertexBuffer	= m_BufferManager->getBuffer(vertexBuffer);
+		const vk::Buffer vulkanIndexBuffer	= m_BufferManager->getBuffer(indexBuffer);
 
 		const vk::Framebuffer framebuffer = createFramebuffer(m_Context.getDevice(), renderpass, width, height, imageView);
 		m_TemporaryFramebuffers.push_back(framebuffer);
@@ -184,7 +185,7 @@ namespace vkcv
 		submitInfo.queueType = QueueType::Graphics;
 		submitInfo.signalSemaphores = { m_SyncResources.renderFinished };
 		submitCommands(submitInfo, [renderpass, renderArea, imageView, framebuffer, pipeline, pipelineLayout, 
-			pushConstantSize, pushConstantData, vulkanVertexBuffer](const vk::CommandBuffer& cmdBuffer) {
+			pushConstantSize, pushConstantData, vulkanVertexBuffer, indexCount, vulkanIndexBuffer](const vk::CommandBuffer& cmdBuffer) {
 
 			const std::array<float, 4> clearColor = { 0.f, 0.f, 0.f, 1.f };
 			const vk::ClearValue clearValues(clearColor);
@@ -196,8 +197,9 @@ namespace vkcv
 			cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {});
 			
 			cmdBuffer.bindVertexBuffers(0, (vulkanVertexBuffer), { 0 });
+			cmdBuffer.bindIndexBuffer(vulkanIndexBuffer, 0, vk::IndexType::eUint16);	//FIXME: choose proper size
             cmdBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eAll, 0, pushConstantSize, pushConstantData);
-			cmdBuffer.draw(3, 1, 0, 0, {});
+			cmdBuffer.drawIndexed(indexCount, 1, 0, 0, {});
 			cmdBuffer.endRenderPass();
 		}, nullptr);
 	}