pipeline.cpp

cross platform rendering playground

src/backend/vulkan/pipeline.cpp

13.3 KB
#include <volk.h>

#include <cstdio>
#include <vector>

#include "backend/vulkan/debug.h"
#include "backend/vulkan/utils.h"
#include "backend/vulkan/vk_api.h"
#include "backend/vulkan/vk_conversion.h"
#include "shaders/renderer_types.h"

namespace rhi {
using namespace vk;

RenderPipelineDesc &
RenderPipelineDesc::set_shaders(ShaderCompiler &sc, const char *path, const char *vs, const char *fs) {
    assert(device != nullptr && "RenderPipelineDesc::device must be assigned before set_shaders");
    shader_modules.clear();

    shader_modules.push_back(load_shader(sc, *device, path, vs, ShaderStage::VERTEX));
    shader_modules.push_back(load_shader(sc, *device, path, fs, ShaderStage::FRAGMENT));

    shader_path = path;
    vs_entry = vs;
    fs_entry = fs;
    return *this;
}

ComputePipelineDesc &ComputePipelineDesc::set_shader(ShaderCompiler &sc, const char *path, const char *cs) {
    assert(device != nullptr && "ComputePipelineDesc::device must be assigned before set_shader");
    shader_modules.clear();
    shader_modules.push_back(load_shader(sc, *device, path, cs, ShaderStage::COMPUTE));
    shader_path = path;
    cs_entry = cs;
    return *this;
}

RenderPipelineDesc &RenderPipelineDesc::set_input_topology(rhi::PipelineTopology topology) {
    this->topology = topology;
    return *this;
}

RenderPipelineDesc &RenderPipelineDesc::set_polygon_mode(rhi::PipelineFillMode mode) {
    fill_mode = mode;
    return *this;
}

RenderPipelineDesc &RenderPipelineDesc::set_color_format(ImageFormat format) {
    color_attachment_formats.push_back(format);
    return *this;
};

RenderPipelineDesc &RenderPipelineDesc::add_vertex_binding(u32 binding, u32 stride, bool is_instanced) {
    vertex_bindings.push_back({binding, stride, is_instanced});
    return *this;
}

RenderPipelineDesc &
RenderPipelineDesc::add_vertex_attribute(u32 location, u32 binding, ImageFormat format, u32 offset) {
    vertex_attributes.push_back({location, binding, format, offset});
    return *this;
}

RenderPipelineDesc &RenderPipelineDesc::set_depth_format(ImageFormat format) {
    depth_attachment_format = format;
    return *this;
};

RenderPipelineDesc &
RenderPipelineDesc::set_cull_mode(rhi::PipelineCullMode mode, rhi::PipelineTriangleWindingOrder winding) {
    cull_mode = mode;
    this->winding = winding;
    return *this;
}

RenderPipelineDesc &RenderPipelineDesc::set_multisampling(rhi::SampleCount count) {
    samples = count;
    return *this;
}

RenderPipelineDesc &RenderPipelineDesc::set_depth_testing(bool do_test, bool do_write, rhi::PipelineCompareOp op) {
    depth_test_enable = do_test;
    depth_write_enable = do_write;
    depth_compare_op = op;
    return *this;
}

RenderPipelineDesc &RenderPipelineDesc::set_depth_bias(bool enabled, f32 constant, f32 clamp, f32 slope) {
    depth_bias_enable = enabled;
    depth_bias_constant = constant;
    depth_bias_clamp = clamp;
    depth_bias_slope = slope;
    return *this;
}

RenderPipelineDesc &RenderPipelineDesc::set_depth_clamp(bool enabled) {
    depth_clamp_enable = enabled;
    return *this;
}

RenderPipelineDesc &RenderPipelineDesc::set_blending(
    rhi::PipelineBlendFactor src_color,
    rhi::PipelineBlendFactor dst_color,
    rhi::PipelineBlendOp color_op,
    rhi::PipelineBlendFactor src_alpha,
    rhi::PipelineBlendFactor dst_alpha,
    rhi::PipelineBlendOp alpha_op
) {
    blend_enable = true;
    src_color_blend_factor = src_color;
    dst_color_blend_factor = dst_color;
    color_blend_op = color_op;
    src_alpha_blend_factor = src_alpha;
    dst_alpha_blend_factor = dst_alpha;
    alpha_blend_op = alpha_op;
    return *this;
}

RenderPipeline RenderPipelineDesc::build() {
    assert(device != nullptr && "RenderPipelineDesc::device must be assigned before build");
    if (shader_modules.empty()) {
        VEL_ERROR("Cannot build render pipeline: no shader modules set");
        return {};
    }

    std::vector<VkPipelineShaderStageCreateInfo> shader_stages;
    for (auto &mod : shader_modules) {
        VkShaderModule vk_mod = mod.handle;
        if (vk_mod == VK_NULL_HANDLE) {
            VEL_ERROR("Cannot build render pipeline: one or more shader modules are null");
            for (auto &stage : shader_stages) {
                vkDestroyShaderModule(device->logical, stage.module, nullptr);
            }
            return {};
        }
        VkPipelineShaderStageCreateInfo stage_info = {};
        stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
        stage_info.stage = static_cast<VkShaderStageFlagBits>(vk::shader_stage_to_vk(mod.stage));
        stage_info.module = vk_mod;
        stage_info.pName = "main";
        shader_stages.push_back(stage_info);
    }

    RenderPipeline result;
    result.device = device;

    {

        VkDynamicState dynamic_states[8];
        u32 ds_count = 0;
        dynamic_states[ds_count++] = VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT;
        dynamic_states[ds_count++] = VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT;
        if (enable_dynamic_polygon_mode) {
            dynamic_states[ds_count++] = VK_DYNAMIC_STATE_POLYGON_MODE_EXT;
        }
        if (enable_dynamic_depth_write) {
            dynamic_states[ds_count++] = VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE;
        }
        VkPipelineDynamicStateCreateInfo dynamic_info{
            .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
            .dynamicStateCount = ds_count,
            .pDynamicStates = dynamic_states
        };

        std::vector<VkVertexInputBindingDescription> vk_bindings;
        vk_bindings.reserve(vertex_bindings.size());
        for (const auto &b : vertex_bindings) {
            vk_bindings.push_back({
                .binding = b.binding,
                .stride = b.stride,
                .inputRate = b.is_instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX,
            });
        }

        std::vector<VkVertexInputAttributeDescription> vk_attributes;
        vk_attributes.reserve(vertex_attributes.size());
        for (const auto &a : vertex_attributes) {
            vk_attributes.push_back({
                .location = a.location,
                .binding = a.binding,
                .format = vk::image_format_to_vk(a.format),
                .offset = a.offset,
            });
        }

        VkPipelineVertexInputStateCreateInfo vertex_input{
            .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
            .vertexBindingDescriptionCount = (u32)vk_bindings.size(),
            .pVertexBindingDescriptions = vk_bindings.empty() ? nullptr : vk_bindings.data(),
            .vertexAttributeDescriptionCount = (u32)vk_attributes.size(),
            .pVertexAttributeDescriptions = vk_attributes.empty() ? nullptr : vk_attributes.data(),
        };

        VkPipelineInputAssemblyStateCreateInfo input_assembly{
            .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
            .topology = vk::pipeline_topology_to_vk(topology),
        };

        VkPipelineRasterizationStateCreateInfo rasterizer{
            .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
            .depthClampEnable = depth_clamp_enable ? VK_TRUE : VK_FALSE,
            .rasterizerDiscardEnable = VK_FALSE,
            .polygonMode = vk::pipeline_fill_mode_to_vk(fill_mode),
            .cullMode = vk::pipeline_cull_mode_to_vk(cull_mode),
            .frontFace = vk::triangle_winding_to_vk(winding),
            .depthBiasEnable = depth_bias_enable ? VK_TRUE : VK_FALSE,
            .depthBiasConstantFactor = depth_bias_constant,
            .depthBiasClamp = depth_bias_clamp,
            .depthBiasSlopeFactor = depth_bias_slope,
            .lineWidth = 1.0f
        };

        VkPipelineMultisampleStateCreateInfo multisampling{
            .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
            .rasterizationSamples = vk::sample_count_to_vk(samples),
        };

        VkPipelineDepthStencilStateCreateInfo depth_stencil{
            .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
            .depthTestEnable = depth_test_enable ? VK_TRUE : VK_FALSE,
            .depthWriteEnable = depth_write_enable ? VK_TRUE : VK_FALSE,
            .depthCompareOp = vk::pipeline_compare_op_to_vk(depth_compare_op),
        };

        VkPipelineViewportStateCreateInfo viewport_state{
            .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
            .viewportCount = 0,
            .pViewports = nullptr,
            .scissorCount = 0,
            .pScissors = nullptr,
        };

        std::vector<VkFormat> vk_color_formats;
        for (auto &format : color_attachment_formats) {
            vk_color_formats.push_back(vk::image_format_to_vk(format));
        }
        VkPipelineRenderingCreateInfo rendering_info{
            .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
            .colorAttachmentCount = (u32)color_attachment_formats.size(),
            .pColorAttachmentFormats = vk_color_formats.empty() ? nullptr : vk_color_formats.data(),
            .depthAttachmentFormat = vk::image_format_to_vk(depth_attachment_format),
        };

        VkPipelineColorBlendAttachmentState color_blend_attachment{
            .blendEnable = blend_enable ? VK_TRUE : VK_FALSE,
            .srcColorBlendFactor = vk::pipeline_blend_factor_to_vk(src_color_blend_factor),
            .dstColorBlendFactor = vk::pipeline_blend_factor_to_vk(dst_color_blend_factor),
            .colorBlendOp = vk::pipeline_blend_op_to_vk(color_blend_op),
            .srcAlphaBlendFactor = vk::pipeline_blend_factor_to_vk(src_alpha_blend_factor),
            .dstAlphaBlendFactor = vk::pipeline_blend_factor_to_vk(dst_alpha_blend_factor),
            .alphaBlendOp = vk::pipeline_blend_op_to_vk(alpha_blend_op),
            .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT |
                              VK_COLOR_COMPONENT_A_BIT,
        };

        std::vector<VkPipelineColorBlendAttachmentState> blend_attachments(
            rendering_info.colorAttachmentCount, color_blend_attachment
        );

        VkPipelineColorBlendStateCreateInfo color_blending{
            .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
            .attachmentCount = (u32)blend_attachments.size(),
            .pAttachments = blend_attachments.data()
        };

        VkGraphicsPipelineCreateInfo pipeline_info = {VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
        pipeline_info.pNext = &rendering_info;
        pipeline_info.stageCount = (u32)shader_stages.size();
        pipeline_info.pStages = shader_stages.data();
        pipeline_info.pVertexInputState = &vertex_input;
        pipeline_info.pInputAssemblyState = &input_assembly;
        pipeline_info.pViewportState = &viewport_state;
        pipeline_info.pRasterizationState = &rasterizer;
        pipeline_info.pMultisampleState = &multisampling;
        pipeline_info.pColorBlendState = &color_blending;
        pipeline_info.pDepthStencilState = &depth_stencil;
        pipeline_info.pDynamicState = &dynamic_info;
        pipeline_info.layout = device->pipeline_layout;
        VK_CHECK(vkCreateGraphicsPipelines(
            device->logical, device->pipeline_cache, 1, &pipeline_info, nullptr, &result._handle
        ));
        if (!name.empty()) {
            char pipe_name[256];
            (void)snprintf(pipe_name, sizeof(pipe_name), "pipe/%s", name.c_str());
            VK_NAME_EX((*device), result._handle, pipe_name);
        }
    }

    for (auto &stage : shader_stages) {
        vkDestroyShaderModule(device->logical, stage.module, nullptr);
    }
    shader_modules.clear();
    vertex_bindings.clear();
    vertex_attributes.clear();

    return result;
}

ComputePipeline ComputePipelineDesc::build() {
    assert(device != nullptr && "ComputePipelineDesc::device must be assigned before build");
    if (shader_modules.empty()) {
        VEL_ERROR("Cannot build compute pipeline: no shader modules set");
        return {};
    }
    VkShaderModule vk_mod = shader_modules[0].handle;
    if (vk_mod == VK_NULL_HANDLE) {
        VEL_ERROR("Cannot build compute pipeline: shader module is null");
        return {};
    }
    VkPipelineShaderStageCreateInfo stage_info = {};
    stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    stage_info.stage = static_cast<VkShaderStageFlagBits>(vk::shader_stage_to_vk(shader_modules[0].stage));
    stage_info.module = vk_mod;
    stage_info.pName = "main";

    ComputePipeline result;
    result.device = device;

    VkComputePipelineCreateInfo pipeline_info = {
        .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
        .stage = stage_info,
        .layout = device->pipeline_layout,
    };
    VK_CHECK(
        vkCreateComputePipelines(device->logical, device->pipeline_cache, 1, &pipeline_info, nullptr, &result._handle)
    );
    if (!name.empty()) {
        char pipe_name[256];
        (void)snprintf(pipe_name, sizeof(pipe_name), "pipe/%s", name.c_str());
        VK_NAME_EX((*device), result._handle, pipe_name);
    }

    vkDestroyShaderModule(device->logical, stage_info.module, nullptr);
    shader_modules.clear();

    return result;
}

void destroy_pipeline(Device &device, RenderPipeline &p) {
    vkDestroyPipeline(device.logical, p._handle, nullptr);
    p = {};
}

void destroy_pipeline(Device &device, ComputePipeline &p) {
    vkDestroyPipeline(device.logical, p._handle, nullptr);
    p = {};
}

} // namespace rhi