/** * @file pipeline_builder.c * @author Piotr Krygier (everyonecancode@gmail.com) * @brief * @version 0.1 * @date 2024-03-01 * * @copyright Copyright (c) 2024 * */ #include "pipeline_builder.h" #include #include #include "src/graphics_context.h" #include "utilities/commons.h" #include "utilities/errors_common.h" #include "utilities/file_utils.h" #include "utilities/linked_list.h" #include "utilities/localization.h" #include "vulkan/vulkan_core.h" #include "vulkan_errors.h" #include "vulkan_image.h" #define MAX_BINDLESS_RESOURCES 1000 RSE_LINKED_LIST_DEFINE(vertex_binding_list_t, VkVertexInputBindingDescription); RSE_LINKED_LIST_DEFINE(shader_attribute_list_t, VkVertexInputAttributeDescription); RSE_LINKED_LIST_DEFINE(dynamic_state_list_t, VkDynamicState); RSE_LINKED_LIST_DEFINE(pipeline_infos_list_t, VkGraphicsPipelineCreateInfo); RSE_STACK_DEFINE(required_dynamic_states_t, VkDynamicState, 16U); struct pipeline_t { VkPipeline pipeline; uint8_t id; /* Shader stages */ size_t shader_stages_count; VkPipelineShaderStageCreateInfo shader_stages[MAX_SHADER_STAGES]; /* Input assembly */ VkPipelineInputAssemblyStateCreateInfo input_assembly; /* Viewport state */ VkPipelineViewportStateCreateInfo viewport_state; /* Rasterizer */ VkPipelineRasterizationStateCreateInfo rasterizer; /* Multisampling */ VkPipelineMultisampleStateCreateInfo multisampling; /* Color blending */ VkPipelineColorBlendAttachmentState color_blend_attachment; VkPipelineColorBlendStateCreateInfo color_blending; /* Depth stencil */ VkPipelineDepthStencilStateCreateInfo depth_stencil; struct vertex_binding_list_t* vertex_bindings; struct shader_attribute_list_t* shader_attributes; struct dynamic_state_list_t* dynamic_states; struct required_dynamic_states_t required_dynamic_states; }; /** * @brief Creates shader module * * @param binary_data Shader compiled code * @return rse_err_t RSE_ERROR_NO_ERROR on success */ static rse_err_t create_shader_module(struct graphics_context_t* context, struct pipeline_t* pipeline, const char* binary_data, size_t data_size) { VkShaderModuleCreateInfo create_info = {}; create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; create_info.pNext = NULL; create_info.flags = 0U; create_info.codeSize = data_size; create_info.pCode = (const uint32_t*)(binary_data); RSE_LINKED_LIST_PUSH_FRONT(context->shader_modules, (VkShaderModule){0}); if (VK_SUCCESS != vkCreateShaderModule(context->device, &create_info, NULL, &RSE_LINKED_LIST_FRONT(context->shader_modules))) { LOGF(_("Failed to create a shader\n")); return VULKAN_ERROR_SHADER_CREATION_FAILED; } return RSE_ERROR_NO_ERROR; } static rse_err_t pipeline_add_input_assembly(struct pipeline_t* pipeline) { /* For now input assebly is hardcoded */ pipeline->input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; pipeline->input_assembly.pNext = NULL; pipeline->input_assembly.flags = 0U; pipeline->input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; pipeline->input_assembly.primitiveRestartEnable = VK_FALSE; return RSE_ERROR_NO_ERROR; } static rse_err_t pipeline_add_viewport_state(struct pipeline_t* pipeline) { /* For now viewport is hardcoded */ pipeline->viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; pipeline->viewport_state.pNext = NULL; pipeline->viewport_state.flags = 0U; pipeline->viewport_state.viewportCount = 1U; pipeline->viewport_state.pViewports = NULL; /* This is set during rendering */ pipeline->viewport_state.scissorCount = 1U; pipeline->viewport_state.pScissors = NULL; /* This is set during rendering */ if (pipeline->viewport_state.pViewports == NULL || pipeline->viewport_state.viewportCount == 0U) { RSE_STACK_PUSH(pipeline->required_dynamic_states, VK_DYNAMIC_STATE_VIEWPORT); } if (pipeline->viewport_state.pScissors == NULL || pipeline->viewport_state.scissorCount == 0U) { RSE_STACK_PUSH(pipeline->required_dynamic_states, VK_DYNAMIC_STATE_SCISSOR); } return RSE_ERROR_NO_ERROR; } static rse_err_t pipeline_add_rasterizer(struct pipeline_t* pipeline) { /* For now rasterizer is hardcoded */ pipeline->rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; pipeline->rasterizer.pNext = NULL; pipeline->rasterizer.flags = 0U; pipeline->rasterizer.depthClampEnable = VK_FALSE; /* VK_TRUE to play with shadow maps */ pipeline->rasterizer.rasterizerDiscardEnable = VK_FALSE; /* Disable/Enable output to framebuffer*/ pipeline->rasterizer.polygonMode = VK_POLYGON_MODE_FILL; pipeline->rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; pipeline->rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; pipeline->rasterizer.depthBiasEnable = VK_FALSE; pipeline->rasterizer.depthBiasConstantFactor = 0.0f; pipeline->rasterizer.depthBiasClamp = 0.0f; pipeline->rasterizer.depthBiasSlopeFactor = 0.0f; pipeline->rasterizer.lineWidth = 1.0f; return RSE_ERROR_NO_ERROR; } static rse_err_t pipeline_add_multisampling(struct pipeline_t* pipeline) { /* For now multisampling is hardcoded */ pipeline->multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; pipeline->multisampling.pNext = NULL; pipeline->multisampling.flags = 0U; pipeline->multisampling.rasterizationSamples = VK_SAMPLE_COUNT_8_BIT; pipeline->multisampling.sampleShadingEnable = VK_FALSE; pipeline->multisampling.minSampleShading = 1.0f; pipeline->multisampling.pSampleMask = NULL; pipeline->multisampling.alphaToCoverageEnable = VK_FALSE; pipeline->multisampling.alphaToOneEnable = VK_FALSE; return RSE_ERROR_NO_ERROR; } static rse_err_t pipeline_add_color_blending(struct pipeline_t* pipeline) { /* For now color blending is hardcoded */ pipeline->color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; pipeline->color_blend_attachment.blendEnable = VK_TRUE; pipeline->color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; pipeline->color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; pipeline->color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD; pipeline->color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; pipeline->color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; pipeline->color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD; pipeline->color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; pipeline->color_blending.pNext = NULL; pipeline->color_blending.flags = 0U; pipeline->color_blending.logicOpEnable = VK_FALSE; pipeline->color_blending.logicOp = VK_LOGIC_OP_COPY; pipeline->color_blending.attachmentCount = 1U; pipeline->color_blending.pAttachments = &pipeline->color_blend_attachment; pipeline->color_blending.blendConstants[0] = 0.0f; pipeline->color_blending.blendConstants[1] = 0.0f; pipeline->color_blending.blendConstants[2] = 0.0f; pipeline->color_blending.blendConstants[3] = 0.0f; return RSE_ERROR_NO_ERROR; } static rse_err_t pipeline_add_depth_stencil(struct pipeline_t* pipeline) { /* For now depth stencil is hardcoded */ pipeline->depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; pipeline->depth_stencil.pNext = NULL; pipeline->depth_stencil.flags = 0U; pipeline->depth_stencil.depthTestEnable = VK_TRUE; pipeline->depth_stencil.depthWriteEnable = VK_TRUE; pipeline->depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS; pipeline->depth_stencil.depthBoundsTestEnable = VK_FALSE; pipeline->depth_stencil.stencilTestEnable = VK_FALSE; pipeline->depth_stencil.minDepthBounds = 0.0f; pipeline->depth_stencil.maxDepthBounds = 1.0f; return RSE_ERROR_NO_ERROR; } /** * @brief Create a Pipeline Layout * * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: * VULKAN_ERROR_PIPELINE_LAYOUT_CREATION_FAILED */ static rse_err_t create_pipeline_layout(struct graphics_context_t* context) { VkPipelineLayoutCreateInfo create_info = {}; create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; create_info.pNext = NULL; create_info.flags = 0U; create_info.setLayoutCount = context->pipelines_data.descriptor_set_layouts_count; create_info.pSetLayouts = context->pipelines_data.descriptor_set_layouts; create_info.pushConstantRangeCount = 0U; create_info.pPushConstantRanges = NULL; if (VK_SUCCESS != vkCreatePipelineLayout(context->device, &create_info, NULL, &context->pipelines_data.pipeline_layout)) { LOGF(_("Failed to create pipeline layout\n")); return VULKAN_ERROR_PIPELINE_LAYOUT_CREATION_FAILED; } return RSE_ERROR_NO_ERROR; } /** * @brief Clean up pipeline builder. This function should be called after all * pipelines are built and no more pipelines are needed. It frees all resources * used by pipeline builder. * * @param[in/out] pipelines List of pipelines to clean up */ static void pipeline_builder_cleanup(struct pipeline_infos_list_t* pipelines) { size_t pipeline_count = RSE_LINKED_LIST_SIZE(pipelines); VkVertexInputBindingDescription* vertex_bindings = NULL; VkVertexInputAttributeDescription* shader_attributes = NULL; VkDynamicState* dynamic_states = NULL; for (size_t i = 0; i < pipeline_count; i++) { VkGraphicsPipelineCreateInfo* pipeline_info = &RSE_LINKED_LIST_FRONT(pipelines); if (pipeline_info != NULL) { vertex_bindings = (VkVertexInputBindingDescription*)pipeline_info ->pVertexInputState->pVertexBindingDescriptions; shader_attributes = (VkVertexInputAttributeDescription*)pipeline_info ->pVertexInputState->pVertexAttributeDescriptions; dynamic_states = (VkDynamicState*)pipeline_info->pDynamicState->pDynamicStates; rse_free((VkPipelineVertexInputStateCreateInfo*) pipeline_info->pVertexInputState); rse_free((VkPipelineDynamicStateCreateInfo*) pipeline_info->pDynamicState); rse_free(vertex_bindings); rse_free(shader_attributes); rse_free(dynamic_states); } RSE_LINKED_LIST_POP_FRONT(pipelines); } if (RSE_LINKED_LIST_IS_EMPTY(pipelines) == 0) { LOGF(_("Failed to clean up pipelines\n")); } } rse_err_t pipeline_add_dynamic_state(struct pipeline_t* pipeline, VkDynamicState dynamic_state) { if (dynamic_state >= VK_DYNAMIC_STATE_MAX_ENUM) { LOGF(_("Invalid dynamic state in pipeline\n")); return VULKAN_ERROR_PIPELINE_INVALID_DYNAMIC_STATE; } RSE_LINKED_LIST_PUSH_FRONT(pipeline->dynamic_states, dynamic_state); return RSE_ERROR_NO_ERROR; } rse_err_t pipeline_builder_init(struct pipeline_t** pipeline) { rse_err_t status = RSE_ERROR_NO_ERROR; struct pipeline_t* pipeline_ptr = NULL; if (*pipeline != NULL) { LOGF( _("Pipeline is not NULL. This function will initialize everything " "for you, no need for manual allocation\n")); return VULKAN_ERROR_PIPELINE_ALREADY_INITIALIZED; } rse_malloc(pipeline_ptr, sizeof(struct pipeline_t)); rse_memset(pipeline_ptr, 0, sizeof(struct pipeline_t)); *pipeline = pipeline_ptr; return status; } rse_err_t pipeline_add_shader_stage(struct graphics_context_t* context, struct pipeline_t* pipeline, const char* shader_path, const VkShaderStageFlagBits shader_stage) { size_t i = 0; rse_err_t status = RSE_ERROR_NO_ERROR; char* buffer = NULL; size_t buffer_size = 0U; /* Check if we have enough space for new shader stages */ if (pipeline->shader_stages_count + 1 > MAX_SHADER_STAGES) { LOGF(_("Too many shader stages in pipeline\n")); return VULKAN_ERROR_PIPELINE_TOO_MANY_SHADER_STAGES; } i = pipeline->shader_stages_count++; /* Check if shader module is valid */ if (shader_stage >= VK_SHADER_STAGE_FLAG_BITS_MAX_ENUM) { LOGF(_("Invalid shader stage in pipeline\n")); return VULKAN_ERROR_PIPELINE_INVALID_SHADER_STAGE; } /* Read shader file */ read_file(shader_path, &buffer_size, NULL); rse_malloc(buffer, buffer_size); read_file(shader_path, &buffer_size, buffer); STATUS_CHECK(create_shader_module(context, pipeline, buffer, buffer_size)); free(buffer); pipeline->shader_stages[i].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; pipeline->shader_stages[i].pNext = NULL; pipeline->shader_stages[i].flags = 0U; pipeline->shader_stages[i].stage = shader_stage; pipeline->shader_stages[i].module = RSE_LINKED_LIST_FRONT( context->shader_modules); /* This is automatically updated during shader module creation */ pipeline->shader_stages[i].pName = "main"; pipeline->shader_stages[i].pSpecializationInfo = NULL; return status; } rse_err_t pipeline_add_vertex_input_binding(struct pipeline_t* pipeline, const uint8_t binding, const uint8_t stride, const VkVertexInputRate input_rate) { VkVertexInputBindingDescription binding_pipeline_description = {}; if (input_rate >= VK_VERTEX_INPUT_RATE_MAX_ENUM) { LOGF(_("Invalid vertex input rate\n")); return VULKAN_ERROR_PIPELINE_INVALID_VERTEX_INPUT_RATE; } binding_pipeline_description.binding = binding; binding_pipeline_description.stride = stride; binding_pipeline_description.inputRate = input_rate; RSE_LINKED_LIST_PUSH_FRONT(pipeline->vertex_bindings, binding_pipeline_description); return RSE_ERROR_NO_ERROR; } rse_err_t pipeline_add_vertex_input_attribute(struct pipeline_t* pipeline, const uint8_t binding, const uint8_t location, const VkFormat format, const uint8_t offset) { VkVertexInputAttributeDescription attribute_description = {}; if (binding >= 16U) { LOGF(_("Invalid binding in pipeline attribute description\n")); return VULKAN_ERROR_PIPELINE_INVALID_BINDING; } if (format >= VK_FORMAT_MAX_ENUM) { LOGF(_("Invalid format in pipeline attribute description\n")); return VULKAN_ERROR_PIPELINE_INVALID_FORMAT; } attribute_description.binding = binding; attribute_description.location = location; attribute_description.format = format; attribute_description.offset = offset; RSE_LINKED_LIST_PUSH_FRONT(pipeline->shader_attributes, attribute_description); return RSE_ERROR_NO_ERROR; } rse_err_t pipelines_build(struct graphics_context_t* context, struct pipeline_infos_list_t* pipelines_infos_list) { assert(NULL != context); assert(NULL != pipelines_infos_list); size_t i = 0U; rse_err_t status = RSE_ERROR_NO_ERROR; size_t pipelines_count = RSE_LINKED_LIST_SIZE(pipelines_infos_list); VkGraphicsPipelineCreateInfo* pipeline_infos = NULL; context->pipelines_data.pipelines_count = pipelines_count; STATUS_CHECK(create_pipeline_layout(context)); rse_malloc(pipeline_infos, sizeof(VkGraphicsPipelineCreateInfo) * pipelines_count); RSE_LINKED_LIST_TO_ARRAY(pipelines_infos_list, pipeline_infos); /* FIXME: layout is created to late. Maybe move it somewhere else so we * don't need this loop? */ for (i = 0; i < pipelines_count; ++i) { pipeline_infos[i].layout = context->pipelines_data.pipeline_layout; } if (VK_SUCCESS != vkCreateGraphicsPipelines(context->device, VK_NULL_HANDLE, pipelines_count, pipeline_infos, NULL, context->pipelines_data.pipelines)) { LOGF(_("Failed to create graphics pipelines\n")); status = VULKAN_ERROR_GRAPHICSPIPELINE_CREATION_FAILED; } rse_free(pipeline_infos); pipeline_builder_cleanup(pipelines_infos_list); return status; } rse_err_t add_pipeline(const struct graphics_context_t* context, struct pipeline_t* pipeline, struct pipeline_infos_list_t** pipelines_infos) { /* Check for send and required dynamic states */ rse_err_t status = RSE_ERROR_NO_ERROR; VkGraphicsPipelineCreateInfo pipeline_info = {}; size_t vertex_bindings_count = 0U; VkVertexInputBindingDescription* vertex_bindings = NULL; VkPipelineVertexInputStateCreateInfo* vertex_bindings_ci = NULL; size_t shader_attributes_count = 0U; VkVertexInputAttributeDescription* shader_attributes = NULL; size_t dynamic_states_count = 0U; VkDynamicState* dynamic_states = NULL; VkPipelineDynamicStateCreateInfo* dynamic_states_ci = NULL; uint8_t dynamic_state_found = 0; while (RSE_STACK_IS_EMPTY(pipeline->required_dynamic_states) == 0) { dynamic_state_found = 0; VkDynamicState dynamic_state = RSE_STACK_POP(pipeline->required_dynamic_states); RSE_LINKED_LIST_FOREACH(pipeline->dynamic_states, dynamic_state_ptr) { if (dynamic_state_ptr->data == dynamic_state) { dynamic_state_found = 1; break; } } if (dynamic_state_found == 0) { LOGF(_("Missing dynamic state in pipeline\n")); return VULKAN_ERROR_PIPELINE_MISSING_DYNAMIC_STATE; } } /* Check for shader modules */ if (RSE_LINKED_LIST_IS_EMPTY(context->shader_modules) == 1) { LOGF(_("Missing shader modules in pipeline\n")); return VULKAN_ERROR_PIPELINE_MISSING_SHADER_MODULES; } /* Check for required shader stages */ if (pipeline->shader_stages_count == 0) { LOGF( _("Missing shader stages in pipeline. There must be at least " "one.\n")); return VULKAN_ERROR_PIPELINE_MISSING_SHADER_STAGES; } /* Check for required vertex bindings */ if (pipeline->vertex_bindings == NULL) { LOGF(_("Missing vertex bindings in pipeline\n")); return VULKAN_ERROR_PIPELINE_MISSING_VERTEX_BINDINGS; } /* Check for required shader attributes */ if (pipeline->shader_attributes == NULL) { LOGF(_("Missing shader attributes in pipeline\n")); return VULKAN_ERROR_PIPELINE_MISSING_SHADER_ATTRIBUTES; } /* Convert linked list to array */ vertex_bindings_count = RSE_LINKED_LIST_TO_ARRAY(pipeline->vertex_bindings, vertex_bindings); shader_attributes_count = RSE_LINKED_LIST_TO_ARRAY( pipeline->shader_attributes, shader_attributes); dynamic_states_count = RSE_LINKED_LIST_TO_ARRAY(pipeline->dynamic_states, dynamic_states); /* Build constant pipeline. TODO: Some parts may be customizable */ pipeline_add_input_assembly(pipeline); pipeline_add_viewport_state(pipeline); pipeline_add_rasterizer(pipeline); pipeline_add_multisampling(pipeline); pipeline_add_color_blending(pipeline); pipeline_add_depth_stencil(pipeline); pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipeline_info.pNext = NULL; pipeline_info.flags = 0U; pipeline_info.stageCount = pipeline->shader_stages_count; pipeline_info.pStages = pipeline->shader_stages; rse_malloc(vertex_bindings_ci, sizeof(VkPipelineVertexInputStateCreateInfo)); vertex_bindings_ci->sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertex_bindings_ci->pNext = NULL; vertex_bindings_ci->flags = 0U; vertex_bindings_ci->vertexBindingDescriptionCount = vertex_bindings_count; vertex_bindings_ci->pVertexBindingDescriptions = vertex_bindings; vertex_bindings_ci->vertexAttributeDescriptionCount = shader_attributes_count; vertex_bindings_ci->pVertexAttributeDescriptions = shader_attributes; pipeline_info.pVertexInputState = vertex_bindings_ci; pipeline_info.pInputAssemblyState = &pipeline->input_assembly; pipeline_info.pTessellationState = NULL; pipeline_info.pViewportState = &pipeline->viewport_state; pipeline_info.pRasterizationState = &pipeline->rasterizer; pipeline_info.pMultisampleState = &pipeline->multisampling; pipeline_info.pDepthStencilState = &pipeline->depth_stencil; pipeline_info.pColorBlendState = &pipeline->color_blending; rse_malloc(dynamic_states_ci, sizeof(VkPipelineDynamicStateCreateInfo)); dynamic_states_ci->sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamic_states_ci->pNext = NULL; dynamic_states_ci->flags = 0U; dynamic_states_ci->dynamicStateCount = dynamic_states_count; dynamic_states_ci->pDynamicStates = dynamic_states; pipeline_info.pDynamicState = dynamic_states_ci; pipeline_info.layout = context->pipelines_data.pipeline_layout; pipeline_info.renderPass = context->render_pass; pipeline_info.subpass = 0; pipeline_info.basePipelineHandle = VK_NULL_HANDLE; // TODO: Add possiblity do derive from another pipeline pipeline_info.basePipelineIndex = -1; RSE_LINKED_LIST_PUSH_BACK(*pipelines_infos, pipeline_info); return status; } void destroy_pipelines(struct graphics_context_t* context) { size_t i = 0; size_t shader_modules_count = RSE_LINKED_LIST_SIZE(context->shader_modules); for (i = 0; i < context->pipelines_data.pipelines_count; i++) { if (context->pipelines_data.pipelines[i] != VK_NULL_HANDLE) { vkDestroyPipeline(context->device, context->pipelines_data.pipelines[i], NULL); } } for (i = 0; i < context->pipelines_data.descriptor_set_layouts_count; i++) { if (context->pipelines_data.descriptor_set_layouts[i] != VK_NULL_HANDLE) { vkDestroyDescriptorSetLayout( context->device, context->pipelines_data.descriptor_set_layouts[i], NULL); } } for (i = 0; i < shader_modules_count; i++) { if (RSE_LINKED_LIST_FRONT(context->shader_modules) != VK_NULL_HANDLE) { vkDestroyShaderModule( context->device, RSE_LINKED_LIST_FRONT(context->shader_modules), NULL); } RSE_LINKED_LIST_POP_FRONT(context->shader_modules); } }