添加从文件中读取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. 最终测试
程序和以前一样,可以正常运行。