From 7cb62e5a90f42050c39f5f76dd54abf4913a897d Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Thu, 30 Nov 2023 09:17:53 +0100 Subject: [PATCH] Feat: Vulkan shader compilation --- src/atta/cmakeConfig.h.in | 2 + src/atta/file/interface.cpp | 2 + src/atta/file/interface.h | 1 + src/atta/file/manager.cpp | 20 +-- src/atta/file/manager.h | 2 + src/atta/graphics/apis/vulkan/shader.cpp | 154 +++++++++++++++++------ src/atta/graphics/apis/vulkan/shader.h | 4 +- src/atta/graphics/shader.cpp | 2 +- src/atta/graphics/shader.h | 36 +++--- 9 files changed, 154 insertions(+), 69 deletions(-) diff --git a/src/atta/cmakeConfig.h.in b/src/atta/cmakeConfig.h.in index b1f3901d..91956633 100644 --- a/src/atta/cmakeConfig.h.in +++ b/src/atta/cmakeConfig.h.in @@ -9,8 +9,10 @@ #ifdef ATTA_OS_WEB #define ATTA_DIR "/" +#define ATTA_BUILD_DIR "/" #else #define ATTA_DIR "@CMAKE_SOURCE_DIR@" +#define ATTA_BUILD_DIR "@CMAKE_BINARY_DIR@" #endif #define ATTA_VERSION "@CMAKE_PROJECT_VERSION@" diff --git a/src/atta/file/interface.cpp b/src/atta/file/interface.cpp index 6071f1e6..f3b4c351 100644 --- a/src/atta/file/interface.cpp +++ b/src/atta/file/interface.cpp @@ -24,6 +24,8 @@ fs::path solveResourcePath(fs::path relativePath, bool mustExist) { return Manag std::vector getResourcePaths() { return Manager::getInstance().getResourcePathsImpl(); } +fs::path getBuildPath() { return Manager::getInstance().getBuildPathImpl(); } + std::vector getDirectoryFilesRecursive(fs::path directory) { return Manager::getInstance().getDirectoryFilesRecursiveImpl(directory); } fs::path getDefaultProjectFolder() { return Manager::getInstance()._defaultProjectFolder; } diff --git a/src/atta/file/interface.h b/src/atta/file/interface.h index b660cca2..3f8892d2 100644 --- a/src/atta/file/interface.h +++ b/src/atta/file/interface.h @@ -27,6 +27,7 @@ std::shared_ptr getProject(); // The return is the absolute resource path fs::path solveResourcePath(fs::path relativePath, bool mustExist = true); std::vector getResourcePaths(); +fs::path getBuildPath(); std::vector getDirectoryFilesRecursive(fs::path directory); fs::path getDefaultProjectFolder(); diff --git a/src/atta/file/manager.cpp b/src/atta/file/manager.cpp index 4cd43871..6cfd874c 100644 --- a/src/atta/file/manager.cpp +++ b/src/atta/file/manager.cpp @@ -69,7 +69,7 @@ bool Manager::openProjectImpl(fs::path projectFile) { _projectSerializer = std::make_shared(_project); // Create .atta file if it does not exists yet - if(!fs::exists(projectFile)) + if (!fs::exists(projectFile)) _projectSerializer->serialize(); // Clear components and read project file @@ -167,6 +167,8 @@ std::vector Manager::getResourcePathsImpl() const { return {fs::path(ATTA_DIR) / "resources"}; } +fs::path Manager::getBuildPathImpl() const { return fs::path(ATTA_BUILD_DIR); } + std::vector Manager::getDirectoryFilesRecursiveImpl(fs::path directory) { std::vector files; #ifndef ATTA_OS_WEB @@ -203,14 +205,14 @@ std::vector Manager::getDirectoryFilesRecursiveImpl(fs::path directory void Manager::onSimulationStateChange(event::Event& event) { switch (event.getType()) { - case event::SimulationStart::type: - _simulationRunning = true; - break; - case event::SimulationStop::type: - _simulationRunning = false; - break; - default: - LOG_WARN("file::Manager", "Unknown simulation event"); + case event::SimulationStart::type: + _simulationRunning = true; + break; + case event::SimulationStop::type: + _simulationRunning = false; + break; + default: + LOG_WARN("file::Manager", "Unknown simulation event"); } } diff --git a/src/atta/file/manager.h b/src/atta/file/manager.h index e284f649..6bf2c75e 100644 --- a/src/atta/file/manager.h +++ b/src/atta/file/manager.h @@ -27,6 +27,7 @@ class Manager final { friend std::shared_ptr getProject(); friend fs::path solveResourcePath(fs::path relativePath, bool mustExist); friend std::vector getResourcePaths(); + friend fs::path getBuildPath(); friend std::vector getDirectoryFilesRecursive(fs::path directory); friend fs::path getDefaultProjectFolder(); friend void update(); @@ -44,6 +45,7 @@ class Manager final { fs::path solveResourcePathImpl(fs::path relativePath, bool mustExist); std::vector getResourcePathsImpl() const; + fs::path getBuildPathImpl() const; std::vector getDirectoryFilesRecursiveImpl(fs::path directory); // Handle events diff --git a/src/atta/graphics/apis/vulkan/shader.cpp b/src/atta/graphics/apis/vulkan/shader.cpp index b94d3a25..ab1a4dbc 100644 --- a/src/atta/graphics/apis/vulkan/shader.cpp +++ b/src/atta/graphics/apis/vulkan/shader.cpp @@ -7,6 +7,7 @@ #include #include +#include namespace atta::graphics::vk { @@ -17,8 +18,9 @@ Shader::Shader(const fs::path& file) : gfx::Shader(file), _device(common::getDev } Shader::~Shader() { - if (_shader != VK_NULL_HANDLE) - vkDestroyShaderModule(_device->getHandle(), _shader, nullptr); + for (auto& [type, shader] : _shaders) + if (shader != VK_NULL_HANDLE) + vkDestroyShaderModule(_device->getHandle(), shader, nullptr); } void Shader::setBool(const char* name, bool b) {} @@ -41,33 +43,105 @@ std::vector Shader::getShaderStages() const { // return result; } -std::string Shader::generateApiCode(ShaderType type, std::string iCode) { return iCode; } +std::string Shader::generateApiCode(ShaderType type, std::string iCode) { + std::string apiCode; + + // GLSL version + apiCode += "#version 450\n"; + + // Uniform buffer + if (!_uniformLayout.getElements().empty()) { + apiCode += "layout(set = 0, binding = 0) uniform UniformBufferObject {\n"; + for (const BufferLayout::Element& element : _uniformLayout.getElements()) + if (element.type != BufferLayout::Element::Type::SAMPLER_2D && element.type != BufferLayout::Element::Type::SAMPLER_CUBE) + apiCode += std::string(" ") + BufferLayout::Element::typeToString(element.type) + " " + element.name + ";\n"; + apiCode += "} ubo;\n"; + } + + // TODO Uniform image sampler + + // Remove uniform declarations from iCode + std::regex pattern(R"(^uniform .*\n?)", std::regex_constants::multiline); + std::string iCodeFix = std::regex_replace(iCode, pattern, ""); + + // Append "ubo." when using uniform in iCode + for (const BufferLayout::Element& element : _uniformLayout.getElements()) { + if (element.type != BufferLayout::Element::Type::SAMPLER_2D && element.type != BufferLayout::Element::Type::SAMPLER_CUBE) { + std::string patternStr = "\\b" + element.name + "\\b"; + std::regex pattern(patternStr); + iCodeFix = std::regex_replace(iCodeFix, pattern, "ubo." + element.name); + } + } + + // Add corrected iCode to apiCode + apiCode += iCodeFix; + + // Add location to in/out + std::istringstream iss(apiCode); + std::ostringstream oss; + std::string line; + int inNum = 0, outNum = 0; + while (std::getline(iss, line)) { + if (std::regex_search(line, std::regex(R"(^in\s)"))) { + oss << "layout(location = " << inNum++ << ") " << line << "\n"; + } else if (std::regex_search(line, std::regex(R"(^out\s)"))) { + oss << "layout(location = " << outNum++ << ") " << line << "\n"; + } else { + oss << line << "\n"; + } + } + apiCode = oss.str(); + + // Add location to out + + // LOG_DEBUG("gfx::vk::Shader", "[y]$0\n[r]iCode:\n[w]$1\n[r]apiCode:\n[w]$2", type, iCode, apiCode); + + return apiCode; +} void Shader::compile() { - // fs::path filepath = file::solveResourcePath(_filepath); - // std::string in = fs::absolute(filepath).string(); - // std::string out = in + ".spv"; - // LOG_DEBUG("sf", "$0 $1", in, out); - // if (!runCommand("glslc " + in + " -o " + out)) - // return; - - //// Parse GLSL - // parseGlsl(readFile(fs::path(in))); - - //// Create vulkan - // std::string spirvCode = readFile(fs::path(out)); - // VkShaderModuleCreateInfo createInfo{}; - // createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - // createInfo.codeSize = spirvCode.size(); - // createInfo.pCode = (const uint32_t*)spirvCode.data(); - - // if (vkCreateShaderModule(_device->getHandle(), &createInfo, nullptr, &_shader) != VK_SUCCESS) - // LOG_ERROR("gfx::vk::Shader", "Failed to create shader!"); + for (const auto& [type, shaderCode] : _shaderCodes) { + // Save shader + std::array ext = {".vert", ".geom", ".frag"}; + fs::path in = file::getBuildPath() / "shaders" / (_file.stem().string() + ext[int(type)]); + fs::create_directories(in.parent_path()); + std::ofstream file(in); + if (file.is_open()) { + file << shaderCode.apiCode; + file.close(); + LOG_DEBUG("shader", "Saved $0 code", in); + } else { + LOG_ERROR("gfx::vk::Shader", "Failed to save shader code to [w]$0[] when compiling [w]$1[]", in, _file.string()); + continue; + } + + // Compile shader + fs::path out = in.string() + ".spv"; + LOG_DEBUG("Shader", "in $0 out $1", in, out); + if (!runCommand("glslc " + in.string() + " -o " + out.string())) + return; + + // Load shader and create shader module + std::string spirvCode = readFile(fs::path(out)); + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = spirvCode.size(); + createInfo.pCode = (const uint32_t*)spirvCode.data(); + + VkShaderModule shader; + if (vkCreateShaderModule(_device->getHandle(), &createInfo, nullptr, &shader) != VK_SUCCESS) + LOG_ERROR("gfx::vk::Shader", "Failed to create shader!"); + else + _shaders[type] = shader; + } } void Shader::bind() {} -VkShaderModule Shader::getHandle() const { return _shader; } +VkShaderModule Shader::getHandle() const { + // TODO + return {}; +} VkPipelineShaderStageCreateInfo Shader::getShaderStage() const { // VkShaderStageFlagBits stage = convertFileToShaderStage(_filepath); @@ -109,27 +183,27 @@ bool Shader::runCommand(std::string cmd) { output += buffer.data(); if (output != "") { - // LOG_ERROR("gfx::vk::Shader", "Failed to compile shader [w]$0[]: $1", _filepath.string(), output); + LOG_ERROR("gfx::vk::Shader", "Failed to compile shader [w]$0[]:\n$1", _file.string(), output); return false; } return true; } -// std::string Shader::readFile(const fs::path& file) { -// std::ifstream f(file, std::ios::ate | std::ios::binary); -// -// if (!f.is_open()) { -// LOG_ERROR("gfx::vk::Shader", "Failed to open file: [w]$0[]!", file.string()); -// return {}; -// } -// -// size_t fileSize = (size_t)f.tellg(); -// std::vector buffer(fileSize); -// f.seekg(0); -// f.read(buffer.data(), fileSize); -// f.close(); -// -// return std::string(buffer.begin(), buffer.end()); -// } +std::string Shader::readFile(const fs::path& file) { + std::ifstream f(file, std::ios::ate | std::ios::binary); + + if (!f.is_open()) { + LOG_ERROR("gfx::vk::Shader", "Failed to open file: [w]$0[]!", file.string()); + return {}; + } + + size_t fileSize = (size_t)f.tellg(); + std::vector buffer(fileSize); + f.seekg(0); + f.read(buffer.data(), fileSize); + f.close(); + + return std::string(buffer.begin(), buffer.end()); +} } // namespace atta::graphics::vk diff --git a/src/atta/graphics/apis/vulkan/shader.h b/src/atta/graphics/apis/vulkan/shader.h index 4ae091d3..34dc929a 100644 --- a/src/atta/graphics/apis/vulkan/shader.h +++ b/src/atta/graphics/apis/vulkan/shader.h @@ -44,10 +44,10 @@ class Shader final : public gfx::Shader { static VkShaderStageFlagBits convertFileToShaderStage(const fs::path& filepath); bool runCommand(std::string cmd); - // std::string readFile(const fs::path& file); + std::string readFile(const fs::path& file); std::shared_ptr _device; - VkShaderModule _shader; + std::map _shaders; }; } // namespace atta::graphics::vk diff --git a/src/atta/graphics/shader.cpp b/src/atta/graphics/shader.cpp index 7a4452ee..802e129f 100644 --- a/src/atta/graphics/shader.cpp +++ b/src/atta/graphics/shader.cpp @@ -57,7 +57,7 @@ void Shader::processASL() { ShaderCode shaderCode; shaderCode.iCode = removeUnusedFunctions(generateICode(type, _aslCode)); shaderCode.apiCode = ""; - LOG_DEBUG("Shader", "[y]$0\n[w]$1", type, shaderCode.iCode); + // LOG_DEBUG("gfx::Shader", "[y]$0\n[w]$1", type, shaderCode.iCode); _shaderCodes[type] = shaderCode; } } diff --git a/src/atta/graphics/shader.h b/src/atta/graphics/shader.h index 1336ed0a..ce08d735 100644 --- a/src/atta/graphics/shader.h +++ b/src/atta/graphics/shader.h @@ -46,6 +46,24 @@ class Shader { std::string apiCode; ///< Graphics API specific code }; + /** + * @brief Process intermediate code to generate API specific code. + * + * It will mainly substitute placeholders with Graphics API specific code. + * + * @param type Shader type + * @param iCode Intermediate code + * + * @return API specific code + */ + virtual std::string generateApiCode(ShaderType type, std::string iCode) = 0; + + /** + * @brief Compile graphics API specific code + */ + virtual void compile() = 0; + + private: /** * @brief Process .asl file to generate ICode * @@ -111,23 +129,7 @@ class Shader { */ void populateUniformLayout(); - /** - * @brief Process intermediate code to generate API specific code. - * - * It will mainly substitute placeholders with Graphics API specific code. - * - * @param type Shader type - * @param iCode Intermediate code - * - * @return API specific code - */ - virtual std::string generateApiCode(ShaderType type, std::string iCode) = 0; - - /** - * @brief Compile graphics API specific code - */ - virtual void compile() = 0; - + protected: fs::path _file; std::string _aslCode;