Program Listing for File ShaderHelper.cpp

Program Listing for File ShaderHelper.cpp#

Return to documentation for file (Src/GraphicsEngineVulkan/vulkan_base/ShaderHelper.cpp)

module;

#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <filesystem>
#include <iomanip>
#include <sstream>
#include <string>
#include <system_error>
#include <vector>
#include <vulkan/vulkan.hpp>

#include "vulkan_base/ShaderIncludes.hpp"

#include "spdlog/spdlog.h"

module kataglyphis.vulkan.shader_helper;

import kataglyphis.vulkan.config;

namespace {
constexpr int kShaderSearchMaxDepth = 8;

auto resolve_shader_source_path(const std::string &raw_shader_src_path) -> std::string
{
    std::error_code filesystem_error;
    std::filesystem::path const source_path(raw_shader_src_path);
    if (std::filesystem::exists(source_path, filesystem_error)) { return source_path.string(); }

    std::string normalized_raw_shader_src_path = raw_shader_src_path;
    std::ranges::replace(normalized_raw_shader_src_path, '\\', '/');

    const std::string marker = "Resources/Shaders/";
    const auto marker_pos = normalized_raw_shader_src_path.find(marker);
    if (marker_pos == std::string::npos) { return raw_shader_src_path; }

    const std::string relative_shader = normalized_raw_shader_src_path.substr(marker_pos + marker.size());
    auto current_path = std::filesystem::current_path(filesystem_error);
    if (filesystem_error) { return raw_shader_src_path; }

    for (int depth = 0; depth < kShaderSearchMaxDepth; ++depth) {
        const auto candidate = current_path / "Resources" / "Shaders" / relative_shader;
        if (std::filesystem::exists(candidate, filesystem_error)) { return candidate.string(); }

        if (filesystem_error || !current_path.has_parent_path()) { break; }

        current_path = current_path.parent_path();
    }

    return raw_shader_src_path;
}
}// namespace

Kataglyphis::ShaderHelper::ShaderHelper() = default;

void Kataglyphis::ShaderHelper::compileShader(const std::string &shader_src_dir, const std::string &shader_name)
{
    // GLSLC_EXE is set by cmake to the location of the vulkan glslc
    std::stringstream shader_src_path;
    std::stringstream shader_log_file;
    std::stringstream cmdShaderCompile;
    std::stringstream adminPriviliges;
    adminPriviliges << "runas /user:<admin-user> \"";

    // with wrapping your path with quotation marks one can use paths with blanks ...
    shader_src_path << shader_src_dir << shader_name;
    const auto resolved_shader_src_path = resolve_shader_source_path(shader_src_path.str());

    std::filesystem::path const resolved_shader_path_object(resolved_shader_src_path);
    std::filesystem::path const shader_spv_path_object =
      resolved_shader_path_object.parent_path() / "spv" / (resolved_shader_path_object.filename().string() + ".spv");
    std::error_code filesystem_error;
    std::filesystem::create_directories(shader_spv_path_object.parent_path(), filesystem_error);

    std::string shader_spv_path = shader_spv_path_object.string();

    // If the SPV file already exists, skip runtime compilation.
    if (std::filesystem::exists(shader_spv_path, filesystem_error) && !filesystem_error) {
        spdlog::default_logger_raw()->log(
          spdlog::level::info, std::string("SPV already present, skipping runtime compile for: ") + shader_spv_path);
        return;
    }

    // By default, disable runtime shader compilation in Release builds unless explicitly enabled
#ifndef KAT_ENABLE_RUNTIME_SHADER_COMPILATION
# ifdef NDEBUG
#  define KAT_ENABLE_RUNTIME_SHADER_COMPILATION 0
# else
#  define KAT_ENABLE_RUNTIME_SHADER_COMPILATION 1
# endif
#endif

#if KAT_ENABLE_RUNTIME_SHADER_COMPILATION
    shader_log_file << shader_src_dir << shader_name << ".log.txt";
    std::stringstream log_stdout_and_stderr;
    log_stdout_and_stderr << " > " << shader_log_file.str() << " 2> " << shader_log_file.str();

    cmdShaderCompile//<< adminPriviliges.str()
      << Kataglyphis::RendererConfig::glslcExe << target << std::quoted(resolved_shader_src_path) << " -o "
      << std::quoted(shader_spv_path) << " " << ShaderIncludes::getShaderIncludes();

    spdlog::default_logger_raw()->log(
      spdlog::level::info, std::string("The shader compile command is the following: ") + cmdShaderCompile.str());

    system(cmdShaderCompile.str().c_str());
#else
    spdlog::default_logger_raw()->log(
      spdlog::level::warn,
      std::string("Runtime shader compilation disabled (release). Missing SPV: ") + shader_spv_path);
    return;
#endif
}

auto Kataglyphis::ShaderHelper::getShaderSpvDir(const std::string &shader_src_dir, const std::string &shader_name)
  -> std::string
{
    std::stringstream shader_src_path;
    shader_src_path << shader_src_dir << shader_name;
    const auto resolved_shader_src_path = resolve_shader_source_path(shader_src_path.str());

    std::filesystem::path const resolved_shader_path_object(resolved_shader_src_path);
    std::filesystem::path const shader_spv_path_object =
      resolved_shader_path_object.parent_path() / "spv" / (resolved_shader_path_object.filename().string() + ".spv");

    return shader_spv_path_object.string();
}

auto Kataglyphis::ShaderHelper::createShaderModule(VulkanDevice *device, const std::vector<char> &code)
  -> vk::ShaderModule
{
    // shader module create info
    vk::ShaderModuleCreateInfo shader_module_create_info{};
    shader_module_create_info.codeSize = code.size();// size of code
    shader_module_create_info.pCode = reinterpret_cast<const uint32_t *>(code.data());// pointer to code

    // C++ API throws on failure, no manual error check needed
    vk::ShaderModule shader_module = device->getLogicalDevice().createShaderModule(shader_module_create_info).value;

    return shader_module;
}

Kataglyphis::ShaderHelper::~ShaderHelper() = default;