/* Crafter® Build Copyright (C) 2026 Catcrafts® Catcrafts.net This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 3.0 as published by the Free Software Foundation; This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ module; #include "SPIRV/GlslangToSpv.h" #include "glslang/Public/ShaderLang.h" #include "glslang/Public/ResourceLimits.h" #include "../lib/DirStackFileIncluder.h" module Crafter.Build:Shader_impl; import :Shader; import std; namespace fs = std::filesystem; namespace { EShLanguage ToEShLanguage(Crafter::ShaderType t) { switch (t) { case Crafter::ShaderType::Vertex: return EShLangVertex; case Crafter::ShaderType::TessControl: return EShLangTessControl; case Crafter::ShaderType::TessEvaluation: return EShLangTessEvaluation; case Crafter::ShaderType::Geometry: return EShLangGeometry; case Crafter::ShaderType::Fragment: return EShLangFragment; case Crafter::ShaderType::Compute: return EShLangCompute; case Crafter::ShaderType::RayGen: return EShLangRayGen; case Crafter::ShaderType::Intersect: return EShLangIntersect; case Crafter::ShaderType::AnyHit: return EShLangAnyHit; case Crafter::ShaderType::ClosestHit: return EShLangClosestHit; case Crafter::ShaderType::Miss: return EShLangMiss; case Crafter::ShaderType::Callable: return EShLangCallable; case Crafter::ShaderType::Task: return EShLangTask; case Crafter::ShaderType::Mesh: return EShLangMesh; } return EShLangVertex; } } namespace Crafter { Shader::Shader(fs::path&& path, std::string&& entrypoint, ShaderType type) : path(std::move(path)), entrypoint(std::move(entrypoint)), type(type) { } bool Shader::Check(const fs::path& outputDir) const { fs::path spv = outputDir / path.filename().replace_extension("spv"); return fs::exists(spv) && fs::last_write_time(path) < fs::last_write_time(spv); } std::string Shader::Compile(const fs::path& outputDir, std::span includeDirs) const { EShLanguage glslangType = ToEShLanguage(type); glslang::InitializeProcess(); // Every error path returns a non-empty string; the caller treats // empty as success (see Crafter.Build-Clang.cpp BuildOnce shader // worker). Prefixing with the source path is what tells the user // which shader actually failed when several compile in parallel. auto fail = [&](std::string_view stage, std::string log) { glslang::FinalizeProcess(); std::string out = path.string(); out += ": "; out += stage; if (!log.empty()) { out += '\n'; out += log; } return out; }; EShMessages messages = static_cast(EShMsgDefault | EShMsgVulkanRules | EShMsgSpvRules); std::ifstream fileStream(path, std::ios::in | std::ios::binary); if (!fileStream) { return fail("failed to open shader source", {}); } std::ostringstream contents; contents << fileStream.rdbuf(); std::string src = contents.str(); std::string pathStr = path.string(); const char* file_name_list[1] = { pathStr.c_str() }; const char* shader_source = src.data(); const int shader_source_len = static_cast(src.size()); glslang::TShader shader(glslangType); shader.setStringsWithLengthsAndNames(&shader_source, &shader_source_len, file_name_list, 1); shader.setEntryPoint(entrypoint.c_str()); shader.setSourceEntryPoint(entrypoint.c_str()); shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_4); DirStackFileIncluder includeDir; includeDir.pushExternalLocalDirectory(path.parent_path().generic_string()); for (const fs::path& dir : includeDirs) { includeDir.pushExternalLocalDirectory(dir.generic_string()); } if (!shader.parse(GetDefaultResources(), 100, false, messages, includeDir)) { return fail("GLSL parse failed", std::string(shader.getInfoLog()) + shader.getInfoDebugLog()); } glslang::TProgram program; program.addShader(&shader); if (!program.link(messages)) { return fail("GLSL link failed", std::string(program.getInfoLog()) + program.getInfoDebugLog()); } glslang::TIntermediate* intermediate = program.getIntermediate(glslangType); if (!intermediate) { // Defensive: parse+link succeeded above, so this should be // unreachable. If glslang ever changes that contract we'd // rather surface a clear error than dereference null (the // pre-fix bug that masqueraded as a silent SIGSEGV). return fail("glslang produced no intermediate code", {}); } spv::SpvBuildLogger logger; std::vector spirv; glslang::GlslangToSpv(*intermediate, spirv, &logger); std::string spvLog = logger.getAllMessages(); fs::path filename = path.filename().replace_extension("spv"); std::ofstream file(outputDir/filename, std::ios::binary); if (!file) { return fail("failed to open SPIR-V output", (outputDir/filename).string()); } file.write(reinterpret_cast(spirv.data()), spirv.size() * sizeof(std::uint32_t)); if (!spvLog.empty()) { return fail("SPIR-V codegen reported issues", std::move(spvLog)); } glslang::FinalizeProcess(); return {}; } }