diff --git a/config/Sources.cmake b/config/Sources.cmake
index 6fc477cc9552d0a9a8921151ca4435b894630755..e3f5c950ce0d7e5e7e697bda0047ec3da7a15e18 100644
--- a/config/Sources.cmake
+++ b/config/Sources.cmake
@@ -69,4 +69,7 @@ set(vkcv_sources
 		${vkcv_source}/vkcv/SamplerManager.cpp
         
         ${vkcv_include}/vkcv/DescriptorWrites.hpp
+        
+        ${vkcv_include}/vkcv/DrawcallRecording.hpp
+        ${vkcv_source}/vkcv/DrawcallRecording.cpp
 )
diff --git a/include/vkcv/Buffer.hpp b/include/vkcv/Buffer.hpp
index d327067ce409e845bcac5e4c9f56e7de59df89c2..ae935ba9501d4d7776cad7e3ba190a2dd02e5e38 100644
--- a/include/vkcv/Buffer.hpp
+++ b/include/vkcv/Buffer.hpp
@@ -37,6 +37,11 @@ namespace vkcv {
 		size_t getSize() const {
 			return m_count * sizeof(T);
 		}
+
+        [[nodiscard]]
+        const vk::Buffer getVulkanHandle() const {
+            return m_manager->getBuffer(m_handle);
+        }
 		
 		void fill(const T* data, size_t count = 0, size_t offset = 0) {
 			 m_manager->fillBuffer(m_handle, data, count * sizeof(T), offset * sizeof(T));
diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index 3071286eea20b57c0fc909716ed933a968e01197..02a58ae875f4a9341268e97e53530f9b2b34b446 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -22,28 +22,11 @@
 #include "Sampler.hpp"
 #include "DescriptorWrites.hpp"
 #include "Event.hpp"
+#include "DrawcallRecording.hpp"
 
 namespace vkcv
 {
 
-	struct VertexBufferBinding {
-		vk::DeviceSize	offset;
-		BufferHandle	buffer;
-	};
-
-	struct Mesh {
-		std::vector<VertexBufferBinding>    vertexBufferBindings;
-		BufferHandle                        indexBuffer;
-		size_t                              indexCount;
-	};
-
-	struct PushConstantData {
-        inline PushConstantData(void* data, size_t sizePerDrawcall) : data(data), sizePerDrawcall(sizePerDrawcall){}
-
-		void*   data;
-		size_t  sizePerDrawcall;
-	};
-
     // forward declarations
     class PassManager;
     class PipelineManager;
@@ -236,13 +219,12 @@ namespace vkcv
 		/**
 		 * @brief render a beautiful triangle
 		*/
-		void renderMesh(
-			const PassHandle                        renderpassHandle, 
-			const PipelineHandle                    pipelineHandle,
-			const PushConstantData                  &pushConstantData,
-			const Mesh                              &mesh,
-			const std::vector<DescriptorSetUsage>   &descriptorSets,
-			const std::vector<ImageHandle>	        &renderTargets);
+		void recordDrawcalls(
+			const PassHandle                renderpassHandle, 
+			const PipelineHandle            pipelineHandle,
+			const PushConstantData          &pushConstantData,
+			const std::vector<DrawcallInfo> &drawcalls,
+			const std::vector<ImageHandle>  &renderTargets);
 
 		/**
 		 * @brief end recording and present image
diff --git a/include/vkcv/DescriptorConfig.hpp b/include/vkcv/DescriptorConfig.hpp
index 15083e8c9863ac2e8d44bbe174e0d7f40bf27dfc..86c2e20eb37633e4519749bef507161133e57425 100644
--- a/include/vkcv/DescriptorConfig.hpp
+++ b/include/vkcv/DescriptorConfig.hpp
@@ -11,13 +11,6 @@ namespace vkcv
         vk::DescriptorSetLayout layout;
     };
 
-    struct DescriptorSetUsage {
-        DescriptorSetUsage(uint32_t setLocation, DescriptorSetHandle handle) noexcept;
-
-        const uint32_t              setLocation;
-        const DescriptorSetHandle   handle;
-    };
-
     /*
     * All the types of descriptors (resources) that can be retrieved by the shaders
     */
diff --git a/include/vkcv/DrawcallRecording.hpp b/include/vkcv/DrawcallRecording.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0929ad038fb95ec1573e7c76e5ce13adb84ab760
--- /dev/null
+++ b/include/vkcv/DrawcallRecording.hpp
@@ -0,0 +1,54 @@
+#pragma once
+#include <vulkan/vulkan.hpp>
+#include <vkcv/Handles.hpp>
+#include <vkcv/DescriptorConfig.hpp>
+
+namespace vkcv {
+    struct VertexBufferBinding {
+        inline VertexBufferBinding(vk::DeviceSize offset, vk::Buffer buffer) noexcept
+            : offset(offset), buffer(buffer) {}
+
+        vk::DeviceSize  offset;
+        vk::Buffer      buffer;
+    };
+
+    struct DescriptorSetUsage {
+        inline DescriptorSetUsage(uint32_t setLocation, vk::DescriptorSet vulkanHandle) noexcept
+            : setLocation(setLocation), vulkanHandle(vulkanHandle) {}
+
+        const uint32_t          setLocation;
+        const vk::DescriptorSet vulkanHandle;
+    };
+
+    struct Mesh {
+        inline Mesh(std::vector<VertexBufferBinding> vertexBufferBindings, vk::Buffer indexBuffer, size_t indexCount) noexcept
+            : vertexBufferBindings(vertexBufferBindings), indexBuffer(indexBuffer), indexCount(indexCount){}
+
+        std::vector<VertexBufferBinding>    vertexBufferBindings;
+        vk::Buffer                          indexBuffer;
+        size_t                              indexCount;
+    };
+
+    struct PushConstantData {
+        inline PushConstantData(void* data, size_t sizePerDrawcall) : data(data), sizePerDrawcall(sizePerDrawcall) {}
+
+        void* data;
+        size_t  sizePerDrawcall;
+    };
+
+    struct DrawcallInfo {
+        inline DrawcallInfo(const Mesh& mesh, const std::vector<DescriptorSetUsage>& descriptorSets)
+            : mesh(mesh), descriptorSets(descriptorSets) {}
+
+        Mesh                            mesh;
+        std::vector<DescriptorSetUsage> descriptorSets;
+    };
+
+    void recordDrawcall(
+        const DrawcallInfo      &drawcall,
+        vk::CommandBuffer       cmdBuffer,
+        vk::PipelineLayout      pipelineLayout,
+        const PushConstantData  &pushConstantData,
+        const size_t            drawcallIndex);
+
+}
\ No newline at end of file
diff --git a/projects/cmd_sync_test/src/main.cpp b/projects/cmd_sync_test/src/main.cpp
index e1bbdf3bda378119a3d90688a2e6a7786f16acfe..7c2a5b5e1417b07064cd09feb733e0d0dc54482a 100644
--- a/projects/cmd_sync_test/src/main.cpp
+++ b/projects/cmd_sync_test/src/main.cpp
@@ -18,6 +18,7 @@ int main(int argc, const char** argv) {
 	);
 
 	vkcv::CameraManager cameraManager(window, windowWidth, windowHeight);
+	cameraManager.getCamera().setPosition(glm::vec3(0.f, 0.f, 3.f));
 
 	window.initEvents();
 
@@ -60,14 +61,12 @@ int main(int argc, const char** argv) {
 	
 	indexBuffer.fill(mesh.vertexGroups[0].indexBuffer.data);
 
-	vkcv::Mesh boxMesh;
-	boxMesh.indexBuffer = indexBuffer.getHandle();
-	boxMesh.vertexBufferBindings = {
-		{ mesh.vertexGroups[0].vertexBuffer.attributes[0].offset, vertexBuffer.getHandle() },
-		{ mesh.vertexGroups[0].vertexBuffer.attributes[1].offset, vertexBuffer.getHandle() },
-		{ mesh.vertexGroups[0].vertexBuffer.attributes[2].offset, vertexBuffer.getHandle() }
-	};
-	boxMesh.indexCount = mesh.vertexGroups[0].numIndices;
+	const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
+		vkcv::VertexBufferBinding(mesh.vertexGroups[0].vertexBuffer.attributes[0].offset, vertexBuffer.getVulkanHandle()),
+		vkcv::VertexBufferBinding(mesh.vertexGroups[0].vertexBuffer.attributes[1].offset, vertexBuffer.getVulkanHandle()),
+		vkcv::VertexBufferBinding(mesh.vertexGroups[0].vertexBuffer.attributes[2].offset, vertexBuffer.getVulkanHandle()) };
+
+	const vkcv::Mesh loadedMesh(vertexBufferBindings, indexBuffer.getVulkanHandle(), mesh.vertexGroups[0].numIndices);
 
 	// an example attachment for passes that output to the window
 	const vkcv::AttachmentDescription present_color_attachment(
@@ -150,6 +149,25 @@ int main(int argc, const char** argv) {
 	});
 
 	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+	const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
+
+	const vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle);
+
+	const std::vector<glm::vec3> instancePositions = {
+		glm::vec3( 0.f, -2.f, 0.f),
+		glm::vec3( 3.f,  0.f, 0.f),
+		glm::vec3(-3.f,  0.f, 0.f),
+		glm::vec3( 0.f,  2.f, 0.f)
+	};
+
+	std::vector<glm::mat4> modelMatrices;
+	std::vector<vkcv::DrawcallInfo> drawcalls;
+	for (const auto& position : instancePositions) {
+		modelMatrices.push_back(glm::translate(glm::mat4(1.f), position));
+		drawcalls.push_back(vkcv::DrawcallInfo(loadedMesh, { descriptorUsage }));
+	}
+
+	std::vector<glm::mat4> mvpMatrices;
 
 	auto start = std::chrono::system_clock::now();
 	while (window.isWindowOpen()) {
@@ -159,15 +177,20 @@ int main(int argc, const char** argv) {
 		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(
+		const glm::mat4 viewProjection = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
+		mvpMatrices.clear();
+		for (const auto& m : modelMatrices) {
+			mvpMatrices.push_back(viewProjection * m);
+		}
+		vkcv::PushConstantData pushConstantData((void*)mvpMatrices.data(), sizeof(glm::mat4));
+
+		core.recordDrawcalls(
 			trianglePass,
 			trianglePipeline,
-			vkcv::PushConstantData((void*) &mvp, sizeof(mvp)),
-			boxMesh,
-			{ vkcv::DescriptorSetUsage(0, descriptorSet) },
-			{ swapchainInput, depthBuffer });
+			pushConstantData,
+			drawcalls,
+			renderTargets);
 
 		core.endFrame();
 	}
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index bc832b91df1a4b2cf65bdcb11d771abc4a6bf3c1..bf728391d090e44b441c3ee12b3c2a26f6dca60e 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -170,7 +170,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.renderMesh(
+		core.recordDrawcalls(
 			trianglePass,
 			trianglePipeline,
 			sizeof(mvp),
diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp
index 24aaa9ef45713e1a1d24e079d29f9279f49386d1..649a3f8748be1e05c2c5ee79b3734f5e6387dfc5 100644
--- a/projects/first_triangle/src/main.cpp
+++ b/projects/first_triangle/src/main.cpp
@@ -147,7 +147,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.renderMesh(
+	    core.recordDrawcalls(
 			trianglePass,
 			trianglePipeline,
 			sizeof(mvp),
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index 96fb0f1e9d1287c0df0b9e63196c516bea716f57..08189af15111128aa51cd8619f0ba092d653eb17 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -141,13 +141,12 @@ namespace vkcv
 		m_Context.getDevice().waitIdle(); // TODO: this is a sin against graphics programming, but its getting late - Alex
 	}
 
-	void Core::renderMesh(
-		const PassHandle                        renderpassHandle, 
-		const PipelineHandle                    pipelineHandle, 
-        const PushConstantData                  &pushConstantData,
-		const Mesh                              &mesh,
-        const std::vector<DescriptorSetUsage>   &descriptorSets,
-		const std::vector<ImageHandle>          &renderTargets) {
+	void Core::recordDrawcalls(
+		const PassHandle                renderpassHandle, 
+		const PipelineHandle            pipelineHandle, 
+        const PushConstantData          &pushConstantData,
+        const std::vector<DrawcallInfo> &drawcalls,
+		const std::vector<ImageHandle>  &renderTargets) {
 
 		if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) {
 			return;
@@ -254,33 +253,10 @@ namespace vkcv
                 cmdBuffer.setScissor(0, 1, &dynamicScissor);
             }
 
-            for (uint32_t i = 0; i < mesh.vertexBufferBindings.size(); i++) {
-                const auto &vertexBinding = mesh.vertexBufferBindings[i];
-                const auto vertexBuffer = bufferManager->getBuffer(vertexBinding.buffer);
-                cmdBuffer.bindVertexBuffers(i, (vertexBuffer), (vertexBinding.offset));
+            for (int i = 0; i < drawcalls.size(); i++) {
+                recordDrawcall(drawcalls[i], cmdBuffer, pipelineLayout, pushConstantData, i);
             }
 
-            for (const auto &descriptorUsage : descriptorSets) {
-                cmdBuffer.bindDescriptorSets(
-                    vk::PipelineBindPoint::eGraphics, 
-                    pipelineLayout, 
-                    descriptorUsage.setLocation, 
-                    m_DescriptorManager->getDescriptorSet(descriptorUsage.handle).vulkanHandle,
-                    nullptr);
-            }
-
-            const vk::Buffer indexBuffer = m_BufferManager->getBuffer(mesh.indexBuffer);
-
-            cmdBuffer.bindIndexBuffer(indexBuffer, 0, vk::IndexType::eUint16);	//FIXME: choose proper size
-
-            cmdBuffer.pushConstants(
-                pipelineLayout, 
-                vk::ShaderStageFlagBits::eAll, 
-                0, 
-                pushConstantData.sizePerDrawcall, 
-                pushConstantData.data);
-
-            cmdBuffer.drawIndexed(mesh.indexCount, 1, 0, 0, {});
             cmdBuffer.endRenderPass();
         };
 
diff --git a/src/vkcv/DescriptorConfig.cpp b/src/vkcv/DescriptorConfig.cpp
index 122d29fe76a6c9a1c933e40c21486f975fd286ce..be6cfc9b40baad6636e6bc6a2b6e803aacd7ddc0 100644
--- a/src/vkcv/DescriptorConfig.cpp
+++ b/src/vkcv/DescriptorConfig.cpp
@@ -1,10 +1,6 @@
 #include "vkcv/DescriptorConfig.hpp"
 
 namespace vkcv {
-
-    DescriptorSetUsage::DescriptorSetUsage(uint32_t setLocation, DescriptorSetHandle handle) noexcept 
-    : setLocation(setLocation), handle(handle) {}
-
 	DescriptorBinding::DescriptorBinding(
 		DescriptorType descriptorType,
 		uint32_t descriptorCount,
diff --git a/src/vkcv/DrawcallRecording.cpp b/src/vkcv/DrawcallRecording.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..85b6eeb5fa413223b7b7f10f77b868252912041b
--- /dev/null
+++ b/src/vkcv/DrawcallRecording.cpp
@@ -0,0 +1,41 @@
+#include <vkcv/DrawcallRecording.hpp>
+
+namespace vkcv {
+
+    void recordDrawcall(
+        const DrawcallInfo      &drawcall,
+        vk::CommandBuffer       cmdBuffer,
+        vk::PipelineLayout      pipelineLayout,
+        const PushConstantData  &pushConstantData,
+        const size_t            drawcallIndex) {
+
+        for (uint32_t i = 0; i < drawcall.mesh.vertexBufferBindings.size(); i++) {
+            const auto& vertexBinding = drawcall.mesh.vertexBufferBindings[i];
+            cmdBuffer.bindVertexBuffers(i, vertexBinding.buffer, vertexBinding.offset);
+        }
+
+        for (const auto& descriptorUsage : drawcall.descriptorSets) {
+            cmdBuffer.bindDescriptorSets(
+                vk::PipelineBindPoint::eGraphics,
+                pipelineLayout,
+                descriptorUsage.setLocation,
+                descriptorUsage.vulkanHandle,
+                nullptr);
+        }
+
+        cmdBuffer.bindIndexBuffer(drawcall.mesh.indexBuffer, 0, vk::IndexType::eUint16);	//FIXME: choose proper size
+
+        const size_t drawcallPushConstantOffset = drawcallIndex * pushConstantData.sizePerDrawcall;
+        // char* cast because void* does not support pointer arithmetic
+        const void* drawcallPushConstantData = drawcallPushConstantOffset + (char*)pushConstantData.data;
+
+        cmdBuffer.pushConstants(
+            pipelineLayout,
+            vk::ShaderStageFlagBits::eAll,
+            0,
+            pushConstantData.sizePerDrawcall,
+            drawcallPushConstantData);
+
+        cmdBuffer.drawIndexed(drawcall.mesh.indexCount, 1, 0, 0, {});
+    }
+}
\ No newline at end of file