Vulkan 学习笔记 01

我最近又开始了Vulkan的学习,为了工作与个人兴趣。在这里做一些笔记,记录一下各种坑。代码在github上可以看到。 代码的主体框架参考的是GAMES104的Piccolo引擎,但是实际上基本只保留了代码反射的部分,其余的都在重新编写和调整。
当前正在按照VulkanTutorial学习和编写Vulkan相关代码。暂时的成果如下,在教程的例子下添加了imgui的界面。

Screen Shot
本章主要介绍如何利用glslang在程序运行时实时编译shader,而不需要每次修改后手动重新编译,方便调试。这个部分代码参考的是glslang自带工具的代码,整理后具体代码如下:

#include <volk.h>

#include <array>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

namespace ArchViz
{
    class ConfigManager;
    class AssetManager;

    class VulkanShaderUtils
    {
    public:
        static std::vector<uint32_t> createShaderModuleFromVFS(const std::string& shader_file, std::shared_ptr<ConfigManager> config_manager, std::shared_ptr<AssetManager> asset_manager);
        static std::vector<uint32_t> createShaderModuleFromFile(const std::string& shader_file, std::shared_ptr<ConfigManager> config_manager, std::shared_ptr<AssetManager> asset_manager);
        static std::vector<uint32_t> createShaderModuleFromCode(const std::string& shader_code, const std::string& shader_type, std::shared_ptr<ConfigManager> config_manager);
        static VkShaderModule        createShaderModule(VkDevice device, const std::vector<uint32_t>& shader_code);
    };
} // namespace ArchViz

#include "runtime/function/render/rhi/vulkan/vulkan_shader_utils.h"
#include "runtime/function/render/rhi/vulkan/vulkan_header_includer.h"

#include "runtime/core/base/macro.h"
#include "runtime/core/string/string_utils.h"

#include "runtime/resource/asset_manager/asset_manager.h"
#include "runtime/resource/config_manager/config_manager.h"

#include <SPIRV/GLSL.ext.EXT.h>
#include <SPIRV/GLSL.ext.KHR.h>
#include <SPIRV/GLSL.std.450.h>
#include <SPIRV/GlslangToSpv.h>
#include <SPIRV/disassemble.h>
#include <SPIRV/doc.h>

#include <glslang/Include/ResourceLimits.h>
#include <glslang/Include/ShHandle.h>

#include <glslang/Public/ResourceLimits.h>
#include <glslang/Public/ShaderLang.h>

#include <algorithm>
#include <cmath>
#include <cstring>
#include <filesystem>
#include <fstream>

namespace ArchViz
{
    EShLanguage shaderLanguageStageFromFileName(const char* fileName)
    {
        if (end_with(fileName, ".vert"))
            return EShLangVertex;
        if (end_with(fileName, ".frag"))
            return EShLangFragment;
        if (end_with(fileName, ".geom"))
            return EShLangGeometry;
        if (end_with(fileName, ".comp"))
            return EShLangCompute;
        if (end_with(fileName, ".tesc"))
            return EShLangTessControl;
        if (end_with(fileName, ".tese"))
            return EShLangTessEvaluation;
        return EShLangVertex;
    }

    std::vector<uint32_t> VulkanShaderUtils::createShaderModuleFromVFS(const std::string& shader_file, std::shared_ptr<ConfigManager> config_manager, std::shared_ptr<AssetManager> asset_manager)
    {
        std::filesystem::path root_path        = config_manager->getRootFolder();
        std::filesystem::path shader_file_path = root_path / "shader" / "glsl" / shader_file;
        std::filesystem::path include_path     = root_path / "shader" / "include";
        LOG_DEBUG("open shader: " + shader_file_path.generic_string());

        std::string shader_code = "";
        // asset_manager->readTextFile(shader_file_path, shader_code);
        asset_manager->readVFSTextFile(shader_file, shader_code);

        EShLanguage stage = shaderLanguageStageFromFileName(shader_file.c_str());

        VulkanHeaderIncluder includer;
        includer.pushExternalLocalDirectory(include_path.generic_string());

        auto client          = glslang::EShClientVulkan;
        auto client_version  = glslang::EShTargetVulkan_1_0;
        auto target_language = glslang::EShTargetSpv;
        auto target_version  = glslang::EShTargetSpv_1_0;
        auto messages        = EShMsgDefault;

        glslang::InitializeProcess();

        glslang::TProgram* program = new glslang::TProgram;
        glslang::TShader*  shader  = new glslang::TShader(stage);

        const char* file_names[]   = {shader_file.c_str()};
        const char* shader_codes[] = {shader_code.c_str()};
        shader->setStringsWithLengthsAndNames(shader_codes, NULL, file_names, 1);

        std::string              preamble_str = "";
        std::vector<std::string> processes    = {};

        shader->setPreamble(preamble_str.c_str());
        shader->addProcesses(processes);

        shader->setEnvInput(glslang::EShSourceGlsl, stage, client, 100);
        shader->setEnvClient(client, client_version);
        shader->setEnvTarget(target_language, target_version);

        const TBuiltInResource* resources      = GetDefaultResources();
        const int               defaultVersion = 100;
        std::string             str;

        if (!shader->preprocess(resources, defaultVersion, ENoProfile, false, false, messages, &str, includer))
        {
            LOG_ERROR(shader->getInfoLog());
            LOG_FATAL(shader->getInfoDebugLog());
        }

        if (!shader->parse(resources, defaultVersion, false, messages, includer))
        {
            LOG_ERROR(shader->getInfoLog());
            LOG_FATAL(shader->getInfoDebugLog());
        }

        program->addShader(shader);

        if (!program->link(messages))
        {
            LOG_ERROR(program->getInfoLog());
            LOG_FATAL(program->getInfoDebugLog());
        }

        if (!program->mapIO())
        {
            LOG_ERROR(program->getInfoLog());
            LOG_FATAL(program->getInfoDebugLog());
        }

        bool SpvToolsDisassembler = false;
        bool SpvToolsValidate     = false;

        std::vector<uint32_t> spirv;
        if (program->getIntermediate(stage))
        {
            spv::SpvBuildLogger logger;
            glslang::SpvOptions spvOptions;
            spvOptions.stripDebugInfo   = true;
            spvOptions.disableOptimizer = true;
            spvOptions.optimizeSize     = true;
            spvOptions.disassemble      = SpvToolsDisassembler;
            spvOptions.validate         = SpvToolsValidate;
            glslang::GlslangToSpv(*program->getIntermediate((EShLanguage)stage), spirv, &logger, &spvOptions);
        }
        else
        {
            LOG_FATAL("cannot find target shader")
        }

        glslang::FinalizeProcess();

        delete shader;
        delete program;

        // copy data to unsigned char;
        // std::vector<unsigned char> spirv_char;
        // spirv_char.resize(spirv.size() * sizeof(unsigned int));
        // memcpy(spirv_char.data(), spirv.data(), spirv_char.size());

        return spirv;
    }

    std::vector<uint32_t> VulkanShaderUtils::createShaderModuleFromFile(const std::string& shader_file, std::shared_ptr<ConfigManager> config_manager, std::shared_ptr<AssetManager> asset_manager)
    {
        std::filesystem::path root_path        = config_manager->getRootFolder();
        std::filesystem::path shader_file_path = root_path / "shader" / "glsl" / shader_file;
        std::filesystem::path include_path     = root_path / "shader" / "include";
        LOG_DEBUG("open shader: " + shader_file_path.generic_string());

        std::string shader_code = "";
        // asset_manager->readTextFile(shader_file_path, shader_code);
        asset_manager->readTextFile(shader_file, shader_code);

        EShLanguage stage = shaderLanguageStageFromFileName(shader_file.c_str());

        VulkanHeaderIncluder includer;
        includer.pushExternalLocalDirectory(include_path.generic_string());

        auto client          = glslang::EShClientVulkan;
        auto client_version  = glslang::EShTargetVulkan_1_0;
        auto target_language = glslang::EShTargetSpv;
        auto target_version  = glslang::EShTargetSpv_1_0;
        auto messages        = EShMsgDefault;

        glslang::InitializeProcess();

        glslang::TProgram* program = new glslang::TProgram;
        glslang::TShader*  shader  = new glslang::TShader(stage);

        const char* file_names[]   = {shader_file.c_str()};
        const char* shader_codes[] = {shader_code.c_str()};
        shader->setStringsWithLengthsAndNames(shader_codes, NULL, file_names, 1);

        std::string              preamble_str = "";
        std::vector<std::string> processes    = {};

        shader->setPreamble(preamble_str.c_str());
        shader->addProcesses(processes);

        shader->setEnvInput(glslang::EShSourceGlsl, stage, client, 100);
        shader->setEnvClient(client, client_version);
        shader->setEnvTarget(target_language, target_version);

        const TBuiltInResource* resources      = GetDefaultResources();
        const int               defaultVersion = 100;
        std::string             str;

        if (!shader->preprocess(resources, defaultVersion, ENoProfile, false, false, messages, &str, includer))
        {
            LOG_ERROR(shader->getInfoLog());
            LOG_FATAL(shader->getInfoDebugLog());
        }

        if (!shader->parse(resources, defaultVersion, false, messages, includer))
        {
            LOG_ERROR(shader->getInfoLog());
            LOG_FATAL(shader->getInfoDebugLog());
        }

        program->addShader(shader);

        if (!program->link(messages))
        {
            LOG_ERROR(program->getInfoLog());
            LOG_FATAL(program->getInfoDebugLog());
        }

        if (!program->mapIO())
        {
            LOG_ERROR(program->getInfoLog());
            LOG_FATAL(program->getInfoDebugLog());
        }

        bool SpvToolsDisassembler = false;
        bool SpvToolsValidate     = false;

        std::vector<uint32_t> spirv;
        if (program->getIntermediate(stage))
        {
            spv::SpvBuildLogger logger;
            glslang::SpvOptions spvOptions;
            spvOptions.stripDebugInfo   = true;
            spvOptions.disableOptimizer = true;
            spvOptions.optimizeSize     = true;
            spvOptions.disassemble      = SpvToolsDisassembler;
            spvOptions.validate         = SpvToolsValidate;
            glslang::GlslangToSpv(*program->getIntermediate((EShLanguage)stage), spirv, &logger, &spvOptions);
        }
        else
        {
            LOG_FATAL("cannot find target shader");
        }

        glslang::FinalizeProcess();

        delete shader;
        delete program;

        // copy data to unsigned char;
        // std::vector<unsigned char> spirv_char;
        // spirv_char.resize(spirv.size() * sizeof(unsigned int));
        // memcpy(spirv_char.data(), spirv.data(), spirv_char.size());

        return spirv;
    }

    std::vector<uint32_t> VulkanShaderUtils::createShaderModuleFromCode(const std::string& shader_code, const std::string& shader_type, std::shared_ptr<ConfigManager> config_manager)
    {
        std::filesystem::path root_path    = config_manager->getRootFolder();
        std::filesystem::path include_path = root_path / "shader" / "include";

        EShLanguage stage = shaderLanguageStageFromFileName(shader_type.c_str());

        VulkanHeaderIncluder includer;
        includer.pushExternalLocalDirectory(include_path.generic_string());

        auto client          = glslang::EShClientVulkan;
        auto client_version  = glslang::EShTargetVulkan_1_0;
        auto target_language = glslang::EShTargetSpv;
        auto target_version  = glslang::EShTargetSpv_1_0;
        auto messages        = EShMsgDefault;

        glslang::InitializeProcess();

        glslang::TProgram* program = new glslang::TProgram;
        glslang::TShader*  shader  = new glslang::TShader(stage);

        const char* file_names[]   = {shader_type.c_str()};
        const char* shader_codes[] = {shader_code.c_str()};
        shader->setStringsWithLengthsAndNames(shader_codes, NULL, file_names, 1);

        std::string              preamble_str = "";
        std::vector<std::string> processes    = {};

        shader->setPreamble(preamble_str.c_str());
        shader->addProcesses(processes);

        shader->setEnvInput(glslang::EShSourceGlsl, stage, client, 100);
        shader->setEnvClient(client, client_version);
        shader->setEnvTarget(target_language, target_version);

        const TBuiltInResource* resources      = GetDefaultResources();
        const int               defaultVersion = 100;
        std::string             str;

        if (!shader->preprocess(resources, defaultVersion, ENoProfile, false, false, messages, &str, includer))
        {
            LOG_ERROR(shader->getInfoLog());
            LOG_FATAL(shader->getInfoDebugLog());
        }

        if (!shader->parse(resources, defaultVersion, false, messages, includer))
        {
            LOG_ERROR(shader->getInfoLog());
            LOG_FATAL(shader->getInfoDebugLog());
        }

        program->addShader(shader);

        if (!program->link(messages))
        {
            LOG_ERROR(program->getInfoLog());
            LOG_FATAL(program->getInfoDebugLog());
        }

        if (!program->mapIO())
        {
            LOG_ERROR(program->getInfoLog());
            LOG_FATAL(program->getInfoDebugLog());
        }

        bool SpvToolsDisassembler = false;
        bool SpvToolsValidate     = false;

        std::vector<uint32_t> spirv;
        if (program->getIntermediate(stage))
        {
            spv::SpvBuildLogger logger;
            glslang::SpvOptions spvOptions;
            spvOptions.stripDebugInfo   = true;
            spvOptions.disableOptimizer = true;
            spvOptions.optimizeSize     = true;
            spvOptions.disassemble      = SpvToolsDisassembler;
            spvOptions.validate         = SpvToolsValidate;
            glslang::GlslangToSpv(*program->getIntermediate((EShLanguage)stage), spirv, &logger, &spvOptions);
        }
        else
        {
            LOG_FATAL("cannot find target shader");
        }

        glslang::FinalizeProcess();

        delete shader;
        delete program;

        // copy data to unsigned char;
        // std::vector<unsigned char> spirv_char;
        // spirv_char.resize(spirv.size() * sizeof(unsigned int));
        // memcpy(spirv_char.data(), spirv.data(), spirv_char.size());

        return spirv;
    }

    VkShaderModule VulkanShaderUtils::createShaderModule(VkDevice device, const std::vector<uint32_t>& shader_code)
    {
        VkShaderModuleCreateInfo shader_module_create_info {};
        shader_module_create_info.sType    = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
        shader_module_create_info.codeSize = shader_code.size() * sizeof(uint32_t);
        shader_module_create_info.pCode    = reinterpret_cast<const uint32_t*>(shader_code.data());

        VkShaderModule shader_module;
        if (vkCreateShaderModule(device, &shader_module_create_info, nullptr, &shader_module) != VK_SUCCESS)
        {
            return VK_NULL_HANDLE;
        }
        return shader_module;
    }
} // namespace ArchViz