diff --git a/include/vulkan/basalt_pipeline.h b/include/vulkan/basalt_pipeline.h new file mode 100644 index 0000000..0fd6be6 --- /dev/null +++ b/include/vulkan/basalt_pipeline.h @@ -0,0 +1,70 @@ +#pragma once +#include "vulkan/basalt_render_pass.h" + +namespace basalt +{ + class Pipeline + { + public: + Pipeline(const Pipeline& src) = delete; + Pipeline& operator =(const Pipeline& src) = delete; + Pipeline(Pipeline&& other) noexcept; + Pipeline& operator =(Pipeline&& other) noexcept; + + Pipeline(basalt::Device& device, VkPipeline pipeline, VkPipelineLayout layout); + ~Pipeline(); + + void swap(Pipeline& other) noexcept; + + VkPipeline vk = VK_NULL_HANDLE; + VkPipelineLayout layout = VK_NULL_HANDLE; + basalt::Device* device = nullptr; + bool should_free = true; + }; + + class PipelineBuilder + { + public: + PipelineBuilder(basalt::Device& device, VkExtent2D extent, VkPipeline previous_pipeline=VK_NULL_HANDLE); + ~PipelineBuilder(); + + PipelineBuilder& add_shader(const char* fpath, VkShaderStageFlagBits stage, const char* entry="main"); + PipelineBuilder& add_dynamic_state(const VkDynamicState state); + PipelineBuilder& add_dynamic_states(const VkDynamicState* states, const u32 num_states); + PipelineBuilder& add_dynamic_states(const basalt::darray& states); + PipelineBuilder& set_render_pass(VkRenderPass render_pass); + PipelineBuilder& add_colour_attachment(VkPipelineColorBlendAttachmentState attachment); + + Pipeline build(); + + basalt::darray shader_modules; + basalt::darray shader_stages; + + VkPipelineRasterizationStateCreateInfo raster_ci; + VkPipelineColorBlendStateCreateInfo colour_blend_ci; + VkPipelineLayoutCreateInfo layout_ci; + VkPipelineVertexInputStateCreateInfo vertex_ci; + VkPipelineMultisampleStateCreateInfo multisample_ci; + VkPipelineViewportStateCreateInfo viewport_ci; + VkPipelineInputAssemblyStateCreateInfo assembly_ci; + VkPipelineDynamicStateCreateInfo dynamic_state_ci; + basalt::darray colour_attachments; + basalt::darray dynamic_states; + VkViewport viewport; + VkRect2D scissor; + basalt::Device& device; + VkRenderPass render_pass = VK_NULL_HANDLE; + VkPipeline previous_pipeline = VK_NULL_HANDLE; + protected: + /// + /// Will read all of the contents of a file into outptr + /// Should outptr not be big enough or nullptr, it will be allocated + /// If it is not big enough it will be reallocated + /// + /// Path to the file to read + /// A reference to a pointer that may be updated + /// Size of an existing allocation associated with outptr + /// Number of bytes read from the file + static size_t read_file(const char* fpath, char*& outptr, size_t& outptr_size); + }; +} \ No newline at end of file diff --git a/src/vulkan/basalt_pipeline.cpp b/src/vulkan/basalt_pipeline.cpp new file mode 100644 index 0000000..ea41be0 --- /dev/null +++ b/src/vulkan/basalt_pipeline.cpp @@ -0,0 +1,226 @@ +#include "vulkan/basalt_pipeline.h" +#include + +basalt::PipelineBuilder::PipelineBuilder(basalt::Device& device, VkExtent2D extent, VkPipeline previous_pipeline) : + shader_stages(MEMORY_TAG_CLASS_DYNARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY), + shader_modules(MEMORY_TAG_CLASS_DYNARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY), + colour_attachments(MEMORY_TAG_CLASS_DYNARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY), + dynamic_states(MEMORY_TAG_CLASS_DYNARRAY | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY), + device(device), previous_pipeline(previous_pipeline) +{ + dynamic_state_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_state_ci.pNext = VK_NULL_HANDLE; + dynamic_state_ci.flags = 0; + + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)extent.width; + viewport.width = (float)extent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + scissor.offset = { 0,0 }; + scissor.extent = extent; + + viewport_ci.pNext = VK_NULL_HANDLE; + viewport_ci.flags = 0; + viewport_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + + vertex_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_ci.pNext = VK_NULL_HANDLE; + vertex_ci.flags = 0; + vertex_ci.vertexBindingDescriptionCount = 0; + vertex_ci.pVertexBindingDescriptions = VK_NULL_HANDLE; + vertex_ci.vertexAttributeDescriptionCount = 0; + vertex_ci.pVertexAttributeDescriptions = VK_NULL_HANDLE; + + assembly_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + assembly_ci.pNext = VK_NULL_HANDLE; + assembly_ci.flags = 0; + assembly_ci.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + assembly_ci.primitiveRestartEnable = VK_FALSE; + + raster_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + raster_ci.pNext = VK_NULL_HANDLE; + raster_ci.flags = 0; + raster_ci.depthClampEnable = VK_FALSE; + raster_ci.rasterizerDiscardEnable = VK_FALSE; + raster_ci.polygonMode = VK_POLYGON_MODE_FILL; + raster_ci.lineWidth = 1.0f; + raster_ci.cullMode = VK_CULL_MODE_BACK_BIT; // TODO: Change this + raster_ci.frontFace = VK_FRONT_FACE_CLOCKWISE; + raster_ci.depthBiasEnable = VK_FALSE; + raster_ci.depthBiasConstantFactor = 0.0f; + raster_ci.depthBiasClamp = 0.0f; + raster_ci.depthBiasSlopeFactor = 0.0f; + + multisample_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisample_ci.pNext = VK_NULL_HANDLE; + multisample_ci.flags = 0; + multisample_ci.sampleShadingEnable = VK_FALSE; + multisample_ci.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisample_ci.minSampleShading = 1.0f; + multisample_ci.pSampleMask = VK_NULL_HANDLE; + multisample_ci.alphaToCoverageEnable = VK_FALSE; + multisample_ci.alphaToOneEnable = VK_FALSE; + + colour_blend_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colour_blend_ci.pNext = VK_NULL_HANDLE; + colour_blend_ci.flags = 0; + colour_blend_ci.logicOpEnable = VK_FALSE; + colour_blend_ci.logicOp = VK_LOGIC_OP_COPY; + colour_blend_ci.blendConstants[0] = 0.0f; + colour_blend_ci.blendConstants[1] = 0.0f; + colour_blend_ci.blendConstants[2] = 0.0f; + colour_blend_ci.blendConstants[3] = 0.0f; + + layout_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + layout_ci.pNext = VK_NULL_HANDLE; + layout_ci.flags = 0; + layout_ci.setLayoutCount = 0; + layout_ci.pSetLayouts = nullptr; + layout_ci.pushConstantRangeCount = 0; + layout_ci.pPushConstantRanges = nullptr; +} + +basalt::PipelineBuilder::~PipelineBuilder() +{ + for (u32 i = 0; i < shader_modules.m_nelements; ++i) + vkDestroyShaderModule(device, shader_modules[i], device.ctx->vk_alloc); +} + +basalt::PipelineBuilder& basalt::PipelineBuilder::add_shader(const char* fpath, VkShaderStageFlagBits stage, const char* entry) +{ + char* shader_src = nullptr; + size_t shader_src_size = 0; + read_file(fpath, shader_src, shader_src_size); + this->shader_stages.push_back(VkPipelineShaderStageCreateInfo{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = VK_NULL_HANDLE, + .flags = 0, + .stage = stage, + .module = (VkShaderModule)this->shader_modules.m_nelements, + .pName = entry, + .pSpecializationInfo = VK_NULL_HANDLE + }); + this->shader_modules.expand(this->shader_modules.m_nelements*2 + 1); + VkShaderModuleCreateInfo ci = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; + ci.flags = 0; + ci.codeSize = shader_src_size; + ci.pCode = (uint32_t*)shader_src; + ci.pNext = VK_NULL_HANDLE; + + VkResult err = VK_SUCCESS; + VK_ASSERT(vkCreateShaderModule(device, &ci, device.ctx->vk_alloc, this->shader_modules.end()), "Failed to create shader\n\tAt %s:%d\n\tError %s\n\tFor shader %s\n", fpath); + this->shader_modules.m_nelements++; + basalt::mem::dealloc(shader_src, shader_src_size, MEMORY_TAG_CLASS_STRING | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY); + + return *this; +} + +size_t basalt::PipelineBuilder::read_file(const char* fpath, char*& outptr, size_t& outptr_size) +{ + std::ifstream f(fpath, std::ios::ate | std::ios::binary); + if (!f.is_open()) + throw std::runtime_error("Failed to open file " + std::string(fpath)); + size_t fsz = static_cast(f.tellg()); + if (outptr == nullptr || outptr_size < fsz) + { + if (outptr != nullptr) + free(outptr); + outptr = (char*)basalt::mem::alloc(fsz*sizeof(char), MEMORY_TAG_CLASS_STRING | MEMORY_TAG_ZONE_ENGINE | MEMORY_TAG_ALIGN_ANY); + outptr_size = fsz; + } + f.seekg(0); + f.read(outptr, fsz); + f.close(); + return fsz; +} + +basalt::Pipeline::Pipeline(basalt::Device& device, VkPipeline pipeline, VkPipelineLayout layout) : + device(&device), vk(pipeline), layout(layout) +{} + +basalt::Pipeline::~Pipeline() +{ + if (this->should_free) + { + vkDestroyPipeline(*device, vk, device->ctx->vk_alloc); + vkDestroyPipelineLayout(*device, layout, device->ctx->vk_alloc); + } +} + +basalt::Pipeline basalt::PipelineBuilder::build() +{ + VkPipelineLayout layout = VK_NULL_HANDLE; + // Create the pipeline layout + VkResult err = VK_SUCCESS; + VK_ASSERT(vkCreatePipelineLayout(device, &layout_ci, device.ctx->vk_alloc, &layout), "Failed to create pipeline layout\n\tAt %s:%d\n\tError %s\n"); + + for (u32 i = 0; i < shader_stages.m_nelements; ++i) + shader_stages[i].module = shader_modules.m_pdata[(size_t)shader_stages[i].module]; + + dynamic_state_ci.dynamicStateCount = this->dynamic_states.m_nelements; + dynamic_state_ci.pDynamicStates = this->dynamic_states.m_pdata; + + viewport_ci.scissorCount = 1; + viewport_ci.pScissors = &scissor; + viewport_ci.viewportCount = 1; + viewport_ci.pViewports = &viewport; + + colour_blend_ci.attachmentCount = this->colour_attachments.m_nelements; + colour_blend_ci.pAttachments = this->colour_attachments.m_pdata; + + VkGraphicsPipelineCreateInfo ci = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; + ci.flags = VK_PIPELINE_CREATE_DERIVATIVE_BIT * (this->previous_pipeline != VK_NULL_HANDLE); + ci.basePipelineHandle = this->previous_pipeline; + ci.basePipelineIndex = -1; + ci.stageCount = this->shader_stages.m_nelements; + ci.pStages = this->shader_stages.m_pdata; + ci.pVertexInputState = &vertex_ci; + ci.pInputAssemblyState = &assembly_ci; + ci.pViewportState = &viewport_ci; + ci.pRasterizationState = &raster_ci; + ci.pMultisampleState = &multisample_ci; + ci.pDepthStencilState = VK_NULL_HANDLE; + ci.pColorBlendState = &colour_blend_ci; + ci.pDynamicState = &dynamic_state_ci; + ci.layout = layout; + ci.renderPass = render_pass; + ci.subpass = 0; + + VkPipeline pipeline = VK_NULL_HANDLE; + VK_ASSERT(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &ci, device.ctx->vk_alloc, &pipeline), "Failed to create pipeline\n\tAt %s:%d\n\tError %s\n"); + + return basalt::Pipeline(device, pipeline, layout); +} + +basalt::PipelineBuilder& basalt::PipelineBuilder::add_dynamic_state(const VkDynamicState state) +{ + this->dynamic_states.push_back(state); + return *this; +} + +basalt::PipelineBuilder& basalt::PipelineBuilder::add_dynamic_states(const VkDynamicState* states, const u32 num_states) +{ + this->dynamic_states.push_back(const_cast(states), num_states); + return *this; +} + +basalt::PipelineBuilder& basalt::PipelineBuilder::add_dynamic_states(const basalt::darray& states) +{ + this->dynamic_states.push_back(states); + return *this; +} + +basalt::PipelineBuilder& basalt::PipelineBuilder::set_render_pass(VkRenderPass render_pass) +{ + this->render_pass = render_pass; + return *this; +} + +basalt::PipelineBuilder& basalt::PipelineBuilder::add_colour_attachment(VkPipelineColorBlendAttachmentState attachment) +{ + this->colour_attachments.push_back(attachment); + return *this; +}