#include "slang-compile.h" #include "Containers/Array.h" #include "Graphics/Descriptor.h" #include #include #include #include using namespace Seele; #define CHECK_RESULT(x) \ { \ SlangResult r = x; \ if (r != 0) { \ throw std::runtime_error(fmt::format("Error: {0}", r)); \ } \ } #define CHECK_DIAGNOSTICS() \ { \ if (diagnostics) { \ std::cout << (const char*)diagnostics->getBufferPointer() << std::endl; \ abort(); \ } \ } thread_local Slang::ComPtr globalSession; thread_local Slang::ComPtr specializedComponent; thread_local Slang::ComPtr session; thread_local Array entryPoints; namespace { std::filesystem::path getExecutableDirectory() { std::error_code error; auto exePath = std::filesystem::read_symlink("/proc/self/exe", error); if (!error) { return exePath.parent_path(); } return std::filesystem::current_path(); } std::filesystem::path findExistingDirectory(std::initializer_list candidates) { for (const auto& candidate : candidates) { std::error_code error; if (std::filesystem::exists(candidate, error) && std::filesystem::is_directory(candidate, error)) { return candidate; } } return {}; } bool containsDownstreamCompilerArtifacts(const std::filesystem::path& directory) { std::error_code error; if (!std::filesystem::exists(directory, error) || !std::filesystem::is_directory(directory, error)) { return false; } for (const auto& entry : std::filesystem::directory_iterator(directory, error)) { if (error || !entry.is_regular_file(error)) { continue; } const auto fileName = entry.path().filename().string(); if (fileName.rfind("slang-glslang", 0) == 0 || fileName.rfind("spirv-opt", 0) == 0 || fileName.rfind("spirv-dis", 0) == 0) { return true; } } return false; } bool hasArtifactWithPrefix(const std::filesystem::path& directory, const char* prefix) { std::error_code error; if (!std::filesystem::exists(directory, error) || !std::filesystem::is_directory(directory, error)) { return false; } for (const auto& entry : std::filesystem::directory_iterator(directory, error)) { if (error || !entry.is_regular_file(error)) { continue; } if (entry.path().filename().string().rfind(prefix, 0) == 0) { return true; } } return false; } std::filesystem::path findDownstreamCompilerDirectory(std::initializer_list candidates) { for (const auto& candidate : candidates) { if (containsDownstreamCompilerArtifacts(candidate)) { return candidate; } } return findExistingDirectory(candidates); } void configureDownstreamCompilers(slang::IGlobalSession* session) { const auto executableDirectory = getExecutableDirectory(); const auto currentDirectory = std::filesystem::current_path(); const auto glslangDirectory = findDownstreamCompilerDirectory({ executableDirectory, executableDirectory / "Seele", executableDirectory / "vcpkg_installed" / "x64-linux" / "lib", currentDirectory / "build", currentDirectory / "build" / "Seele", currentDirectory / "Seele", currentDirectory / "vcpkg_installed" / "x64-linux" / "lib", }); if (!glslangDirectory.empty()) { const std::string path = glslangDirectory.string(); if (hasArtifactWithPrefix(glslangDirectory, "slang-glslang") || hasArtifactWithPrefix(glslangDirectory, "libslang-glslang")) { session->setDownstreamCompilerPath(SLANG_PASS_THROUGH_GLSLANG, path.c_str()); } if (hasArtifactWithPrefix(glslangDirectory, "spirv-opt")) { session->setDownstreamCompilerPath(SLANG_PASS_THROUGH_SPIRV_OPT, path.c_str()); } if (hasArtifactWithPrefix(glslangDirectory, "spirv-dis")) { session->setDownstreamCompilerPath(SLANG_PASS_THROUGH_SPIRV_DIS, path.c_str()); } } // Avoid optional SPIR-V downstream tools that are often unavailable in RenderDoc launch environments. session->setDefaultDownstreamCompiler(SLANG_SOURCE_LANGUAGE_SPIRV, SLANG_PASS_THROUGH_NONE); session->setDownstreamCompilerForTransition(SLANG_SPIRV, SLANG_SPIRV, SLANG_PASS_THROUGH_NONE); session->setDownstreamCompilerForTransition(SLANG_SPIRV, SLANG_SPIRV_ASM, SLANG_PASS_THROUGH_NONE); } } void Seele::beginCompilation(const ShaderCompilationInfo& info, SlangCompileTarget target, Gfx::PPipelineLayout layout) { if (!globalSession) { SlangGlobalSessionDesc sessionDesc = {}; sessionDesc.enableGLSL = true; slang::createGlobalSession(&sessionDesc, globalSession.writeRef()); configureDownstreamCompilers(globalSession); } slang::SessionDesc sessionDesc; sessionDesc.flags = 0; Array option = { { .name = slang::CompilerOptionName::DumpIntermediates, .value = { .kind = slang::CompilerOptionValueKind::Int, .intValue0 = info.dumpIntermediate, }, }, { .name = slang::CompilerOptionName::Optimization, .value = { .kind = slang::CompilerOptionValueKind::Int, .intValue0 = SLANG_OPTIMIZATION_LEVEL_NONE, }, }, { .name = slang::CompilerOptionName::SkipSPIRVValidation, .value = { .kind = slang::CompilerOptionValueKind::Int, .intValue0 = 1, }, }, { .name = slang::CompilerOptionName::SkipDownstreamLinking, .value = { .kind = slang::CompilerOptionValueKind::Int, .intValue0 = 1, }, }, }; sessionDesc.compilerOptionEntries = option.data(); sessionDesc.compilerOptionEntryCount = (uint32)option.size(); sessionDesc.defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_COLUMN_MAJOR; Array macros; for (const auto& [key, val] : info.defines) { macros.add(slang::PreprocessorMacroDesc{ .name = key, .value = val, }); } sessionDesc.preprocessorMacroCount = macros.size(); sessionDesc.preprocessorMacros = macros.data(); slang::TargetDesc targetDesc = {}; targetDesc.format = target; sessionDesc.targetCount = 1; sessionDesc.targets = &targetDesc; StaticArray searchPaths = {"shaders/", "shaders/lib/", "shaders/raytracing/", "shaders/terrain", "shaders/game", "shaders/generated/", "shaders/fluid"}; sessionDesc.searchPaths = searchPaths.data(); sessionDesc.searchPathCount = searchPaths.size(); Slang::ComPtr diagnostics; CHECK_RESULT(globalSession->createSession(sessionDesc, session.writeRef())); Array components; Map moduleMap; for (const auto& moduleName : info.modules) { slang::IModule* loaded = session->loadModule(moduleName.c_str(), diagnostics.writeRef()); CHECK_DIAGNOSTICS(); components.add(loaded); moduleMap[moduleName] = loaded; } entryPoints.clear(); for (const auto& [name, mod] : info.entryPoints) { entryPoints.add(name); slang::IEntryPoint* entry; CHECK_RESULT(moduleMap[mod]->findEntryPointByName(name.c_str(), &entry)); components.add(entry); } Slang::ComPtr moduleComposition; session->createCompositeComponentType(components.data(), components.size(), moduleComposition.writeRef(), diagnostics.writeRef()); CHECK_DIAGNOSTICS(); Slang::ComPtr linkedProgram; moduleComposition->link(linkedProgram.writeRef(), diagnostics.writeRef()); CHECK_DIAGNOSTICS(); slang::ProgramLayout* reflection = linkedProgram->getLayout(0, diagnostics.writeRef()); CHECK_DIAGNOSTICS(); Array specialization; for (const auto& [key, value] : info.typeParameter) { specialization.add(slang::SpecializationArg::fromType(reflection->findTypeByName(value))); } linkedProgram->specialize(specialization.data(), specialization.size(), specializedComponent.writeRef(), diagnostics.writeRef()); CHECK_DIAGNOSTICS(); slang::ProgramLayout* signature = specializedComponent->getLayout(0, diagnostics.writeRef()); CHECK_DIAGNOSTICS(); for (uint32 i = 0; i < signature->getParameterCount(); ++i) { auto param = signature->getParameterByIndex(i); layout->addMapping(param->getName(), param->getBindingIndex()); } } Pair, std::string> Seele::generateShader(const ShaderCreateInfo& createInfo) { Slang::ComPtr diagnostics; Slang::ComPtr kernelBlob; specializedComponent->getEntryPointCode(createInfo.entryPointIndex, 0, kernelBlob.writeRef(), diagnostics.writeRef()); CHECK_DIAGNOSTICS(); return {kernelBlob, entryPoints[createInfo.entryPointIndex]}; }