添加从文件中读取Shader功能后,可以更方便用户修改Shader文件,而不需要每次修改Shader后都重新编译整个引擎,提高一点点效率。

1. 添加读取文件辅助函数

    void AssetManager::readTextFile(const std::filesystem::path& file_path, std::string& content)
    {
        std::ifstream fin(file_path, std::ios::in);
        content = {std::istreambuf_iterator<char>(fin), std::istreambuf_iterator<char>()};
    }

    void AssetManager::readBinaryFile(const std::filesystem::path& file_path, std::vector<unsigned char>& content)
    {
        std::ifstream fin;
        fin.open(file_path, std::ios::in | std::ios::binary);

        size_t begin = fin.tellg();
        size_t end   = begin;

        fin.seekg(0, std::ios_base::end);
        end = fin.tellg();
        fin.seekg(0, std::ios_base::beg);

        size_t file_size = 0;
        file_size        = end - begin;

        content.resize(file_size);
        fin.read(reinterpret_cast<char*>(content.data()), sizeof(file_size));
        fin.close();
    }

2. 在VulkanUtil中添加编译Shader的辅助函数

该编译过程利用了glslang库,参考了glslangValidator的代码:

    VkShaderModule VulkanUtil::createShaderModule(VkDevice device, const std::string& shader_file)
    {
        std::filesystem::path asset_path       = g_runtime_global_context.m_config_manager->getRootFolder();
        std::filesystem::path shader_file_path = asset_path / "shader" / "glsl" / shader_file;
        std::filesystem::path include_path     = asset_path / "shader" / "include";
        LOG_DEBUG("open shader: " + shader_file_path.generic_string());

        std::string shader_code = "";
        g_runtime_global_context.m_asset_manager->readTextFile(shader_file_path, shader_code);

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

        DirStackFileIncluder 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);

        auto        resources      = glslang::DefaultTBuiltInResource;
        const int   defaultVersion = 100;
        std::string str;
        if (shader->preprocess(&resources, defaultVersion, ENoProfile, false, false, messages, &str, includer))
        {
            LOG_WARN(str);
        }
        else
        {
            LOG_ERROR(shader->getInfoLog());
            LOG_ERROR(shader->getInfoDebugLog());
            throw std::exception("preprocess failed");
        }

        if (!shader->parse(&resources, defaultVersion, false, messages, includer))
        {
            LOG_ERROR(shader->getInfoLog());
            LOG_ERROR(shader->getInfoDebugLog());
            throw std::exception("parse failed");
        }

        program->addShader(shader);

        if (!program->link(messages))
        {
            LOG_ERROR(program->getInfoLog());
            LOG_ERROR(program->getInfoDebugLog());
            throw std::exception("link failed");
        }

        if (!program->mapIO())
        {
            LOG_ERROR(program->getInfoLog());
            LOG_ERROR(program->getInfoDebugLog());
            throw std::exception("link failed");
        }

        bool SpvToolsDisassembler = false;
        bool SpvToolsValidate     = false;

        std::vector<unsigned int> spirv;
        for (int stage = 0; stage < EShLangCount; ++stage)
        {
            if (program->getIntermediate((EShLanguage)stage))
            {
                spv::SpvBuildLogger logger;
                glslang::SpvOptions spvOptions;
                // spvOptions.generateDebugInfo = false;
                spvOptions.stripDebugInfo   = true;
                spvOptions.disableOptimizer = true;
                spvOptions.optimizeSize     = true;
                spvOptions.disassemble      = SpvToolsDisassembler;
                spvOptions.validate         = SpvToolsValidate;
                glslang::GlslangToSpv(*program->getIntermediate((EShLanguage)stage), spirv, &logger, &spvOptions);
                break;
            }
        }

        glslang::FinalizeProcess();

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

        return createShaderModule(device, spirv_char);
    }

顺便在VulkanRHI中添加对应的函数:

    RHIShader* VulkanRHI::createShaderModule(const std::string& shader_file)
    {
        RHIShader* shahder = new VulkanShader();

        VkShaderModule vk_shader = VulkanUtil::createShaderModule(m_device, shader_file);

        ((VulkanShader*)shahder)->setResource(vk_shader);

        return shahder;
    }

3. 在PointLightShadowPass中进行测试

将setupPipelines函数中的加载Shader代码进行替换:

RHIShader* vert_shader_module = m_rhi->createShaderModule("mesh_point_light_shadow.vert");
RHIShader* geom_shader_module = m_rhi->createShaderModule("mesh_point_light_shadow.geom");
RHIShader* frag_shader_module = m_rhi->createShaderModule("mesh_point_light_shadow.frag");

4. 修改CMakeLists.txt来拷贝Shader文件

修改shader文件夹下的CMakeLists.txt,添加拷贝文件的命令:

...
add_custom_target(COPY_FILES ALL  
  VERBATIM
  # COMMAND_EXPAND_LISTS
  COMMAND ${CMAKE_COMMAND} -E make_directory "${BINARY_ROOT_DIR}/shader"
  COMMAND ${CMAKE_COMMAND} -E make_directory "${BINARY_ROOT_DIR}/shader/include"
  COMMAND ${CMAKE_COMMAND} -E make_directory "${BINARY_ROOT_DIR}/shader/glsl"
  COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/include" "${BINARY_ROOT_DIR}/shader/include"
  COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/glsl" "${BINARY_ROOT_DIR}/shader/glsl"
)

5. 最终测试

程序和以前一样,可以正常运行。