#include "GraphicsPipelineManager.hpp"

#include "vkcv/Core.hpp"
#include "vkcv/Image.hpp"
#include "vkcv/Logger.hpp"
#include "vkcv/Multisampling.hpp"

namespace vkcv {

	uint64_t GraphicsPipelineManager::getIdFrom(const GraphicsPipelineHandle &handle) const {
		return handle.getId();
	}

	GraphicsPipelineHandle
	GraphicsPipelineManager::createById(uint64_t id, const HandleDestroyFunction &destroy) {
		return GraphicsPipelineHandle(id, destroy);
	}

	void GraphicsPipelineManager::destroyById(uint64_t id) {
		auto &pipeline = getById(id);

		if (pipeline.m_handle) {
			getCore().getContext().getDevice().destroy(pipeline.m_handle);
			pipeline.m_handle = nullptr;
		}

		if (pipeline.m_layout) {
			getCore().getContext().getDevice().destroy(pipeline.m_layout);
			pipeline.m_layout = nullptr;
		}
	}

	GraphicsPipelineManager::GraphicsPipelineManager() noexcept :
		HandleManager<GraphicsPipelineEntry, GraphicsPipelineHandle>() {}

	GraphicsPipelineManager::~GraphicsPipelineManager() noexcept {
		clear();
	}

	// currently assuming default 32 bit formats, no lower precision or normalized variants
	// supported
	vk::Format vertexFormatToVulkanFormat(const VertexAttachmentFormat format) {
		switch (format) {
		case VertexAttachmentFormat::FLOAT:
			return vk::Format::eR32Sfloat;
		case VertexAttachmentFormat::FLOAT2:
			return vk::Format::eR32G32Sfloat;
		case VertexAttachmentFormat::FLOAT3:
			return vk::Format::eR32G32B32Sfloat;
		case VertexAttachmentFormat::FLOAT4:
			return vk::Format::eR32G32B32A32Sfloat;
		case VertexAttachmentFormat::INT:
			return vk::Format::eR32Sint;
		case VertexAttachmentFormat::INT2:
			return vk::Format::eR32G32Sint;
		case VertexAttachmentFormat::INT3:
			return vk::Format::eR32G32B32Sint;
		case VertexAttachmentFormat::INT4:
			return vk::Format::eR32G32B32A32Sint;
		default:
			vkcv_log(LogLevel::WARNING, "Unknown vertex format");
			return vk::Format::eUndefined;
		}
	}

	vk::PrimitiveTopology
	primitiveTopologyToVulkanPrimitiveTopology(const PrimitiveTopology topology) {
		switch (topology) {
		case (PrimitiveTopology::PointList):
			return vk::PrimitiveTopology::ePointList;
		case (PrimitiveTopology::LineList):
			return vk::PrimitiveTopology::eLineList;
		case (PrimitiveTopology::TriangleList):
			return vk::PrimitiveTopology::eTriangleList;
		case (PrimitiveTopology::PatchList):
			return vk::PrimitiveTopology::ePatchList;
		default:
			vkcv_log(LogLevel::ERROR, "Unknown primitive topology type");
			return vk::PrimitiveTopology::eTriangleList;
		}
	}

	vk::CompareOp depthTestToVkCompareOp(DepthTest depthTest) {
		switch (depthTest) {
		case (DepthTest::None):
			return vk::CompareOp::eAlways;
		case (DepthTest::Less):
			return vk::CompareOp::eLess;
		case (DepthTest::LessEqual):
			return vk::CompareOp::eLessOrEqual;
		case (DepthTest::Greater):
			return vk::CompareOp::eGreater;
		case (DepthTest::GreatherEqual):
			return vk::CompareOp::eGreaterOrEqual;
		case (DepthTest::Equal):
			return vk::CompareOp::eEqual;
		default:
			vkcv_log(LogLevel::ERROR, "Unknown depth test enum");
			return vk::CompareOp::eAlways;
		}
	}

	vk::ShaderStageFlagBits shaderStageToVkShaderStage(ShaderStage stage) {
		switch (stage) {
		case ShaderStage::VERTEX:
			return vk::ShaderStageFlagBits::eVertex;
		case ShaderStage::FRAGMENT:
			return vk::ShaderStageFlagBits::eFragment;
		case ShaderStage::GEOMETRY:
			return vk::ShaderStageFlagBits::eGeometry;
		case ShaderStage::TESS_CONTROL:
			return vk::ShaderStageFlagBits::eTessellationControl;
		case ShaderStage::TESS_EVAL:
			return vk::ShaderStageFlagBits::eTessellationEvaluation;
		case ShaderStage::COMPUTE:
			return vk::ShaderStageFlagBits::eCompute;
		case ShaderStage::TASK:
			return vk::ShaderStageFlagBits::eTaskNV;
		case ShaderStage::MESH:
			return vk::ShaderStageFlagBits::eMeshNV;
		default:
			vkcv_log(LogLevel::ERROR, "Unknown shader stage");
			return vk::ShaderStageFlagBits::eAll;
		}
	}

	bool createPipelineShaderStageCreateInfo(const ShaderProgram &shaderProgram, ShaderStage stage,
											 vk::Device device,
											 vk::PipelineShaderStageCreateInfo* outCreateInfo) {

		assert(outCreateInfo);
		std::vector<uint32_t> code = shaderProgram.getShaderBinary(stage);
		vk::ShaderModuleCreateInfo vertexModuleInfo({}, code.size() * sizeof(uint32_t),
													code.data());
		vk::ShaderModule shaderModule;
		if (device.createShaderModule(&vertexModuleInfo, nullptr, &shaderModule)
			!= vk::Result::eSuccess)
			return false;

		const static auto entryName = "main";

		*outCreateInfo = vk::PipelineShaderStageCreateInfo({}, shaderStageToVkShaderStage(stage),
														   shaderModule, entryName, nullptr);
		return true;
	}

	/**
	 * Fills Vertex Attribute and Binding Description with the corresponding objects form the Vertex
	 * Layout.
	 * @param vertexAttributeDescriptions
	 * @param vertexBindingDescriptions
	 * @param existsVertexShader
	 * @param config
	 */
	void fillVertexInputDescription(
		std::vector<vk::VertexInputAttributeDescription> &vertexAttributeDescriptions,
		std::vector<vk::VertexInputBindingDescription> &vertexBindingDescriptions,
		const bool existsVertexShader, const GraphicsPipelineConfig &config) {

		if (existsVertexShader) {
			const VertexLayout &layout = config.getVertexLayout();

			// iterate over the layout's specified, mutually exclusive buffer bindings that make up
			// a vertex buffer
			for (const auto &vertexBinding : layout.vertexBindings) {
				vertexBindingDescriptions.emplace_back(vertexBinding.bindingLocation,
													   vertexBinding.stride,
													   vk::VertexInputRate::eVertex);

				// iterate over the bindings' specified, mutually exclusive vertex input attachments
				// that make up a vertex
				for (const auto &vertexAttachment : vertexBinding.vertexAttachments) {
					vertexAttributeDescriptions.emplace_back(
						vertexAttachment.inputLocation, vertexBinding.bindingLocation,
						vertexFormatToVulkanFormat(vertexAttachment.format),
						vertexAttachment.offset % vertexBinding.stride);
				}
			}
		}
	}

	/**
	 * Creates a Pipeline Vertex Input State Create Info Struct and fills it with Attribute and
	 * Binding data.
	 * @param vertexAttributeDescriptions
	 * @param vertexBindingDescriptions
	 * @return Pipeline Vertex Input State Create Info Struct
	 */
	vk::PipelineVertexInputStateCreateInfo createPipelineVertexInputStateCreateInfo(
		std::vector<vk::VertexInputAttributeDescription> &vertexAttributeDescriptions,
		std::vector<vk::VertexInputBindingDescription> &vertexBindingDescriptions) {

		vk::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo(
			{}, vertexBindingDescriptions.size(), vertexBindingDescriptions.data(),
			vertexAttributeDescriptions.size(), vertexAttributeDescriptions.data());
		return pipelineVertexInputStateCreateInfo;
	}

	/**
	 * Creates a Pipeline Input Assembly State Create Info Struct with 'Primitive Restart' disabled.
	 * @param config provides data for primitive topology.
	 * @return Pipeline Input Assembly State Create Info Struct
	 */
	vk::PipelineInputAssemblyStateCreateInfo
	createPipelineInputAssemblyStateCreateInfo(const GraphicsPipelineConfig &config) {
		vk::PipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo(
			{}, primitiveTopologyToVulkanPrimitiveTopology(config.getPrimitiveTopology()), false);

		return pipelineInputAssemblyStateCreateInfo;
	}

	vk::PipelineTessellationStateCreateInfo
	createPipelineTessellationStateCreateInfo(const GraphicsPipelineConfig &config) {
		vk::PipelineTessellationStateCreateInfo pipelineTessellationStateCreateInfo(
			{}, config.getTesselationControlPoints());

		return pipelineTessellationStateCreateInfo;
	}

	/**
	 * Creates a Pipeline Viewport State Create Info Struct with default set viewport and scissor
	 * settings.
	 * @param config provides with and height of the output window
	 * @return Pipeline Viewport State Create Info Struct
	 */
	vk::PipelineViewportStateCreateInfo
	createPipelineViewportStateCreateInfo(const GraphicsPipelineConfig &config) {
		static vk::Viewport viewport;
		static vk::Rect2D scissor;

		viewport = vk::Viewport(0.f, 0.f, static_cast<float>(config.getWidth()),
								static_cast<float>(config.getHeight()), 0.f, 1.f);

		scissor = vk::Rect2D({ 0, 0 }, { config.getWidth(), config.getHeight() });

		vk::PipelineViewportStateCreateInfo pipelineViewportStateCreateInfo({}, 1, &viewport, 1,
																			&scissor);

		return pipelineViewportStateCreateInfo;
	}

	/**
	 * Creates a Pipeline Rasterization State Create Info Struct with default values set to:
	 * Rasterizer Discard: Disabled
	 * Polygon Mode: Fill
	 * Front Face: Counter Clockwise
	 * Depth Bias: Disabled
	 * Line Width: 1.0
	 * Depth Clamping and Culling Mode ist set by the Pipeline Config
	 * @param config sets Depth Clamping and Culling Mode
	 * @return Pipeline Rasterization State Create Info Struct
	 */
	vk::PipelineRasterizationStateCreateInfo createPipelineRasterizationStateCreateInfo(
		const GraphicsPipelineConfig &config,
		const vk::PhysicalDeviceConservativeRasterizationPropertiesEXT
			&conservativeRasterProperties) {
		vk::CullModeFlags cullMode;
		switch (config.getCulling()) {
		case CullMode::None:
			cullMode = vk::CullModeFlagBits::eNone;
			break;
		case CullMode::Front:
			cullMode = vk::CullModeFlagBits::eFront;
			break;
		case CullMode::Back:
			cullMode = vk::CullModeFlagBits::eBack;
			break;
		case CullMode::Both:
			cullMode = vk::CullModeFlagBits::eFrontAndBack;
			break;
		default:
			vkcv_log(LogLevel::ERROR, "Unknown CullMode");
			cullMode = vk::CullModeFlagBits::eNone;
		}

		vk::PipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo(
			{}, config.isDepthClampingEnabled(), false, vk::PolygonMode::eFill, cullMode,
			vk::FrontFace::eCounterClockwise, false, 0.f, 0.f, 0.f, 1.f);

		static vk::PipelineRasterizationConservativeStateCreateInfoEXT conservativeRasterization;

		if (config.isUsingConservativeRasterization()) {
			const float overestimationSize =
				1.0f - conservativeRasterProperties.primitiveOverestimationSize;
			const float maxOverestimationSize =
				conservativeRasterProperties.maxExtraPrimitiveOverestimationSize;

			conservativeRasterization = vk::PipelineRasterizationConservativeStateCreateInfoEXT(
				{}, vk::ConservativeRasterizationModeEXT::eOverestimate,
				std::min(std::max(overestimationSize, 0.f), maxOverestimationSize));

			pipelineRasterizationStateCreateInfo.pNext = &conservativeRasterization;
		}

		return pipelineRasterizationStateCreateInfo;
	}

	/**
	 * Creates a Pipeline Multisample State Create Info Struct.
	 * @param config set MSAA Sample Count Flag
	 * @return Pipeline Multisample State Create Info Struct
	 */
	vk::PipelineMultisampleStateCreateInfo
	createPipelineMultisampleStateCreateInfo(const GraphicsPipelineConfig &config,
											 const PassConfig &passConfig) {
		vk::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo(
			{}, msaaToSampleCountFlagBits(passConfig.getMultisampling()), false, 0.f, nullptr,
			config.isWritingAlphaToCoverage(), false);

		return pipelineMultisampleStateCreateInfo;
	}

	/**
	 * Creates a Pipeline Color Blend State Create Info Struct.
	 * Currently only one blend mode is supported! There for, blending is set to additive.
	 * @param config sets blend mode
	 * @return
	 */
	vk::PipelineColorBlendStateCreateInfo
	createPipelineColorBlendStateCreateInfo(const GraphicsPipelineConfig &config) {
		// currently set to additive, if not disabled
		// BlendFactors must be set as soon as additional BlendModes are added
		static vk::PipelineColorBlendAttachmentState colorBlendAttachmentState(
			config.getBlendMode() != BlendMode::None, vk::BlendFactor::eOne, vk::BlendFactor::eOne,
			vk::BlendOp::eAdd, vk::BlendFactor::eOne, vk::BlendFactor::eOne, vk::BlendOp::eAdd,
			vk::ColorComponentFlags(VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
									| VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT));

		vk::PipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo(
			{}, false, vk::LogicOp::eClear,
			1, // TODO: hardcoded to one
			&colorBlendAttachmentState, { 1.f, 1.f, 1.f, 1.f });

		return pipelineColorBlendStateCreateInfo;
	}

	/**
	 * Creates a Pipeline Layout Create Info Struct.
	 * @param config sets Push Constant Size and Descriptor Layouts.
	 * @return Pipeline Layout Create Info Struct
	 */
	vk::PipelineLayoutCreateInfo createPipelineLayoutCreateInfo(
		const GraphicsPipelineConfig &config,
		const std::vector<vk::DescriptorSetLayout> &descriptorSetLayouts) {
		static vk::PushConstantRange pushConstantRange;

		const size_t pushConstantsSize = config.getShaderProgram().getPushConstantsSize();
		pushConstantRange =
			vk::PushConstantRange(vk::ShaderStageFlagBits::eAll, 0, pushConstantsSize);

		vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo({}, (descriptorSetLayouts),
															  (pushConstantRange));

		if (pushConstantsSize == 0) {
			pipelineLayoutCreateInfo.pushConstantRangeCount = 0;
		}

		return pipelineLayoutCreateInfo;
	}

	/**
	 * Creates a Pipeline Depth Stencil State Create Info Struct.
	 * @param config sets if depth test in enabled or not.
	 * @return Pipeline Layout Create Info Struct
	 */
	vk::PipelineDepthStencilStateCreateInfo
	createPipelineDepthStencilStateCreateInfo(const GraphicsPipelineConfig &config) {
		const vk::PipelineDepthStencilStateCreateInfo pipelineDepthStencilCreateInfo(
			vk::PipelineDepthStencilStateCreateFlags(), config.getDepthTest() != DepthTest::None,
			config.isWritingDepth(), depthTestToVkCompareOp(config.getDepthTest()), false, false,
			{}, {}, 0.0f, 1.0f);

		return pipelineDepthStencilCreateInfo;
	}

	/**
	 * Creates a Pipeline Dynamic State Create Info Struct.
	 * @param config sets whenever a dynamic viewport is used or not.
	 * @return Pipeline Dynamic State Create Info Struct
	 */
	vk::PipelineDynamicStateCreateInfo
	createPipelineDynamicStateCreateInfo(const GraphicsPipelineConfig &config) {
		static std::vector<vk::DynamicState> dynamicStates;
		dynamicStates.clear();

		if (config.isViewportDynamic()) {
			dynamicStates.push_back(vk::DynamicState::eViewport);
			dynamicStates.push_back(vk::DynamicState::eScissor);
		}

		vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo(
			{}, static_cast<uint32_t>(dynamicStates.size()), dynamicStates.data());

		return dynamicStateCreateInfo;
	}

	GraphicsPipelineHandle
	GraphicsPipelineManager::createPipeline(const GraphicsPipelineConfig &config,
											const PassManager &passManager,
											const DescriptorSetLayoutManager &descriptorManager) {
		const vk::RenderPass &pass = passManager.getVkPass(config.getPass());

		const auto &program = config.getShaderProgram();

		const bool existsTaskShader = program.existsShader(ShaderStage::TASK);
		const bool existsMeshShader = program.existsShader(ShaderStage::MESH);
		const bool existsVertexShader = program.existsShader(ShaderStage::VERTEX);
		const bool existsFragmentShader = program.existsShader(ShaderStage::FRAGMENT);
		const bool existsGeometryShader = program.existsShader(ShaderStage::GEOMETRY);
		const bool existsTessellationControlShader =
			program.existsShader(ShaderStage::TESS_CONTROL);
		const bool existsTessellationEvaluationShader =
			program.existsShader(ShaderStage::TESS_EVAL);

		const bool validGeometryStages =
			((existsVertexShader
			  && (existsTessellationControlShader == existsTessellationEvaluationShader))
			 || (existsTaskShader && existsMeshShader));

		if (!validGeometryStages) {
			vkcv_log(LogLevel::ERROR, "Requires vertex or task and mesh shader");
			return {};
		}

		if (!existsFragmentShader) {
			vkcv_log(LogLevel::ERROR, "Requires fragment shader code");
			return {};
		}

		std::vector<vk::PipelineShaderStageCreateInfo> shaderStages;
		auto destroyShaderModules = [&shaderStages, this] {
			for (auto stage : shaderStages) {
				getCore().getContext().getDevice().destroyShaderModule(stage.module);
			}
			shaderStages.clear();
		};

		if (existsVertexShader) {
			vk::PipelineShaderStageCreateInfo createInfo;
			const bool success = createPipelineShaderStageCreateInfo(
				program, ShaderStage::VERTEX, getCore().getContext().getDevice(), &createInfo);

			if (success) {
				shaderStages.push_back(createInfo);
			} else {
				destroyShaderModules();
				return {};
			}
		}

		if (existsTaskShader) {
			vk::PipelineShaderStageCreateInfo createInfo;
			const bool success = createPipelineShaderStageCreateInfo(
				program, ShaderStage::TASK, getCore().getContext().getDevice(), &createInfo);

			if (success) {
				shaderStages.push_back(createInfo);
			} else {
				destroyShaderModules();
				return {};
			}
		}

		if (existsMeshShader) {
			vk::PipelineShaderStageCreateInfo createInfo;
			const bool success = createPipelineShaderStageCreateInfo(
				program, ShaderStage::MESH, getCore().getContext().getDevice(), &createInfo);

			if (success) {
				shaderStages.push_back(createInfo);
			} else {
				destroyShaderModules();
				return {};
			}
		}

		{
			vk::PipelineShaderStageCreateInfo createInfo;
			const bool success = createPipelineShaderStageCreateInfo(
				program, ShaderStage::FRAGMENT, getCore().getContext().getDevice(), &createInfo);

			if (success) {
				shaderStages.push_back(createInfo);
			} else {
				destroyShaderModules();
				return {};
			}
		}

		if (existsGeometryShader) {
			vk::PipelineShaderStageCreateInfo createInfo;
			const bool success = createPipelineShaderStageCreateInfo(
				program, ShaderStage::GEOMETRY, getCore().getContext().getDevice(), &createInfo);

			if (success) {
				shaderStages.push_back(createInfo);
			} else {
				destroyShaderModules();
				return {};
			}
		}

		if (existsTessellationControlShader) {
			vk::PipelineShaderStageCreateInfo createInfo;
			const bool success = createPipelineShaderStageCreateInfo(
				program, ShaderStage::TESS_CONTROL, getCore().getContext().getDevice(),
				&createInfo);

			if (success) {
				shaderStages.push_back(createInfo);
			} else {
				destroyShaderModules();
				return {};
			}
		}

		if (existsTessellationEvaluationShader) {
			vk::PipelineShaderStageCreateInfo createInfo;
			const bool success = createPipelineShaderStageCreateInfo(
				program, ShaderStage::TESS_EVAL, getCore().getContext().getDevice(), &createInfo);

			if (success) {
				shaderStages.push_back(createInfo);
			} else {
				destroyShaderModules();
				return {};
			}
		}

		const PassConfig &passConfig = passManager.getPassConfig(config.getPass());

		// vertex input state
		// Fill up VertexInputBindingDescription and VertexInputAttributeDescription Containers
		std::vector<vk::VertexInputAttributeDescription> vertexAttributeDescriptions;
		std::vector<vk::VertexInputBindingDescription> vertexBindingDescriptions;
		fillVertexInputDescription(vertexAttributeDescriptions, vertexBindingDescriptions,
								   existsVertexShader, config);

		// Handover Containers to PipelineVertexInputStateCreateIngo Struct
		vk::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo =
			createPipelineVertexInputStateCreateInfo(vertexAttributeDescriptions,
													 vertexBindingDescriptions);

		// input assembly state
		vk::PipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo =
			createPipelineInputAssemblyStateCreateInfo(config);

		// tesselation state
		vk::PipelineTessellationStateCreateInfo pipelineTessellationStateCreateInfo =
			createPipelineTessellationStateCreateInfo(config);

		// viewport state
		vk::PipelineViewportStateCreateInfo pipelineViewportStateCreateInfo =
			createPipelineViewportStateCreateInfo(config);

		// rasterization state
		vk::PhysicalDeviceConservativeRasterizationPropertiesEXT conservativeRasterProperties;
		vk::PhysicalDeviceProperties deviceProperties;
		vk::PhysicalDeviceProperties2 deviceProperties2(deviceProperties);
		deviceProperties2.pNext = &conservativeRasterProperties;
		getCore().getContext().getPhysicalDevice().getProperties2(&deviceProperties2);
		vk::PipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo =
			createPipelineRasterizationStateCreateInfo(config, conservativeRasterProperties);

		// multisample state
		vk::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo =
			createPipelineMultisampleStateCreateInfo(config, passConfig);

		// color blend state
		vk::PipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo =
			createPipelineColorBlendStateCreateInfo(config);

		// Dynamic State
		vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo =
			createPipelineDynamicStateCreateInfo(config);

		std::vector<vk::DescriptorSetLayout> descriptorSetLayouts;
		descriptorSetLayouts.reserve(config.getDescriptorSetLayouts().size());
		for (const auto &handle : config.getDescriptorSetLayouts()) {
			descriptorSetLayouts.push_back(
				descriptorManager.getDescriptorSetLayout(handle).vulkanHandle);
		}

		// pipeline layout
		vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo =
			createPipelineLayoutCreateInfo(config, descriptorSetLayouts);

		vk::PipelineLayout vkPipelineLayout {};
		if (getCore().getContext().getDevice().createPipelineLayout(&pipelineLayoutCreateInfo,
																	nullptr, &vkPipelineLayout)
			!= vk::Result::eSuccess) {
			destroyShaderModules();
			return {};
		}

		// Depth Stencil
		const vk::PipelineDepthStencilStateCreateInfo depthStencilCreateInfo =
			createPipelineDepthStencilStateCreateInfo(config);

		const vk::PipelineDepthStencilStateCreateInfo* p_depthStencilCreateInfo = nullptr;

		for (const auto &attachment : passConfig.getAttachments()) {
			if ((isDepthFormat(attachment.getFormat()))
				|| (isStencilFormat(attachment.getFormat()))) {
				p_depthStencilCreateInfo = &depthStencilCreateInfo;
				break;
			}
		}

		// Get all setting structs together and create the Pipeline
		const vk::GraphicsPipelineCreateInfo graphicsPipelineCreateInfo(
			{}, static_cast<uint32_t>(shaderStages.size()), shaderStages.data(),
			&pipelineVertexInputStateCreateInfo, &pipelineInputAssemblyStateCreateInfo,
			&pipelineTessellationStateCreateInfo, &pipelineViewportStateCreateInfo,
			&pipelineRasterizationStateCreateInfo, &pipelineMultisampleStateCreateInfo,
			p_depthStencilCreateInfo, &pipelineColorBlendStateCreateInfo, &dynamicStateCreateInfo,
			vkPipelineLayout, pass, 0, {}, 0);

		vk::Pipeline vkPipeline {};
		if (getCore().getContext().getDevice().createGraphicsPipelines(
				nullptr, 1, &graphicsPipelineCreateInfo, nullptr, &vkPipeline)
			!= vk::Result::eSuccess) {
			// Catch runtime error if the creation of the pipeline fails.
			// Destroy everything to keep the memory clean.
			destroyShaderModules();
			return {};
		}

		// Clean Up
		destroyShaderModules();

		// Hand over Handler to main Application
		return add({ vkPipeline, vkPipelineLayout, config });
	}

	vk::Pipeline
	GraphicsPipelineManager::getVkPipeline(const GraphicsPipelineHandle &handle) const {
		auto &pipeline = (*this) [handle];
		return pipeline.m_handle;
	}

	vk::PipelineLayout
	GraphicsPipelineManager::getVkPipelineLayout(const GraphicsPipelineHandle &handle) const {
		auto &pipeline = (*this) [handle];
		return pipeline.m_layout;
	}

	const GraphicsPipelineConfig &
	GraphicsPipelineManager::getPipelineConfig(const GraphicsPipelineHandle &handle) const {
		auto &pipeline = (*this) [handle];
		return pipeline.m_config;
	}

} // namespace vkcv