diff --git a/.gitmodules b/.gitmodules index 983b753744e8767da0ec3c959c32a3766ee346f6..62938a4b1ff2c6787b619cc2c18ef91cb0f0f679 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,4 +15,7 @@ url = https://github.com/nothings/stb.git [submodule "modules/camera/lib/glm"] path = modules/camera/lib/glm - url = https://github.com/g-truc/glm.git \ No newline at end of file + url = https://github.com/g-truc/glm.git +[submodule "modules/shader_compiler/lib/glslang"] + path = modules/shader_compiler/lib/glslang + url = https://github.com/KhronosGroup/glslang.git diff --git a/include/vkcv/ShaderProgram.hpp b/include/vkcv/ShaderProgram.hpp index ce28cccf07e22dda21fd14d0bddd0ba6e9842328..99de20d872158881f43cabc6fc3450afcb799e12 100644 --- a/include/vkcv/ShaderProgram.hpp +++ b/include/vkcv/ShaderProgram.hpp @@ -53,7 +53,7 @@ namespace vkcv { const VertexLayout &getVertexLayout() const; size_t getPushConstantSize() const; - const std::vector<std::vector<DescriptorBinding>> getReflectedDescriptors() const; + const std::vector<std::vector<DescriptorBinding>>& getReflectedDescriptors() const; private: std::unordered_map<ShaderStage, Shader> m_Shaders; diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index f29ff2fc86c88aa8bae2560f199d3882c9919b65..e8efea4981da3ffb338d508431ed4f92805ed5cd 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -2,4 +2,5 @@ # Add new modules here: add_subdirectory(asset_loader) add_subdirectory(camera) +add_subdirectory(shader_compiler) add_subdirectory(testing) diff --git a/modules/shader_compiler/CMakeLists.txt b/modules/shader_compiler/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..4b674ec41ed4ea5f42dc73187c212e6a69952cec --- /dev/null +++ b/modules/shader_compiler/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.16) +project(vkcv_shader_compiler) + +# setting c++ standard for the module +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(vkcv_shader_compiler_source ${PROJECT_SOURCE_DIR}/src) +set(vkcv_shader_compiler_include ${PROJECT_SOURCE_DIR}/include) + +# Add source and header files to the module +set(vkcv_shader_compiler_sources + ${vkcv_shader_compiler_include}/vkcv/shader/GLSLCompiler.hpp + ${vkcv_shader_compiler_source}/vkcv/shader/GLSLCompiler.cpp +) + +# adding source files to the module +add_library(vkcv_shader_compiler STATIC ${vkcv_shader_compiler_sources}) + +# Setup some path variables to load libraries +set(vkcv_shader_compiler_lib lib) +set(vkcv_shader_compiler_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_shader_compiler_lib}) + +# Check and load GLSLANG +include(config/GLSLANG.cmake) + +# link the required libraries to the module +target_link_libraries(vkcv_shader_compiler ${vkcv_shader_compiler_libraries} vkcv) + +# including headers of dependencies and the VkCV framework +target_include_directories(vkcv_shader_compiler SYSTEM BEFORE PRIVATE ${vkcv_shader_compiler_includes} ${vkcv_include}) + +# add the own include directory for public headers +target_include_directories(vkcv_shader_compiler BEFORE PUBLIC ${vkcv_shader_compiler_include}) diff --git a/modules/shader_compiler/config/GLSLANG.cmake b/modules/shader_compiler/config/GLSLANG.cmake new file mode 100644 index 0000000000000000000000000000000000000000..b592c61d07af67733f6048cf24aeb0a746d1f7be --- /dev/null +++ b/modules/shader_compiler/config/GLSLANG.cmake @@ -0,0 +1,25 @@ + +if (EXISTS "${vkcv_shader_compiler_lib_path}/glslang") + set(SKIP_GLSLANG_INSTALL ON CACHE INTERNAL "") + set(ENABLE_SPVREMAPPER OFF CACHE INTERNAL "") + set(ENABLE_GLSLANG_BINARIES OFF CACHE INTERNAL "") + set(ENABLE_GLSLANG_JS OFF CACHE INTERNAL "") + set(ENABLE_GLSLANG_WEBMIN OFF CACHE INTERNAL "") + set(ENABLE_GLSLANG_WEBMIN_DEVEL OFF CACHE INTERNAL "") + set(ENABLE_EMSCRIPTEN_SINGLE_FILE OFF CACHE INTERNAL "") + set(ENABLE_EMSCRIPTEN_ENVIRONMENT_NODE OFF CACHE INTERNAL "") + set(ENABLE_HLSL OFF CACHE INTERNAL "") + set(ENABLE_RTTI OFF CACHE INTERNAL "") + set(ENABLE_EXCEPTIONS OFF CACHE INTERNAL "") + set(ENABLE_OPT OFF CACHE INTERNAL "") + set(ENABLE_PCH OFF CACHE INTERNAL "") + set(ENABLE_CTEST OFF CACHE INTERNAL "") + set(USE_CCACHE OFF CACHE INTERNAL "") + + add_subdirectory(${vkcv_shader_compiler_lib}/glslang) + + list(APPEND vkcv_shader_compiler_libraries glslang SPIRV) + list(APPEND vkcv_shader_compiler_includes ${vkcv_shader_compiler_lib}) +else() + message(WARNING "GLSLANG is required..! Update the submodules!") +endif () diff --git a/modules/shader_compiler/include/vkcv/shader/Compiler.hpp b/modules/shader_compiler/include/vkcv/shader/Compiler.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d7b7af7178531aea358cecbc8b86a29527173014 --- /dev/null +++ b/modules/shader_compiler/include/vkcv/shader/Compiler.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include <vkcv/Event.hpp> + +namespace vkcv::shader { + + typedef typename event_function<ShaderStage, const std::filesystem::path&>::type ShaderCompiledFunction; + + class Compiler { + private: + public: + virtual void compile(ShaderStage shaderStage, const std::filesystem::path& shaderPath, + const ShaderCompiledFunction& compiled, bool update = false) = 0; + + }; + +} diff --git a/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp b/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7105d93a0c3e153bf3abe1d624d0c13c6f09ac6d --- /dev/null +++ b/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include <filesystem> + +#include <vkcv/ShaderStage.hpp> +#include "Compiler.hpp" + +namespace vkcv::shader { + + class GLSLCompiler { + private: + public: + GLSLCompiler(); + + GLSLCompiler(const GLSLCompiler& other); + GLSLCompiler(GLSLCompiler&& other) = default; + + ~GLSLCompiler(); + + GLSLCompiler& operator=(const GLSLCompiler& other); + GLSLCompiler& operator=(GLSLCompiler&& other) = default; + + void compile(ShaderStage shaderStage, const std::filesystem::path& shaderPath, + const ShaderCompiledFunction& compiled, bool update = false); + + }; + +} diff --git a/modules/shader_compiler/lib/glslang b/modules/shader_compiler/lib/glslang new file mode 160000 index 0000000000000000000000000000000000000000..fe15158676657bf965e41c32e15ae5db7ea2ab6a --- /dev/null +++ b/modules/shader_compiler/lib/glslang @@ -0,0 +1 @@ +Subproject commit fe15158676657bf965e41c32e15ae5db7ea2ab6a diff --git a/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4fcddb3125de2322061957a08d4f1dd52928c0e6 --- /dev/null +++ b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp @@ -0,0 +1,261 @@ + +#include "vkcv/shader/GLSLCompiler.hpp" + +#include <fstream> +#include <glslang/SPIRV/GlslangToSpv.h> + +#include <vkcv/Logger.hpp> + +namespace vkcv::shader { + + static uint32_t s_CompilerCount = 0; + + GLSLCompiler::GLSLCompiler() { + if (s_CompilerCount == 0) { + glslang::InitializeProcess(); + } + + s_CompilerCount++; + } + + GLSLCompiler::GLSLCompiler(const GLSLCompiler &other) { + s_CompilerCount++; + } + + GLSLCompiler::~GLSLCompiler() { + s_CompilerCount--; + + if (s_CompilerCount == 0) { + glslang::FinalizeProcess(); + } + } + + GLSLCompiler &GLSLCompiler::operator=(const GLSLCompiler &other) { + s_CompilerCount++; + return *this; + } + + constexpr EShLanguage findShaderLanguage(ShaderStage shaderStage) { + switch (shaderStage) { + case ShaderStage::VERTEX: + return EShLangVertex; + case ShaderStage::TESS_CONTROL: + return EShLangTessControl; + case ShaderStage::TESS_EVAL: + return EShLangTessEvaluation; + case ShaderStage::GEOMETRY: + return EShLangGeometry; + case ShaderStage::FRAGMENT: + return EShLangFragment; + case ShaderStage::COMPUTE: + return EShLangCompute; + default: + return EShLangCount; + } + } + + static void initResources(TBuiltInResource& resources) { + resources.maxLights = 32; + resources.maxClipPlanes = 6; + resources.maxTextureUnits = 32; + resources.maxTextureCoords = 32; + resources.maxVertexAttribs = 64; + resources.maxVertexUniformComponents = 4096; + resources.maxVaryingFloats = 64; + resources.maxVertexTextureImageUnits = 32; + resources.maxCombinedTextureImageUnits = 80; + resources.maxTextureImageUnits = 32; + resources.maxFragmentUniformComponents = 4096; + resources.maxDrawBuffers = 32; + resources.maxVertexUniformVectors = 128; + resources.maxVaryingVectors = 8; + resources.maxFragmentUniformVectors = 16; + resources.maxVertexOutputVectors = 16; + resources.maxFragmentInputVectors = 15; + resources.minProgramTexelOffset = -8; + resources.maxProgramTexelOffset = 7; + resources.maxClipDistances = 8; + resources.maxComputeWorkGroupCountX = 65535; + resources.maxComputeWorkGroupCountY = 65535; + resources.maxComputeWorkGroupCountZ = 65535; + resources.maxComputeWorkGroupSizeX = 1024; + resources.maxComputeWorkGroupSizeY = 1024; + resources.maxComputeWorkGroupSizeZ = 64; + resources.maxComputeUniformComponents = 1024; + resources.maxComputeTextureImageUnits = 16; + resources.maxComputeImageUniforms = 8; + resources.maxComputeAtomicCounters = 8; + resources.maxComputeAtomicCounterBuffers = 1; + resources.maxVaryingComponents = 60; + resources.maxVertexOutputComponents = 64; + resources.maxGeometryInputComponents = 64; + resources.maxGeometryOutputComponents = 128; + resources.maxFragmentInputComponents = 128; + resources.maxImageUnits = 8; + resources.maxCombinedImageUnitsAndFragmentOutputs = 8; + resources.maxCombinedShaderOutputResources = 8; + resources.maxImageSamples = 0; + resources.maxVertexImageUniforms = 0; + resources.maxTessControlImageUniforms = 0; + resources.maxTessEvaluationImageUniforms = 0; + resources.maxGeometryImageUniforms = 0; + resources.maxFragmentImageUniforms = 8; + resources.maxCombinedImageUniforms = 8; + resources.maxGeometryTextureImageUnits = 16; + resources.maxGeometryOutputVertices = 256; + resources.maxGeometryTotalOutputComponents = 1024; + resources.maxGeometryUniformComponents = 1024; + resources.maxGeometryVaryingComponents = 64; + resources.maxTessControlInputComponents = 128; + resources.maxTessControlOutputComponents = 128; + resources.maxTessControlTextureImageUnits = 16; + resources.maxTessControlUniformComponents = 1024; + resources.maxTessControlTotalOutputComponents = 4096; + resources.maxTessEvaluationInputComponents = 128; + resources.maxTessEvaluationOutputComponents = 128; + resources.maxTessEvaluationTextureImageUnits = 16; + resources.maxTessEvaluationUniformComponents = 1024; + resources.maxTessPatchComponents = 120; + resources.maxPatchVertices = 32; + resources.maxTessGenLevel = 64; + resources.maxViewports = 16; + resources.maxVertexAtomicCounters = 0; + resources.maxTessControlAtomicCounters = 0; + resources.maxTessEvaluationAtomicCounters = 0; + resources.maxGeometryAtomicCounters = 0; + resources.maxFragmentAtomicCounters = 8; + resources.maxCombinedAtomicCounters = 8; + resources.maxAtomicCounterBindings = 1; + resources.maxVertexAtomicCounterBuffers = 0; + resources.maxTessControlAtomicCounterBuffers = 0; + resources.maxTessEvaluationAtomicCounterBuffers = 0; + resources.maxGeometryAtomicCounterBuffers = 0; + resources.maxFragmentAtomicCounterBuffers = 1; + resources.maxCombinedAtomicCounterBuffers = 1; + resources.maxAtomicCounterBufferSize = 16384; + resources.maxTransformFeedbackBuffers = 4; + resources.maxTransformFeedbackInterleavedComponents = 64; + resources.maxCullDistances = 8; + resources.maxCombinedClipAndCullDistances = 8; + resources.maxSamples = 4; + resources.maxMeshOutputVerticesNV = 256; + resources.maxMeshOutputPrimitivesNV = 512; + resources.maxMeshWorkGroupSizeX_NV = 32; + resources.maxMeshWorkGroupSizeY_NV = 1; + resources.maxMeshWorkGroupSizeZ_NV = 1; + resources.maxTaskWorkGroupSizeX_NV = 32; + resources.maxTaskWorkGroupSizeY_NV = 1; + resources.maxTaskWorkGroupSizeZ_NV = 1; + resources.maxMeshViewCountNV = 4; + resources.limits.nonInductiveForLoops = 1; + resources.limits.whileLoops = 1; + resources.limits.doWhileLoops = 1; + resources.limits.generalUniformIndexing = 1; + resources.limits.generalAttributeMatrixVectorIndexing = 1; + resources.limits.generalVaryingIndexing = 1; + resources.limits.generalSamplerIndexing = 1; + resources.limits.generalVariableIndexing = 1; + resources.limits.generalConstantMatrixVectorIndexing = 1; + } + + static std::vector<char> readShaderCode(const std::filesystem::path &shaderPath) { + std::ifstream file (shaderPath.string(), std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + vkcv_log(LogLevel::ERROR, "The file could not be opened"); + return std::vector<char>{}; + } + + std::streamsize fileSize = file.tellg(); + std::vector<char> buffer (fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + file.close(); + + return buffer; + } + + static bool writeSpirvCode(const std::filesystem::path &shaderPath, const std::vector<uint32_t>& spirv) { + std::ofstream file (shaderPath.string(), std::ios::out); + + if (!file.is_open()) { + vkcv_log(LogLevel::ERROR, "The file could not be opened"); + return false; + } + + std::streamsize fileSize = static_cast<std::streamsize>(spirv.size()) * sizeof(uint32_t); + + file.seekp(0); + file.write(reinterpret_cast<const char*>(spirv.data()), fileSize); + file.close(); + + return true; + } + + void GLSLCompiler::compile(ShaderStage shaderStage, const std::filesystem::path &shaderPath, + const ShaderCompiledFunction& compiled, bool update) { + const EShLanguage language = findShaderLanguage(shaderStage); + + if (language == EShLangCount) { + vkcv_log(LogLevel::ERROR, "Shader stage not supported"); + return; + } + + const std::vector<char> code = readShaderCode(shaderPath); + + glslang::TShader shader (language); + glslang::TProgram program; + + const char *shaderStrings [1]; + shaderStrings[0] = code.data(); + + shader.setStrings(shaderStrings, 1); + + TBuiltInResource resources = {}; + initResources(resources); + + const auto messages = (EShMessages) ( + EShMsgSpvRules | + EShMsgVulkanRules + ); + + if (!shader.parse(&resources, 100, false, messages)) { + vkcv_log(LogLevel::ERROR, "Shader parsing failed {\n%s\n%s\n}", + shader.getInfoLog(), shader.getInfoDebugLog()); + return; + } + + program.addShader(&shader); + + if (!program.link(messages)) { + vkcv_log(LogLevel::ERROR, "Shader linking failed {\n%s\n%s\n}", + shader.getInfoLog(), shader.getInfoDebugLog()); + return; + } + + const glslang::TIntermediate* intermediate = program.getIntermediate(language); + + if (!intermediate) { + vkcv_log(LogLevel::ERROR, "No valid intermediate representation"); + return; + } + + std::vector<uint32_t> spirv; + glslang::GlslangToSpv(*intermediate, spirv); + + const std::filesystem::path tmp_path (std::tmpnam(nullptr)); + + if (!writeSpirvCode(tmp_path, spirv)) { + vkcv_log(LogLevel::ERROR, "Spir-V could not be written to disk"); + return; + } + + if (compiled) { + compiled(shaderStage, tmp_path); + } + + std::filesystem::remove(tmp_path); + } + +} diff --git a/projects/first_triangle/CMakeLists.txt b/projects/first_triangle/CMakeLists.txt index e7c8373e085df6497060b8d1d8164cf740dfb01f..7e606b2348ea82486c2a57ee1062ef34150e46a0 100644 --- a/projects/first_triangle/CMakeLists.txt +++ b/projects/first_triangle/CMakeLists.txt @@ -22,7 +22,7 @@ if(MSVC) endif() # including headers of dependencies and the VkCV framework -target_include_directories(first_triangle SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_camera_include}) +target_include_directories(first_triangle SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include}) # linking with libraries from all dependencies and the VkCV framework -target_link_libraries(first_triangle vkcv vkcv_testing vkcv_camera) +target_link_libraries(first_triangle vkcv vkcv_testing vkcv_camera vkcv_shader_compiler) diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp index 2ede653ff98e19159e0155b282cab1b309a13816..1ad4901088021e83662a534cf326400f9e83e2b6 100644 --- a/projects/first_triangle/src/main.cpp +++ b/projects/first_triangle/src/main.cpp @@ -4,6 +4,8 @@ #include <vkcv/camera/CameraManager.hpp> #include <chrono> +#include <vkcv/shader/GLSLCompiler.hpp> + int main(int argc, const char** argv) { const char* applicationName = "First Triangle"; @@ -91,12 +93,26 @@ int main(int argc, const char** argv) { std::cout << "Error. Could not create renderpass. Exiting." << std::endl; return EXIT_FAILURE; } - + vkcv::ShaderProgram triangleShaderProgram{}; - triangleShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("shaders/vert.spv")); - triangleShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("shaders/frag.spv")); - triangleShaderProgram.reflectShader(vkcv::ShaderStage::VERTEX); - triangleShaderProgram.reflectShader(vkcv::ShaderStage::FRAGMENT); + vkcv::shader::GLSLCompiler compiler; + + compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("shaders/shader.vert"), + [&triangleShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + triangleShaderProgram.addShader(shaderStage, path); + triangleShaderProgram.reflectShader(shaderStage); + }); + + compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("shaders/shader.frag"), + [&triangleShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + triangleShaderProgram.addShader(shaderStage, path); + triangleShaderProgram.reflectShader(shaderStage); + }); + + //triangleShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("shaders/vert.spv")); + //triangleShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("shaders/frag.spv")); + //triangleShaderProgram.reflectShader(vkcv::ShaderStage::VERTEX); + //triangleShaderProgram.reflectShader(vkcv::ShaderStage::FRAGMENT); const vkcv::PipelineConfig trianglePipelineDefinition( triangleShaderProgram, diff --git a/src/vkcv/ShaderProgram.cpp b/src/vkcv/ShaderProgram.cpp index 7c54c301d1301127273303128b0c10a9c2c53942..74383476fb3b9364c86d1cef6ac86aaf0c0cb08b 100644 --- a/src/vkcv/ShaderProgram.cpp +++ b/src/vkcv/ShaderProgram.cpp @@ -14,17 +14,21 @@ namespace vkcv { * @param[in] relative path to the shader code * @return vector of chars as a buffer for the code */ - std::vector<char> readShaderCode(const std::filesystem::path &shaderPath) - { - std::ifstream file(shaderPath.string(), std::ios::ate | std::ios::binary); + std::vector<char> readShaderCode(const std::filesystem::path &shaderPath) { + std::ifstream file (shaderPath.string(), std::ios::ate | std::ios::binary); + if (!file.is_open()) { vkcv_log(LogLevel::ERROR, "The file could not be opened"); return std::vector<char>{}; } + size_t fileSize = (size_t)file.tellg(); std::vector<char> buffer(fileSize); + file.seekg(0); file.read(buffer.data(), fileSize); + file.close(); + return buffer; } @@ -205,7 +209,7 @@ namespace vkcv { return m_VertexLayout; } - const std::vector<std::vector<DescriptorBinding>> ShaderProgram::getReflectedDescriptors() const { + const std::vector<std::vector<DescriptorBinding>>& ShaderProgram::getReflectedDescriptors() const { return m_DescriptorSets; } size_t ShaderProgram::getPushConstantSize() const {