Created pipeline builder and wrapper

basalt::Pipeline is mainly just a wrapper around VkPipeline however there is no cast operator provided (yet)
This commit is contained in:
2025-07-07 22:56:00 +10:00
parent 4d797e1170
commit b687d74e32
2 changed files with 296 additions and 0 deletions

View File

@@ -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<VkDynamicState>& states);
PipelineBuilder& set_render_pass(VkRenderPass render_pass);
PipelineBuilder& add_colour_attachment(VkPipelineColorBlendAttachmentState attachment);
Pipeline build();
basalt::darray<VkShaderModule> shader_modules;
basalt::darray<VkPipelineShaderStageCreateInfo> 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<VkPipelineColorBlendAttachmentState> colour_attachments;
basalt::darray<VkDynamicState> dynamic_states;
VkViewport viewport;
VkRect2D scissor;
basalt::Device& device;
VkRenderPass render_pass = VK_NULL_HANDLE;
VkPipeline previous_pipeline = VK_NULL_HANDLE;
protected:
/// <summary>
/// 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
/// </summary>
/// <param name="fpath">Path to the file to read</param>
/// <param name="outptr">A reference to a pointer that may be updated</param>
/// <param name="size">Size of an existing allocation associated with outptr</param>
/// <returns>Number of bytes read from the file</returns>
static size_t read_file(const char* fpath, char*& outptr, size_t& outptr_size);
};
}

View File

@@ -0,0 +1,226 @@
#include "vulkan/basalt_pipeline.h"
#include <fstream>
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<size_t>(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<VkDynamicState*>(states), num_states);
return *this;
}
basalt::PipelineBuilder& basalt::PipelineBuilder::add_dynamic_states(const basalt::darray<VkDynamicState>& 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;
}