From 1c84af84434ccfdc4072ec5f09ebefc10814d373 Mon Sep 17 00:00:00 2001
From: Artur Wasmut <awasmut@uni-koblenz.de>
Date: Sun, 16 May 2021 14:35:57 +0200
Subject: [PATCH] implement PipelineManager class.

---
 config/Sources.cmake                          |   7 +-
 include/vkcv/Core.hpp                         |  26 ++-
 .../vkcv/{Pipeline.hpp => PipelineConfig.hpp} |  12 +-
 projects/first_triangle/src/main.cpp          |   7 +-
 src/vkcv/Core.cpp                             | 194 +--------------
 src/vkcv/PassManager.cpp                      |   3 +-
 src/vkcv/{Pipeline.cpp => PipelineConfig.cpp} |   4 +-
 src/vkcv/PipelineManager.cpp                  | 221 ++++++++++++++++++
 src/vkcv/PipelineManager.hpp                  |  35 +++
 9 files changed, 302 insertions(+), 207 deletions(-)
 rename include/vkcv/{Pipeline.hpp => PipelineConfig.hpp} (77%)
 rename src/vkcv/{Pipeline.cpp => PipelineConfig.cpp} (59%)
 create mode 100644 src/vkcv/PipelineManager.cpp
 create mode 100644 src/vkcv/PipelineManager.hpp

diff --git a/config/Sources.cmake b/config/Sources.cmake
index 0a612e7f..dc4d2e85 100644
--- a/config/Sources.cmake
+++ b/config/Sources.cmake
@@ -25,6 +25,9 @@ set(vkcv_sources
 		${vkcv_include}/vkcv/ShaderProgram.hpp
 		${vkcv_source}/vkcv/ShaderProgram.cpp
 
-		${vkcv_include}/vkcv/Pipeline.hpp
-		${vkcv_source}/vkcv/Pipeline.cpp
+		${vkcv_include}/vkcv/PipelineConfig.hpp
+		${vkcv_source}/vkcv/PipelineConfig.cpp
+
+		${vkcv_source}/vkcv/PipelineManager.hpp
+		${vkcv_source}/vkcv/PipelineManager.cpp
 )
diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index 048b1ad5..e700ac4a 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -12,7 +12,7 @@
 #include "vkcv/Window.hpp"
 #include "vkcv/PassConfig.hpp"
 #include "vkcv/Handles.hpp"
-#include "vkcv/Pipeline.hpp"
+#include "vkcv/PipelineConfig.hpp"
 
 namespace vkcv
 {
@@ -21,6 +21,7 @@ namespace vkcv
 
     // forward declarations
     class PassManager;
+    class PipelineManager;
 
     class Core final
     {
@@ -46,6 +47,7 @@ namespace vkcv
         std::vector<vk::PipelineLayout> m_PipelineLayouts;
 
         std::unique_ptr<PassManager> m_PassManager;
+        std::unique_ptr<PipelineManager> m_PipelineManager;
     public:
         /**
          * Destructor of #Core destroys the Vulkan objects contained in the core's context.
@@ -108,19 +110,29 @@ namespace vkcv
                            std::vector<const char*> deviceExtensions    = {});
 
         /**
-         * Creates a basic vulkan graphics pipeline using @p pipeline from the pipeline class and returns it using the @p handle.
+         * Creates a basic vulkan graphics pipeline using @p config from the pipeline config class and returns it using the @p handle.
          * Fixed Functions for pipeline are set with standard values.
          *
-         * @param pipeline a pipeline object from the pipeline class
+         * @param config a pipeline config object from the pipeline config class
          * @param handle a handle to return the created vulkan handle
-         * @return True if Pipeline creation was successfull, False if not
+         * @return True if pipeline creation was successful, False if not
          */
-        bool createGraphicsPipeline(const Pipeline &pipeline, PipelineHandle &handle);
+        [[nodiscard]]
+        PipelineHandle createGraphicsPipeline(const PipelineConfig &config);
+
+        /**
+         * Creates a basic vulkan render pass using @p config from the render pass config class and returns it using the @p handle.
+         * Fixed Functions for pipeline are set with standard values.
+         *
+         * @param config a render pass config object from the render pass config class
+         * @param handle a handle to return the created vulkan handle
+         * @return True if render pass creation was successful, False if not
+         */
+        [[nodiscard]]
+        PassHandle createPass(const PassConfig &config);
 
         // TODO:
         BufferHandle createBuffer(const Buffer &buf);
 
-        [[nodiscard]]
-        PassHandle createPass(const PassConfig &config);
     };
 }
diff --git a/include/vkcv/Pipeline.hpp b/include/vkcv/PipelineConfig.hpp
similarity index 77%
rename from include/vkcv/Pipeline.hpp
rename to include/vkcv/PipelineConfig.hpp
index 06380d0a..5aa4dac9 100644
--- a/include/vkcv/Pipeline.hpp
+++ b/include/vkcv/PipelineConfig.hpp
@@ -4,8 +4,8 @@
  * @brief Pipeline class to handle shader stages
  */
 
-#ifndef VKCV_PIPELINE_HPP
-#define VKCV_PIPELINE_HPP
+#ifndef VKCV_PIPELINECONFIG_HPP
+#define VKCV_PIPELINECONFIG_HPP
 
 #include <vector>
 #include <cstdint>
@@ -14,13 +14,13 @@
 
 namespace vkcv {
 
-    class Pipeline {
+    class PipelineConfig {
 
     public:
         /**
          *  Default constructer is deleted!
          */
-        Pipeline() = delete;
+        PipelineConfig() = delete;
 
         /**
          *  Constructor for the pipeline. Creates a pipeline using @p vertexCode, @p fragmentCode as well as the
@@ -31,7 +31,7 @@ namespace vkcv {
          * @param width width of the application window
          * @param passHandle handle for Render Pass
          */
-        Pipeline(const ShaderProgram& shaderProgram, uint32_t width, uint32_t height, PassHandle &passHandle);
+        PipelineConfig(const ShaderProgram& shaderProgram, uint32_t width, uint32_t height, PassHandle &passHandle);
 
 		ShaderProgram m_shaderProgram;
         uint32_t m_height;
@@ -40,4 +40,4 @@ namespace vkcv {
     };
 
 }
-#endif //VKCV_PIPELINE_HPP
+#endif //VKCV_PIPELINECONFIG_HPP
diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp
index e39e19c3..4c2a380e 100644
--- a/projects/first_triangle/src/main.cpp
+++ b/projects/first_triangle/src/main.cpp
@@ -61,9 +61,10 @@ int main(int argc, const char** argv) {
 	triangleShaderProgram.addShader(vkcv::ShaderProgram::ShaderStage::VERTEX, "shaders/vert.spv");
 	triangleShaderProgram.addShader(vkcv::ShaderProgram::ShaderStage::FRAGMENT, "shaders/frag.spv");
 
-	const vkcv::Pipeline trianglePipelineDefinition(triangleShaderProgram, windowWidth, windowHeight, trianglePass);
-	vkcv::PipelineHandle trianglePipeline{};
-	if (!core.createGraphicsPipeline(trianglePipelineDefinition, trianglePipeline)) {
+	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;
 	}
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index 6015363c..07e00816 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -6,6 +6,7 @@
 
 #include "vkcv/Core.hpp"
 #include "PassManager.hpp"
+#include "PipelineManager.hpp"
 
 namespace vkcv
 {
@@ -465,7 +466,8 @@ namespace vkcv
 			m_NextPipelineId(0),
 			m_Pipelines{},
 			m_PipelineLayouts{},
-			m_PassManager{std::make_unique<PassManager>(m_Context.m_Device)}
+			m_PassManager{std::make_unique<PassManager>(m_Context.m_Device)},
+			m_PipelineManager{std::make_unique<PipelineManager>(m_Context.m_Device)}
     {}
 
 	Core::~Core() noexcept {
@@ -493,194 +495,16 @@ namespace vkcv
 		m_Context.m_Instance.destroySurfaceKHR(m_swapchain.getSurface());
 	}
 
-	bool Core::createGraphicsPipeline(const Pipeline &pipeline, PipelineHandle &handle) {
-		
-		// TODO: this search could be avoided if ShaderProgram could be queried for a specific stage
-		const auto shaderStageFlags = pipeline.m_shaderProgram.getShaderStages();
-		const auto shaderCode = pipeline.m_shaderProgram.getShaderCode();
-		std::vector<char> vertexCode;
-		std::vector<char> fragCode;
-		assert(shaderStageFlags.size() == shaderCode.size());
-		for (int i = 0; i < shaderStageFlags.size(); i++) {
-			switch (shaderStageFlags[i]) {
-				case vk::ShaderStageFlagBits::eVertex: vertexCode = shaderCode[i]; break;
-				case vk::ShaderStageFlagBits::eFragment: fragCode = shaderCode[i]; break;
-				default: std::cout << "Core::createGraphicsPipeline encountered unknown shader stage" << std::endl; return false;
-			}
-		}
-
-		const bool foundVertexCode = !vertexCode.empty();
-		const bool foundFragCode = !fragCode.empty();
-		const bool foundRequiredShaderCode = foundVertexCode && foundFragCode;
-		if (!foundRequiredShaderCode) {
-			std::cout << "Core::createGraphicsPipeline requires vertex and fragment shader code" << std::endl; 
-			return false;
-		}
-
-		// vertex shader stage
-		// TODO: store shader code as uint32_t in ShaderProgram to avoid pointer cast
-		vk::ShaderModuleCreateInfo vertexModuleInfo({}, vertexCode.size(), reinterpret_cast<uint32_t*>(vertexCode.data()));
-		vk::ShaderModule vertexModule{};
-		if (m_Context.m_Device.createShaderModule(&vertexModuleInfo, nullptr, &vertexModule) != vk::Result::eSuccess)
-			return false;
-
-		vk::PipelineShaderStageCreateInfo pipelineVertexShaderStageInfo(
-			{},
-			vk::ShaderStageFlagBits::eVertex,
-			vertexModule,
-			"main",
-			nullptr
-		);
-
-		// fragment shader stage
-		vk::ShaderModuleCreateInfo fragmentModuleInfo({}, fragCode.size(), reinterpret_cast<uint32_t*>(fragCode.data()));
-		vk::ShaderModule fragmentModule{};
-		if (m_Context.m_Device.createShaderModule(&fragmentModuleInfo, nullptr, &fragmentModule) != vk::Result::eSuccess)
-        {
-		    m_Context.m_Device.destroy(vertexModule);
-			return false;
-        }
-
-		vk::PipelineShaderStageCreateInfo pipelineFragmentShaderStageInfo(
-			{},
-			vk::ShaderStageFlagBits::eFragment,
-			fragmentModule,
-			"main",
-			nullptr
-		);
-
-		// vertex input state
-		vk::VertexInputBindingDescription vertexInputBindingDescription(0, 12, vk::VertexInputRate::eVertex);
-		vk::VertexInputAttributeDescription vertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, 0);
-
-		vk::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo(
-			{},
-			1,
-			&vertexInputBindingDescription,
-			1,
-			&vertexInputAttributeDescription
-		);
-
-		// input assembly state
-		vk::PipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo(
-			{},
-			vk::PrimitiveTopology::eTriangleList,
-			false
-		);
-
-		// viewport state
-		vk::Viewport viewport(0.f, 0.f, static_cast<float>(pipeline.m_width), static_cast<float>(pipeline.m_height), 0.f, 1.f);
-		vk::Rect2D scissor({ 0,0 }, { pipeline.m_width, pipeline.m_height });
-		vk::PipelineViewportStateCreateInfo pipelineViewportStateCreateInfo({}, 1, &viewport, 1, &scissor);
-
-		// rasterization state
-		vk::PipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo(
-			{},
-			false,
-			false,
-			vk::PolygonMode::eFill,
-			vk::CullModeFlagBits::eNone,
-			vk::FrontFace::eCounterClockwise,
-			false,
-			0.f,
-			0.f,
-			0.f,
-			1.f
-		);
-
-		// multisample state
-		vk::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo(
-			{},
-			vk::SampleCountFlagBits::e1,
-			false,
-			0.f,
-			nullptr,
-			false,
-			false
-		);
-
-		// color blend state
-		vk::ColorComponentFlags colorWriteMask(VK_COLOR_COMPONENT_R_BIT |
-			VK_COLOR_COMPONENT_G_BIT |
-			VK_COLOR_COMPONENT_B_BIT |
-			VK_COLOR_COMPONENT_A_BIT);
-		vk::PipelineColorBlendAttachmentState colorBlendAttachmentState(
-			false,
-			vk::BlendFactor::eOne,
-			vk::BlendFactor::eOne,
-			vk::BlendOp::eAdd,
-			vk::BlendFactor::eOne,
-			vk::BlendFactor::eOne,
-			vk::BlendOp::eAdd,
-			colorWriteMask
-		);
-		vk::PipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo(
-			{},
-			false,
-			vk::LogicOp::eClear,
-			1,	//TODO: hardcoded to one
-			&colorBlendAttachmentState,
-			{ 1.f,1.f,1.f,1.f }
-		);
-
-		// pipeline layout
-		vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo(
-			{},
-			0,
-			{},
-			0,
-			{}
-		);
-		vk::PipelineLayout vkPipelineLayout{};
-		if (m_Context.m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &vkPipelineLayout) != vk::Result::eSuccess)
-        {
-		    m_Context.m_Device.destroy(vertexModule);
-		    m_Context.m_Device.destroy(fragmentModule);
-			return false;
-        }
-
-		// graphics pipeline create
-		std::vector<vk::PipelineShaderStageCreateInfo> shaderStages = { pipelineVertexShaderStageInfo, pipelineFragmentShaderStageInfo };
-		vk::GraphicsPipelineCreateInfo graphicsPipelineCreateInfo(
-			{},
-			static_cast<uint32_t>(shaderStages.size()),
-			shaderStages.data(),
-			&pipelineVertexInputStateCreateInfo,
-			&pipelineInputAssemblyStateCreateInfo,
-			nullptr,
-			&pipelineViewportStateCreateInfo,
-			&pipelineRasterizationStateCreateInfo,
-			&pipelineMultisampleStateCreateInfo,
-			nullptr,
-			&pipelineColorBlendStateCreateInfo,
-			nullptr,
-			vkPipelineLayout,
-			m_PassManager->getVkPass(pipeline.m_passHandle),
-			0,
-			{},
-			0
-		);
-
-		vk::Pipeline vkPipeline{};
-		if (m_Context.m_Device.createGraphicsPipelines(nullptr, 1, &graphicsPipelineCreateInfo, nullptr, &vkPipeline) != vk::Result::eSuccess)
-        {
-            m_Context.m_Device.destroy(vertexModule);
-            m_Context.m_Device.destroy(fragmentModule);
-			return false;
-        }
-
-		m_Context.m_Device.destroy(vertexModule);
-		m_Context.m_Device.destroy(fragmentModule);
-
-		m_Pipelines.push_back(vkPipeline);
-		m_PipelineLayouts.push_back(vkPipelineLayout);
-		handle.id = m_NextPipelineId++;
+    PipelineHandle Core::createGraphicsPipeline(const PipelineConfig &config)
+    {
+        const vk::RenderPass &pass = m_PassManager->getVkPass(config.m_passHandle);
+        return m_PipelineManager->createPipeline(config, pass);
+    }
 
-		return true;
-	}
 
     PassHandle Core::createPass(const PassConfig &config)
     {
         return m_PassManager->createPass(config);
     }
+
 }
diff --git a/src/vkcv/PassManager.cpp b/src/vkcv/PassManager.cpp
index 43674620..3184cfa2 100644
--- a/src/vkcv/PassManager.cpp
+++ b/src/vkcv/PassManager.cpp
@@ -56,9 +56,8 @@ namespace vkcv
     PassManager::~PassManager() noexcept
     {
         for(const auto &pass : m_RenderPasses)
-        {
             m_Device.destroy(pass);
-        }
+
         m_RenderPasses.clear();
         m_NextPassId = 1;
     }
diff --git a/src/vkcv/Pipeline.cpp b/src/vkcv/PipelineConfig.cpp
similarity index 59%
rename from src/vkcv/Pipeline.cpp
rename to src/vkcv/PipelineConfig.cpp
index df3e6041..9fed7323 100644
--- a/src/vkcv/Pipeline.cpp
+++ b/src/vkcv/PipelineConfig.cpp
@@ -4,10 +4,10 @@
  * @brief Pipeline class to handle shader stages
  */
 
-#include "vkcv/Pipeline.hpp"
+#include "vkcv/PipelineConfig.hpp"
 
 namespace vkcv {
 
-    Pipeline::Pipeline(const ShaderProgram& shaderProgram, uint32_t width, uint32_t height, PassHandle &passHandle):
+    PipelineConfig::PipelineConfig(const ShaderProgram& shaderProgram, uint32_t width, uint32_t height, PassHandle &passHandle):
 		m_shaderProgram(shaderProgram), m_height(height), m_width(width), m_passHandle(passHandle) {}
 }
diff --git a/src/vkcv/PipelineManager.cpp b/src/vkcv/PipelineManager.cpp
new file mode 100644
index 00000000..327c443c
--- /dev/null
+++ b/src/vkcv/PipelineManager.cpp
@@ -0,0 +1,221 @@
+#include "PipelineManager.hpp"
+
+namespace vkcv
+{
+
+    PipelineManager::PipelineManager(vk::Device device) noexcept :
+    m_Device{device},
+    m_Pipelines{},
+    m_PipelineLayouts{},
+    m_NextPipelineId{1}
+    {}
+
+    PipelineManager::~PipelineManager() noexcept
+    {
+        for(const auto &pipeline: m_Pipelines)
+            m_Device.destroy(pipeline);
+
+        for(const auto &layout : m_PipelineLayouts)
+            m_Device.destroy(layout);
+
+        m_Pipelines.clear();
+        m_PipelineLayouts.clear();
+        m_NextPipelineId = 1;
+    }
+
+    PipelineHandle PipelineManager::createPipeline(const PipelineConfig &config, const vk::RenderPass &pass)
+    {
+
+        // TODO: this search could be avoided if ShaderProgram could be queried for a specific stage
+        const auto shaderStageFlags = config.m_shaderProgram.getShaderStages();
+        const auto shaderCode = config.m_shaderProgram.getShaderCode();
+        std::vector<char> vertexCode;
+        std::vector<char> fragCode;
+        assert(shaderStageFlags.size() == shaderCode.size());
+        for (int i = 0; i < shaderStageFlags.size(); i++) {
+            switch (shaderStageFlags[i]) {
+                case vk::ShaderStageFlagBits::eVertex: vertexCode = shaderCode[i]; break;
+                case vk::ShaderStageFlagBits::eFragment: fragCode = shaderCode[i]; break;
+                default: std::cout << "Core::createGraphicsPipeline encountered unknown shader stage" << std::endl;
+                return PipelineHandle{0};
+            }
+        }
+
+        const bool foundVertexCode = !vertexCode.empty();
+        const bool foundFragCode = !fragCode.empty();
+        const bool foundRequiredShaderCode = foundVertexCode && foundFragCode;
+        if (!foundRequiredShaderCode) {
+            std::cout << "Core::createGraphicsPipeline requires vertex and fragment shader code" << std::endl;
+            return PipelineHandle{0};
+        }
+
+        // vertex shader stage
+        // TODO: store shader code as uint32_t in ShaderProgram to avoid pointer cast
+        vk::ShaderModuleCreateInfo vertexModuleInfo({}, vertexCode.size(), reinterpret_cast<uint32_t*>(vertexCode.data()));
+        vk::ShaderModule vertexModule{};
+        if (m_Device.createShaderModule(&vertexModuleInfo, nullptr, &vertexModule) != vk::Result::eSuccess)
+            return PipelineHandle{0};
+
+        vk::PipelineShaderStageCreateInfo pipelineVertexShaderStageInfo(
+                {},
+                vk::ShaderStageFlagBits::eVertex,
+                vertexModule,
+                "main",
+                nullptr
+        );
+
+        // fragment shader stage
+        vk::ShaderModuleCreateInfo fragmentModuleInfo({}, fragCode.size(), reinterpret_cast<uint32_t*>(fragCode.data()));
+        vk::ShaderModule fragmentModule{};
+        if (m_Device.createShaderModule(&fragmentModuleInfo, nullptr, &fragmentModule) != vk::Result::eSuccess)
+        {
+            m_Device.destroy(vertexModule);
+            return PipelineHandle{0};
+        }
+
+        vk::PipelineShaderStageCreateInfo pipelineFragmentShaderStageInfo(
+                {},
+                vk::ShaderStageFlagBits::eFragment,
+                fragmentModule,
+                "main",
+                nullptr
+        );
+
+        // vertex input state
+        vk::VertexInputBindingDescription vertexInputBindingDescription(0, 12, vk::VertexInputRate::eVertex);
+        vk::VertexInputAttributeDescription vertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, 0);
+
+        vk::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo(
+                {},
+                1,
+                &vertexInputBindingDescription,
+                1,
+                &vertexInputAttributeDescription
+        );
+
+        // input assembly state
+        vk::PipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo(
+                {},
+                vk::PrimitiveTopology::eTriangleList,
+                false
+        );
+
+        // viewport state
+        vk::Viewport viewport(0.f, 0.f, static_cast<float>(config.m_width), static_cast<float>(config.m_height), 0.f, 1.f);
+        vk::Rect2D scissor({ 0,0 }, { config.m_width, config.m_height });
+        vk::PipelineViewportStateCreateInfo pipelineViewportStateCreateInfo({}, 1, &viewport, 1, &scissor);
+
+        // rasterization state
+        vk::PipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo(
+                {},
+                false,
+                false,
+                vk::PolygonMode::eFill,
+                vk::CullModeFlagBits::eNone,
+                vk::FrontFace::eCounterClockwise,
+                false,
+                0.f,
+                0.f,
+                0.f,
+                1.f
+        );
+
+        // multisample state
+        vk::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo(
+                {},
+                vk::SampleCountFlagBits::e1,
+                false,
+                0.f,
+                nullptr,
+                false,
+                false
+        );
+
+        // color blend state
+        vk::ColorComponentFlags colorWriteMask(VK_COLOR_COMPONENT_R_BIT |
+                                               VK_COLOR_COMPONENT_G_BIT |
+                                               VK_COLOR_COMPONENT_B_BIT |
+                                               VK_COLOR_COMPONENT_A_BIT);
+        vk::PipelineColorBlendAttachmentState colorBlendAttachmentState(
+                false,
+                vk::BlendFactor::eOne,
+                vk::BlendFactor::eOne,
+                vk::BlendOp::eAdd,
+                vk::BlendFactor::eOne,
+                vk::BlendFactor::eOne,
+                vk::BlendOp::eAdd,
+                colorWriteMask
+        );
+        vk::PipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo(
+                {},
+                false,
+                vk::LogicOp::eClear,
+                1,	//TODO: hardcoded to one
+                &colorBlendAttachmentState,
+                { 1.f,1.f,1.f,1.f }
+        );
+
+        // pipeline layout
+        vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo(
+                {},
+                0,
+                {},
+                0,
+                {}
+        );
+        vk::PipelineLayout vkPipelineLayout{};
+        if (m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &vkPipelineLayout) != vk::Result::eSuccess)
+        {
+            m_Device.destroy(vertexModule);
+            m_Device.destroy(fragmentModule);
+            return PipelineHandle{0};
+        }
+
+        // graphics pipeline create
+        std::vector<vk::PipelineShaderStageCreateInfo> shaderStages = { pipelineVertexShaderStageInfo, pipelineFragmentShaderStageInfo };
+        vk::GraphicsPipelineCreateInfo graphicsPipelineCreateInfo(
+                {},
+                static_cast<uint32_t>(shaderStages.size()),
+                shaderStages.data(),
+                &pipelineVertexInputStateCreateInfo,
+                &pipelineInputAssemblyStateCreateInfo,
+                nullptr,
+                &pipelineViewportStateCreateInfo,
+                &pipelineRasterizationStateCreateInfo,
+                &pipelineMultisampleStateCreateInfo,
+                nullptr,
+                &pipelineColorBlendStateCreateInfo,
+                nullptr,
+                vkPipelineLayout,
+                pass,
+                0,
+                {},
+                0
+        );
+
+        vk::Pipeline vkPipeline{};
+        if (m_Device.createGraphicsPipelines(nullptr, 1, &graphicsPipelineCreateInfo, nullptr, &vkPipeline) != vk::Result::eSuccess)
+        {
+            m_Device.destroy(vertexModule);
+            m_Device.destroy(fragmentModule);
+            return PipelineHandle{0};
+        }
+
+        m_Device.destroy(vertexModule);
+        m_Device.destroy(fragmentModule);
+
+        m_Pipelines.push_back(vkPipeline);
+        m_PipelineLayouts.push_back(vkPipelineLayout);
+        return PipelineHandle{m_NextPipelineId++};
+    }
+
+    vk::Pipeline PipelineManager::getVkPipeline(const PipelineHandle &handle) const
+    {
+        return m_Pipelines[handle.id -1];
+    }
+
+    vk::PipelineLayout PipelineManager::getVkPipelineLayout(const PipelineHandle &handle) const
+    {
+        return m_PipelineLayouts[handle.id - 1];
+    }
+}
\ No newline at end of file
diff --git a/src/vkcv/PipelineManager.hpp b/src/vkcv/PipelineManager.hpp
new file mode 100644
index 00000000..b5c0948e
--- /dev/null
+++ b/src/vkcv/PipelineManager.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <vulkan/vulkan.hpp>
+#include <vector>
+#include "vkcv/Handles.hpp"
+#include "vkcv/PipelineConfig.hpp"
+
+namespace vkcv
+{
+    class PipelineManager
+    {
+    private:
+        vk::Device m_Device;
+        std::vector<vk::Pipeline> m_Pipelines;
+        std::vector<vk::PipelineLayout> m_PipelineLayouts;
+        uint64_t m_NextPipelineId;
+    public:
+        PipelineManager() = delete; // no default ctor
+        explicit PipelineManager(vk::Device device) noexcept; // ctor
+        ~PipelineManager() noexcept; // dtor
+
+        PipelineManager(const PipelineManager &other) = delete; // copy-ctor
+        PipelineManager(PipelineManager &&other) = delete; // move-ctor;
+
+        PipelineManager & operator=(const PipelineManager &other) = delete; // copy-assign op
+        PipelineManager & operator=(PipelineManager &&other) = delete; // move-assign op
+
+        PipelineHandle createPipeline(const PipelineConfig &config, const vk::RenderPass &pass);
+
+        [[nodiscard]]
+        vk::Pipeline getVkPipeline(const PipelineHandle &handle) const;
+        [[nodiscard]]
+        vk::PipelineLayout getVkPipelineLayout(const PipelineHandle &handle) const;
+    };
+}
-- 
GitLab