diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c49c7c..3a855ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,9 +48,9 @@ jobs: cmake --build cmake-build-release --config Release binary: cmake-build-release\bin\cae.exe build_test: | - cmake -S . -G "Ninja" -B cmake-build-tests -DBUILD_CAE_TESTS=ON - cmake --build cmake-build-tests - binary_test: cmake-build-tests\bin\cae-tests.exe + cmake -S . -G "Ninja" -B cmake-build-release -DCMAKE_BUILD_TYPE=Release -DBUILD_CAE_TESTS=ON + cmake --build cmake-build-release --config Release + binary_test: cmake-build-release\bin\cae-tests.exe deps: choco install cmake -y runs-on: ${{ matrix.os }} diff --git a/assets/shaders/animated.frag b/assets/shaders/animated.frag new file mode 100644 index 0000000..1abef88 --- /dev/null +++ b/assets/shaders/animated.frag @@ -0,0 +1,15 @@ +#version 450 core + +layout(location = 0) out vec4 FragColor; + +layout(binding = 1) uniform TimeColor { + float time; +} uTime; + +void main() +{ + float r = 0.5 + 0.5 * sin(uTime.time); + float g = 0.5 + 0.5 * sin(uTime.time * 1.3); + float b = 0.5 + 0.5 * sin(uTime.time * 2.1); + FragColor = vec4(r, g, b, 1.0); +} diff --git a/assets/shaders/animated.vert b/assets/shaders/animated.vert new file mode 100644 index 0000000..a752dd6 --- /dev/null +++ b/assets/shaders/animated.vert @@ -0,0 +1,13 @@ +#version 450 core + +layout(location = 0) in vec2 aPos; + +layout(binding = 0) uniform TimeData { + float time; +} uTime; + +void main() +{ + float scale = 0.5 + 0.5 * sin(uTime.time); + gl_Position = vec4(aPos * scale, 0.0, 1.0); +} diff --git a/assets/shaders/basic_color.frag b/assets/shaders/basic_color.frag new file mode 100644 index 0000000..ffb6c38 --- /dev/null +++ b/assets/shaders/basic_color.frag @@ -0,0 +1,9 @@ +#version 450 core + +layout(location = 0) in vec3 vColor; +layout(location = 0) out vec4 FragColor; + +void main() +{ + FragColor = vec4(vColor, 1.0); +} diff --git a/assets/shaders/basic_color.vert b/assets/shaders/basic_color.vert new file mode 100644 index 0000000..0e1fcec --- /dev/null +++ b/assets/shaders/basic_color.vert @@ -0,0 +1,12 @@ +#version 450 core + +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec3 aColor; + +layout(location = 0) out vec3 vColor; + +void main() +{ + gl_Position = vec4(aPos, 0.0, 1.0); + vColor = aColor; +} diff --git a/assets/shaders/texture.frag b/assets/shaders/texture.frag new file mode 100644 index 0000000..398f485 --- /dev/null +++ b/assets/shaders/texture.frag @@ -0,0 +1,12 @@ +#version 450 core + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 FragColor; + +layout(binding = 0) uniform sampler2D uTexture; + +void main() +{ + // Même sans texture bindée, ça se compile. + FragColor = texture(uTexture, vUV); +} diff --git a/assets/shaders/texture.vert b/assets/shaders/texture.vert new file mode 100644 index 0000000..38120fc --- /dev/null +++ b/assets/shaders/texture.vert @@ -0,0 +1,12 @@ +#version 450 core + +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec2 aUV; + +layout(location = 0) out vec2 vUV; + +void main() +{ + gl_Position = vec4(aPos, 0.0, 1.0); + vUV = aUV; +} diff --git a/assets/shaders/transform.frag b/assets/shaders/transform.frag new file mode 100644 index 0000000..ffb6c38 --- /dev/null +++ b/assets/shaders/transform.frag @@ -0,0 +1,9 @@ +#version 450 core + +layout(location = 0) in vec3 vColor; +layout(location = 0) out vec4 FragColor; + +void main() +{ + FragColor = vec4(vColor, 1.0); +} diff --git a/assets/shaders/transform.vert b/assets/shaders/transform.vert new file mode 100644 index 0000000..0ba71b1 --- /dev/null +++ b/assets/shaders/transform.vert @@ -0,0 +1,18 @@ +#version 450 core + +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec3 aColor; + +layout(location = 0) out vec3 vColor; + +layout(binding = 0) uniform Matrices { + mat4 model; + mat4 view; + mat4 proj; +} uMVP; + +void main() +{ + gl_Position = uMVP.proj * uMVP.view * uMVP.model * vec4(aPos, 0.0, 1.0); + vColor = aColor; +} diff --git a/assets/shaders/uniform_color.frag b/assets/shaders/uniform_color.frag new file mode 100644 index 0000000..c2bb879 --- /dev/null +++ b/assets/shaders/uniform_color.frag @@ -0,0 +1,12 @@ +#version 450 core + +layout(location = 0) out vec4 FragColor; + +layout(binding = 0) uniform ColorData { + vec3 color; +} uColor; + +void main() +{ + FragColor = vec4(uColor.color, 1.0); +} diff --git a/assets/shaders/uniform_color.vert b/assets/shaders/uniform_color.vert new file mode 100644 index 0000000..52e73dc --- /dev/null +++ b/assets/shaders/uniform_color.vert @@ -0,0 +1,8 @@ +#version 450 core + +layout(location = 0) in vec2 aPos; + +void main() +{ + gl_Position = vec4(aPos, 0.0, 1.0); +} diff --git a/include/CAE/Application.hpp b/include/CAE/Application.hpp index 6f85d53..fff8b44 100644 --- a/include/CAE/Application.hpp +++ b/include/CAE/Application.hpp @@ -41,7 +41,7 @@ namespace cae void stop(); private: - void setupEngine(const std::string &rendererName, const std::string &windowName); + void setupEngine(const std::string &rendererName, const std::string &windowName, const std::string &shaderName); static EngineConfig parseEngineConf(const std::string &path); diff --git a/include/CAE/Engine/Engine.hpp b/include/CAE/Engine/Engine.hpp index eaf3818..442b834 100644 --- a/include/CAE/Engine/Engine.hpp +++ b/include/CAE/Engine/Engine.hpp @@ -49,6 +49,7 @@ namespace cae const std::function()> &inputFactory, const std::function()> &networkFactory, const std::function()> &rendererFactory, + const std::function()> &shaderFactory, const std::function()> &windowFactory); ~Engine() = default; @@ -73,6 +74,7 @@ namespace cae std::shared_ptr m_inputPlugin = nullptr; std::shared_ptr m_networkPlugin = nullptr; std::shared_ptr m_rendererPlugin = nullptr; + std::shared_ptr m_shaderPlugin = nullptr; std::shared_ptr m_windowPlugin = nullptr; std::unique_ptr m_clock = nullptr; diff --git a/modules/Interfaces/include/Interfaces/Input/Key/Keyboard.hpp b/modules/Interfaces/include/Interfaces/Input/Key/Keyboard.hpp index d495d26..8c9f8ea 100644 --- a/modules/Interfaces/include/Interfaces/Input/Key/Keyboard.hpp +++ b/modules/Interfaces/include/Interfaces/Input/Key/Keyboard.hpp @@ -11,7 +11,7 @@ namespace cae { - enum KeyState : std::uint8_t + enum class KeyState : std::uint8_t { Pressed = 0, Released = 1, diff --git a/modules/Interfaces/include/Interfaces/Renderer/ARenderer.hpp b/modules/Interfaces/include/Interfaces/Renderer/ARenderer.hpp new file mode 100644 index 0000000..bb9a56c --- /dev/null +++ b/modules/Interfaces/include/Interfaces/Renderer/ARenderer.hpp @@ -0,0 +1,39 @@ +/// +/// @file ARenderer.hpp +/// @brief This file contains the Renderer abstract class +/// @namespace cae +/// + +#pragma once + +#include "Interfaces/Renderer/IRenderer.hpp" + +#include + +namespace cae +{ + + /// + /// @interface ARenderer + /// @brief Abstract class for renderer + /// @namespace cae + /// + class ARenderer : public IRenderer + { + + public: + ~ARenderer() override = default; + + std::shared_ptr getShader() const override { return m_shader; } + std::shared_ptr getModel() const override { return m_model; } + + void setShader(const std::shared_ptr shader) override { m_shader = shader; } + void setModel(const std::shared_ptr model) override { m_model = model; } + + protected: + std::shared_ptr m_shader; + std::shared_ptr m_model; + + }; // interface ARenderer + +} // namespace cae diff --git a/modules/Interfaces/include/Interfaces/Renderer/IRenderer.hpp b/modules/Interfaces/include/Interfaces/Renderer/IRenderer.hpp index 0a92907..174dcbe 100644 --- a/modules/Interfaces/include/Interfaces/Renderer/IRenderer.hpp +++ b/modules/Interfaces/include/Interfaces/Renderer/IRenderer.hpp @@ -6,6 +6,8 @@ #pragma once +#include "Interfaces/Renderer/IModel.hpp" +#include "Interfaces/Renderer/IShader.hpp" #include "Interfaces/IWindow.hpp" namespace cae @@ -22,7 +24,15 @@ namespace cae public: ~IRenderer() override = default; - virtual void initialize(const NativeWindowHandle &nativeWindowHandle) = 0; + virtual std::shared_ptr getShader() const = 0; + virtual std::shared_ptr getModel() const = 0; + + virtual void setShader(std::shared_ptr shader) = 0; + virtual void setModel(std::shared_ptr model) = 0; + + virtual void initialize(const NativeWindowHandle &nativeWindowHandle, std::shared_ptr shader) = 0; + virtual void createPipeline(const ShaderPipelineDesc& pipeline) = 0; + virtual void draw(const WindowSize &windowSize) = 0; virtual void setVSyncEnabled(bool enabled) = 0; diff --git a/modules/Interfaces/include/Interfaces/Renderer/IShader.hpp b/modules/Interfaces/include/Interfaces/Renderer/IShader.hpp index 1670e6c..ed2ef8f 100644 --- a/modules/Interfaces/include/Interfaces/Renderer/IShader.hpp +++ b/modules/Interfaces/include/Interfaces/Renderer/IShader.hpp @@ -6,8 +6,54 @@ #pragma once +#include + #include "Utils/Interfaces/IPlugin.hpp" +using ShaderID = std::string; + +enum class ShaderSourceType : uint8_t +{ + GLSL, + HLSL, + SPIRV, + MSL, + WGSL, + UNDEFINED = 255 +}; + +enum class ShaderStage : uint8_t +{ + VERTEX, + FRAGMENT, + GEOMETRY, + COMPUTE, + UNDEFINED = 255 +}; + +struct ShaderModuleDesc +{ + // ShaderSourceType type; + ShaderID id; + std::string source; + ShaderStage stage; +}; + +struct ShaderPipelineDesc +{ + ShaderID id; + ShaderID vertex; + ShaderID fragment; +}; + +struct ShaderData { + ShaderSourceType type; + std::string source; // GLSL/HLSL/etc. pour debug/fallback + std::vector spirv; // SPIR-V compilé + ShaderStage stage; + std::string entryPoint = "main"; +}; + namespace cae { @@ -22,6 +68,11 @@ namespace cae public: ~IShader() override = default; + virtual void addShader(const ShaderModuleDesc& pipelineDesc) = 0; + virtual bool compileAll() = 0; + virtual const ShaderData& getShader(const ShaderID& name) const = 0; + virtual bool isCompiled(const ShaderID& name) const = 0; + }; // interface IShader } // namespace cae diff --git a/modules/utils/include/Utils/Interfaces/IPlugin.hpp b/modules/utils/include/Utils/Interfaces/IPlugin.hpp index 297206b..593563b 100644 --- a/modules/utils/include/Utils/Interfaces/IPlugin.hpp +++ b/modules/utils/include/Utils/Interfaces/IPlugin.hpp @@ -23,11 +23,12 @@ namespace utl AUDIO = 0, NETWORK = 1, RENDERER = 2, - WINDOW = 3, + SHADER = 3, + WINDOW = 4, UNDEFINED = 255, }; - enum PluginPlatform : uint8_t + enum class PluginPlatform : uint8_t { LINUX = 0, MACOSX = 1, diff --git a/modules/utils/include/Utils/Logger.hpp b/modules/utils/include/Utils/Logger.hpp index 44cf16b..31c38b8 100644 --- a/modules/utils/include/Utils/Logger.hpp +++ b/modules/utils/include/Utils/Logger.hpp @@ -28,6 +28,12 @@ namespace utl class Logger { + private: + template + static constexpr auto to_underlying(E e) noexcept { + return static_cast>(e); + } + public: Logger(const Logger &) = delete; Logger &operator=(const Logger &) = delete; @@ -45,22 +51,22 @@ namespace utl std::cout << getColorForDuration(duration) << formatLogMessage(LogLevel::INFO, message + " took " + std::to_string(duration) + " ms") - << LOG_LEVEL_COLOR[COLOR_RESET]; + << LOG_LEVEL_COLOR[to_underlying(ColorIndex::COLOR_RESET)]; } static void log(const std::string &message, const LogLevel &logLevel) { - std::cout << (logLevel == LogLevel::INFO ? LOG_LEVEL_COLOR[COLOR_INFO] : LOG_LEVEL_COLOR[COLOR_WARNING]) - << formatLogMessage(logLevel, message) << LOG_LEVEL_COLOR[COLOR_RESET]; + std::cout << (logLevel == LogLevel::INFO ? LOG_LEVEL_COLOR[to_underlying(ColorIndex::COLOR_INFO)] : LOG_LEVEL_COLOR[to_underlying(ColorIndex::COLOR_WARNING)]) + << formatLogMessage(logLevel, message) << LOG_LEVEL_COLOR[to_underlying(ColorIndex::COLOR_RESET)]; } private: - enum ColorIndex : uint8_t + enum class ColorIndex : uint8_t { - COLOR_ERROR, - COLOR_INFO, - COLOR_WARNING, - COLOR_RESET + COLOR_ERROR = 0, + COLOR_INFO = 1, + COLOR_WARNING = 2, + COLOR_RESET = 3 }; static constexpr std::array LOG_LEVEL_COLOR = { @@ -78,8 +84,8 @@ namespace utl [[nodiscard]] static const char *getColorForDuration(const float duration) { return duration < 20.0F - ? LOG_LEVEL_COLOR[COLOR_INFO] - : (duration < 90.0F ? LOG_LEVEL_COLOR[COLOR_WARNING] : LOG_LEVEL_COLOR[COLOR_ERROR]); + ? LOG_LEVEL_COLOR[to_underlying(ColorIndex::COLOR_INFO)] + : (duration < 90.0F ? LOG_LEVEL_COLOR[to_underlying(ColorIndex::COLOR_WARNING)] : LOG_LEVEL_COLOR[to_underlying(ColorIndex::COLOR_ERROR)]); } [[nodiscard]] static std::string formatLogMessage(LogLevel level, const std::string &message) diff --git a/modules/utils/include/Utils/Utils.hpp b/modules/utils/include/Utils/Utils.hpp index 35bf0cf..0731e6e 100644 --- a/modules/utils/include/Utils/Utils.hpp +++ b/modules/utils/include/Utils/Utils.hpp @@ -6,9 +6,11 @@ #pragma once +#include #include namespace utl { - [[nodiscard]] std::vector readFile(const std::string &filename); + [[nodiscard]] std::vector fileToVector(const std::filesystem::path& path); + [[nodiscard]] std::string fileToString(const std::filesystem::path& path); } // namespace utl diff --git a/modules/utils/src/utils.cpp b/modules/utils/src/utils.cpp index 61f5f50..8051675 100644 --- a/modules/utils/src/utils.cpp +++ b/modules/utils/src/utils.cpp @@ -1,24 +1,36 @@ #include +#include #include "Utils/Utils.hpp" -std::vector utl::readFile(const std::string &filename) +std::vector utl::fileToVector(const std::filesystem::path& path) { - std::ifstream file(filename, std::ios::binary | std::ios::ate); + std::ifstream file(path, std::ios::in); if (!file.is_open()) { - throw std::runtime_error("failed to open file " + filename); + throw std::runtime_error("failed to open file " + path.string()); } const long int fileSize = file.tellg(); if (fileSize <= 0) { - throw std::runtime_error("file " + filename + " is empty"); + throw std::runtime_error("file " + path.string() + " is empty"); } std::vector buffer(static_cast(fileSize)); file.seekg(0, std::ios::beg); if (!file.read(buffer.data(), fileSize)) { - throw std::runtime_error("failed to read file " + filename); + throw std::runtime_error("failed to read file " + path.string()); } return buffer; } + +std::string utl::fileToString(const std::filesystem::path& path) +{ + std::ifstream file(path, std::ios::in); + if (!file.is_open()) { + throw std::runtime_error("Failed to open file: " + path.string()); + } + std::stringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); +} diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 203979c..ef3b903 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -2,4 +2,5 @@ add_subdirectory(Audio) add_subdirectory(Input) add_subdirectory(Network) add_subdirectory(Renderer) +add_subdirectory(Shader) add_subdirectory(Window) diff --git a/plugins/Renderer/OpenGL/include/OPGL/OPGL.hpp b/plugins/Renderer/OpenGL/include/OPGL/OPGL.hpp index aa6c802..2bf4588 100644 --- a/plugins/Renderer/OpenGL/include/OPGL/OPGL.hpp +++ b/plugins/Renderer/OpenGL/include/OPGL/OPGL.hpp @@ -9,7 +9,9 @@ #include "OPGL/glad/glad.h" #include "Context/IContext.hpp" -#include "Interfaces/Renderer/IRenderer.hpp" +#include "Interfaces/Renderer/ARenderer.hpp" + +#include namespace cae { @@ -19,7 +21,7 @@ namespace cae /// @brief Class for the OpenGL plugin /// @namespace cae /// - class OPGL final : public IRenderer + class OPGL final : public ARenderer { public: OPGL() = default; @@ -34,7 +36,8 @@ namespace cae [[nodiscard]] utl::PluginType getType() const override { return utl::PluginType::RENDERER; } [[nodiscard]] utl::PluginPlatform getPlatform() const override { return utl::PluginPlatform::ALL; } - void initialize(const NativeWindowHandle &window) override; + void initialize(const NativeWindowHandle &window, std::shared_ptr shader) override; + void createPipeline(const ShaderPipelineDesc& pipeline) override; void draw(const WindowSize &windowSize) override; void setVSyncEnabled(bool enabled) override; @@ -42,12 +45,15 @@ namespace cae private: std::unique_ptr m_context; + std::shared_ptr m_shader; + + std::unordered_map m_programs; GLuint gVAO = 0; GLuint gVBO = 0; - GLuint gShaderProgram = 0; - void createShaderProgram(); + GLuint createProgramFromPipeline(const ShaderPipelineDesc& pipeline) const; + static GLuint createGLShader(GLenum type, const ShaderData& data); void createTriangle(); }; // class OPGL diff --git a/plugins/Renderer/OpenGL/src/opgl.cpp b/plugins/Renderer/OpenGL/src/opgl.cpp index 601bda7..afd9755 100644 --- a/plugins/Renderer/OpenGL/src/opgl.cpp +++ b/plugins/Renderer/OpenGL/src/opgl.cpp @@ -3,7 +3,7 @@ #include #include "OPGL/OPGL.hpp" - +#include "Utils/Utils.hpp" #ifdef __linux__ #include "OPGL/Context/EGLContextLinux.hpp" #elifdef _WIN32 @@ -12,23 +12,21 @@ #include "OPGL/Context/NSGLContextMac.hpp" #endif -void cae::OPGL::initialize(const NativeWindowHandle &window) +void cae::OPGL::initialize(const NativeWindowHandle &window, const std::shared_ptr shader) { + m_shader = shader; #ifdef __linux__ m_context = std::make_unique(); #elifdef _WIN32 m_context = std::make_unique(); #elifdef __APPLE__ m_context = std::make_unique(); -#else - static_assert(false, "Unsupported platform"); #endif + m_context->initialize(window); glEnable(GL_DEPTH_TEST); glClearColor(1.F, 1.F, 1.F, 1.F); - - createShaderProgram(); createTriangle(); } @@ -37,7 +35,7 @@ void cae::OPGL::draw(const WindowSize &windowSize) glViewport(0, 0, windowSize.width, windowSize.height); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glUseProgram(gShaderProgram); + glUseProgram(m_programs.at("basic")); glBindVertexArray(gVAO); glDrawArrays(GL_TRIANGLES, 0, 3); glBindVertexArray(0); @@ -49,54 +47,16 @@ void cae::OPGL::setVSyncEnabled(const bool enabled) { m_context->setVSyncEnabled bool cae::OPGL::isVSyncEnabled() const { return m_context->isVSyncEnabled(); } -void cae::OPGL::createShaderProgram() +void cae::OPGL::createPipeline(const ShaderPipelineDesc& pipeline) { - const auto *const vertexShaderSource = R"(#version 330 core - layout(location=0) in vec2 aPos; - layout(location=1) in vec3 aColor; - out vec3 ourColor; - void main() { gl_Position = vec4(aPos,0.0,1.0); ourColor = aColor; })"; - - const auto *const fragmentShaderSource = R"(#version 330 core - in vec3 ourColor; - out vec4 FragColor; - void main() { FragColor = vec4(ourColor,1.0); })"; - - auto compileShader = [](const GLenum type, const char *src) -> GLuint + if (!m_shader->isCompiled(pipeline.vertex) || + !m_shader->isCompiled(pipeline.fragment)) { - const GLuint shader = glCreateShader(type); - glShaderSource(shader, 1, &src, nullptr); - glCompileShader(shader); - GLint success = 0; - glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - if (!success) - { - char log[512]; - glGetShaderInfoLog(shader, 512, nullptr, log); - throw std::runtime_error(log); - } - return shader; - }; - - const GLuint vertex = compileShader(GL_VERTEX_SHADER, vertexShaderSource); - const GLuint fragment = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource); - - gShaderProgram = glCreateProgram(); - glAttachShader(gShaderProgram, vertex); - glAttachShader(gShaderProgram, fragment); - glLinkProgram(gShaderProgram); - - GLint success = 0; - glGetProgramiv(gShaderProgram, GL_LINK_STATUS, &success); - if (success == 0) - { - char log[512]; - glGetProgramInfoLog(gShaderProgram, 512, nullptr, log); - throw std::runtime_error(log); + throw std::runtime_error("Pipeline uses uncompiled shaders"); } - glDeleteShader(vertex); - glDeleteShader(fragment); + const GLuint program = createProgramFromPipeline(pipeline); + m_programs[pipeline.id] = program; } void cae::OPGL::createTriangle() @@ -119,3 +79,47 @@ void cae::OPGL::createTriangle() glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } + +GLuint cae::OPGL::createProgramFromPipeline(const ShaderPipelineDesc& pipeline) const +{ + const auto& vert = m_shader->getShader(pipeline.vertex); + const auto& frag = m_shader->getShader(pipeline.fragment); + + const GLuint program = glCreateProgram(); + + GLuint vs = createGLShader(GL_VERTEX_SHADER, vert); + GLuint fs = createGLShader(GL_FRAGMENT_SHADER, frag); + + glAttachShader(program, vs); + glAttachShader(program, fs); + glLinkProgram(program); + + GLint success = 0; + glGetProgramiv(program, GL_LINK_STATUS, &success); + if (success == 0) { + char log[512]; + glGetProgramInfoLog(program, 512, nullptr, log); + throw std::runtime_error(log); + } + + glDeleteShader(vs); + glDeleteShader(fs); + + return program; +} + +GLuint cae::OPGL::createGLShader(const GLenum type, const ShaderData& data) +{ + const GLuint shader = glCreateShader(type); + + glShaderBinary( + 1, &shader, + GL_SHADER_BINARY_FORMAT_SPIR_V, + data.spirv.data(), + static_cast(data.spirv.size() * sizeof(uint32_t)) + ); + + glSpecializeShader(shader, data.entryPoint.c_str(), 0, nullptr, nullptr); + + return shader; +} diff --git a/plugins/Renderer/Vulkan/include/VULKN/VULKN.hpp b/plugins/Renderer/Vulkan/include/VULKN/VULKN.hpp index cda878b..47fad56 100644 --- a/plugins/Renderer/Vulkan/include/VULKN/VULKN.hpp +++ b/plugins/Renderer/Vulkan/include/VULKN/VULKN.hpp @@ -6,7 +6,7 @@ #pragma once -#include "Interfaces/Renderer/IRenderer.hpp" +#include "Interfaces/Renderer/ARenderer.hpp" namespace cae { @@ -16,7 +16,7 @@ namespace cae /// @brief Class for the Vulkan plugin /// @namespace cae /// - class VULKN final : public IRenderer + class VULKN final : public ARenderer { public: @@ -32,7 +32,8 @@ namespace cae [[nodiscard]] utl::PluginType getType() const override { return utl::PluginType::RENDERER; } [[nodiscard]] utl::PluginPlatform getPlatform() const override { return utl::PluginPlatform::ALL; } - void initialize(const NativeWindowHandle &nativeWindowHandle) override {} + void initialize(const NativeWindowHandle &nativeWindowHandle, std::shared_ptr shader) override {} + void createPipeline(const ShaderPipelineDesc& pipeline) override {} void draw(const WindowSize &windowSize) override {}; void setVSyncEnabled(bool enabled) override {} diff --git a/plugins/Shader/CMakeLists.txt b/plugins/Shader/CMakeLists.txt index e69de29..84ec7f3 100644 --- a/plugins/Shader/CMakeLists.txt +++ b/plugins/Shader/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(SPIR-V) diff --git a/plugins/Shader/SPIR-V/CMakeLists.txt b/plugins/Shader/SPIR-V/CMakeLists.txt new file mode 100644 index 0000000..4bb834b --- /dev/null +++ b/plugins/Shader/SPIR-V/CMakeLists.txt @@ -0,0 +1,64 @@ +project(cae-spriv + DESCRIPTION "CAE SPIR-V Shader Plugin" + LANGUAGES C CXX +) + +find_package(glslang QUIET) +find_package(SPIRV-Tools QUIET) + +if (NOT SPIRV-Tools_FOUND) + include(FetchContent) + FetchContent_Declare( + spirv-headers + GIT_REPOSITORY "https://github.com/KhronosGroup/SPIRV-Headers.git" + GIT_TAG main + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + ) + FetchContent_MakeAvailable(spirv-headers) + FetchContent_Declare( + spirv-tools + GIT_REPOSITORY "https://github.com/KhronosGroup/SPIRV-Tools.git" + GIT_TAG main + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + ) + FetchContent_MakeAvailable(spirv-tools) +endif () + +if (NOT glslang_FOUND) + include(FetchContent) + + set(ENABLE_OPT ON CACHE BOOL "" FORCE) + set(ENABLE_HLSL OFF CACHE BOOL "" FORCE) + + FetchContent_Declare( + glslang + GIT_REPOSITORY "https://github.com/KhronosGroup/glslang.git" + GIT_TAG main + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + ) + FetchContent_MakeAvailable(glslang) +endif () + +file(GLOB_RECURSE SOURCES "${PROJECT_SOURCE_DIR}/src/*.cpp") + +add_library(${PROJECT_NAME} SHARED ${SOURCES}) + +target_include_directories(${PROJECT_NAME} PRIVATE + "${PROJECT_SOURCE_DIR}/include" +) +target_compile_options(${PROJECT_NAME} PRIVATE ${WARNING_FLAGS}) +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_23) +target_link_libraries(${PROJECT_NAME} PRIVATE + cae-modules + glslang + SPIRV + SPIRV-Tools + SPIRV-Tools-opt +) +set_target_properties(${PROJECT_NAME} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${PLUGIN_DIR}" + RUNTIME_OUTPUT_DIRECTORY "${PLUGIN_DIR}" +) \ No newline at end of file diff --git a/plugins/Shader/SPIR-V/include/SPIR-V/SPIR-V.hpp b/plugins/Shader/SPIR-V/include/SPIR-V/SPIR-V.hpp new file mode 100644 index 0000000..9edfe47 --- /dev/null +++ b/plugins/Shader/SPIR-V/include/SPIR-V/SPIR-V.hpp @@ -0,0 +1,141 @@ +/// +/// @file SPIR-V.hpp +/// @brief This file contains the SPIR-V class declaration +/// @namespace cae +/// + +#pragma once + +#include +#include + +#include "Interfaces/Renderer/IShader.hpp" + +#include +#include + +namespace cae +{ + + constexpr auto VERSION = 450; + + /// + /// @class SPIRV + /// @brief Class for the SPIRV plugin + /// @namespace cae + /// + class SPIRV final : public IShader + { + + public: + SPIRV() = default; + ~SPIRV() override = default; + + SPIRV(const SPIRV &) = delete; + SPIRV &operator=(const SPIRV &) = delete; + SPIRV(SPIRV &&) = delete; + SPIRV &operator=(SPIRV &&) = delete; + + [[nodiscard]] std::string getName() const override { return "SPIRV"; } + [[nodiscard]] utl::PluginType getType() const override { return utl::PluginType::SHADER; } + [[nodiscard]] utl::PluginPlatform getPlatform() const override { return utl::PluginPlatform::LINUX; } + + + void addShader(const ShaderModuleDesc& pipelineDesc) override; + bool compileAll() override; + const ShaderData& getShader(const std::string& name) const override; + bool isCompiled(const std::string& name) const override; + + private: + std::unordered_map m_shaders; + std::unordered_map m_compiled; + + static EShLanguage shaderStageToESh(const ShaderStage stage) { + switch (stage) { + case ShaderStage::VERTEX: return EShLangVertex; + case ShaderStage::FRAGMENT: return EShLangFragment; + case ShaderStage::GEOMETRY: return EShLangGeometry; + case ShaderStage::COMPUTE: return EShLangCompute; + default: throw std::runtime_error("Unsupported ShaderStage"); + } + } + + static std::vector compileGLSLtoSPIRV(const std::string& src, const ShaderStage stage) { + static bool glslangInitialized = false; + if (!glslangInitialized) { + glslang::InitializeProcess(); + glslangInitialized = true; + } + + const EShLanguage lang = shaderStageToESh(stage); + glslang::TShader shader(lang); + const char* shaderStrings[1] = { src.c_str() }; + shader.setStrings(shaderStrings, 1); + + shader.setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, VERSION); + shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_3); + shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_6); + + 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; + + constexpr auto messages = static_cast(EShMsgSpvRules | EShMsgVulkanRules); + + if (!shader.parse(&Resources, VERSION, false, messages)) { + throw std::runtime_error("GLSL parsing failed: " + std::string(shader.getInfoLog())); + } + + glslang::TProgram program; + program.addShader(&shader); + + if (!program.link(messages)) { + throw std::runtime_error("GLSL linking failed: " + std::string(program.getInfoLog())); + } + + std::vector spirv; + glslang::GlslangToSpv(*program.getIntermediate(lang), spirv); + + return spirv; + } + + }; // class SPIRV + +} // namespace cae diff --git a/plugins/Shader/SPIR-V/src/entrypoint.cpp b/plugins/Shader/SPIR-V/src/entrypoint.cpp new file mode 100644 index 0000000..8f97ca8 --- /dev/null +++ b/plugins/Shader/SPIR-V/src/entrypoint.cpp @@ -0,0 +1,8 @@ +#include + +#include "SPIR-V/SPIR-V.hpp" + +extern "C" +{ + PLUGIN_EXPORT cae::IShader *entryPoint() { return std::make_unique().release(); } +} diff --git a/plugins/Shader/SPIR-V/src/spirv.cpp b/plugins/Shader/SPIR-V/src/spirv.cpp new file mode 100644 index 0000000..3ccfb27 --- /dev/null +++ b/plugins/Shader/SPIR-V/src/spirv.cpp @@ -0,0 +1,43 @@ +#include "SPIR-V/SPIR-V.hpp" +#include "Utils/Logger.hpp" + +void cae::SPIRV::addShader(const ShaderModuleDesc& pipelineDesc) +{ + ShaderData data; + data.type = ShaderSourceType::GLSL; + data.source = pipelineDesc.source; + data.stage = pipelineDesc.stage; + data.entryPoint = "main"; + m_shaders[pipelineDesc.id] = std::move(data); +} + +bool cae::SPIRV::compileAll() +{ + bool allCompiled = true; + for (auto& [name, shader] : m_shaders) { + try { + shader.spirv = compileGLSLtoSPIRV(shader.source, shader.stage); + m_compiled[name] = true; + } catch (const std::exception& e) { + m_compiled[name] = false; + allCompiled = false; + utl::Logger::log("Shader compilation failed [" + name + "]: " + e.what(), utl::LogLevel::INFO); + } + } + return allCompiled; +} + +const ShaderData& cae::SPIRV::getShader(const std::string& name) const +{ + auto it = m_shaders.find(name); + if (it == m_shaders.end()) { + throw std::runtime_error("Shader not found: " + name); + } + return it->second; +} + +bool cae::SPIRV::isCompiled(const std::string& name) const +{ + auto it = m_compiled.find(name); + return it != m_compiled.end() && it->second; +} diff --git a/src/application.cpp b/src/application.cpp index b039d57..e4639aa 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -44,7 +44,7 @@ cae::Application::Application(const ArgsConfig &argsConfig, const EnvConfig &env { m_appConfig.engineConfig = parseEngineConf(argsConfig.config_path); } - setupEngine("OpenGL", "GLFW"); + setupEngine("OpenGL", "X11", "SPIRV"); } catch (const std::exception &e) { @@ -52,10 +52,11 @@ cae::Application::Application(const ArgsConfig &argsConfig, const EnvConfig &env } } -void cae::Application::setupEngine(const std::string &rendererName, const std::string &windowName) +void cae::Application::setupEngine(const std::string &rendererName, const std::string &windowName, const std::string &shaderName) { std::shared_ptr windowPlugin = nullptr; std::shared_ptr rendererPlugin = nullptr; + std::shared_ptr shaderPlugin = nullptr; for (auto &plugin : loadPlugins(m_pluginLoader)) { @@ -73,6 +74,13 @@ void cae::Application::setupEngine(const std::string &rendererName, const std::s windowPlugin = window; } } + if (const auto shader = std::dynamic_pointer_cast(plugin)) + { + if (shader->getName() == shaderName) + { + shaderPlugin = shader; + } + } } if (windowPlugin == nullptr) { @@ -82,9 +90,13 @@ void cae::Application::setupEngine(const std::string &rendererName, const std::s { utl::Logger::log("No renderer plugin found with name: " + windowName, utl::LogLevel::WARNING); } + if (shaderPlugin == nullptr) + { + utl::Logger::log("No shader plugin found with name: SPIRV", utl::LogLevel::WARNING); + } m_engine = std::make_unique( m_appConfig.engineConfig, []() { return nullptr; }, []() { return nullptr; }, []() { return nullptr; }, - [rendererPlugin]() { return rendererPlugin; }, [windowPlugin]() { return windowPlugin; }); + [rendererPlugin]() { return rendererPlugin; }, [shaderPlugin] { return shaderPlugin; }, [windowPlugin]() { return windowPlugin; }); } void cae::Application::start() const { m_engine->run(); } diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 27b5118..65a5028 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1,14 +1,18 @@ #include "Utils/Logger.hpp" #include "CAE/Engine/Engine.hpp" +#include "Utils/Utils.hpp" + +#include cae::Engine::Engine(const EngineConfig &config, const std::function()> &audioFactory, const std::function()> &inputFactory, const std::function()> &networkFactory, const std::function()> &rendererFactory, + const std::function()> &shaderFactory, const std::function()> &windowFactory) : m_audioPlugin(audioFactory()), m_inputPlugin(inputFactory()), m_networkPlugin(networkFactory()), - m_rendererPlugin(rendererFactory()), m_windowPlugin(windowFactory()), m_clock(std::make_unique()) + m_rendererPlugin(rendererFactory()), m_shaderPlugin(shaderFactory()), m_windowPlugin(windowFactory()), m_clock(std::make_unique()) { utl::Logger::log("Loading engine with configuration:", utl::LogLevel::INFO); utl::Logger::log("\tAudio master volume: " + std::to_string(config.audio_master_volume), utl::LogLevel::INFO); @@ -24,18 +28,40 @@ cae::Engine::Engine(const EngineConfig &config, const std::functioncreate(config.window_name, {.width = config.window_width, .height = config.window_height}); - m_rendererPlugin->initialize(m_windowPlugin->getNativeHandle()); + const std::vector shadersPipelineDesc = { + {.id = "basic_vertex", .source = utl::fileToString("assets/shaders/uniform_color.vert"), + .stage = ShaderStage::VERTEX}, + {.id = "basic_fragment", .source = utl::fileToString("assets/shaders/uniform_color.frag"), + .stage = ShaderStage::FRAGMENT}}; + m_shaderPlugin->addShader(shadersPipelineDesc.at(0)); + m_shaderPlugin->addShader(shadersPipelineDesc.at(1)); + if (!m_shaderPlugin->compileAll()) + { + throw std::runtime_error("Failed to compile shaders"); + } + m_rendererPlugin->initialize(m_windowPlugin->getNativeHandle(), m_shaderPlugin); + m_rendererPlugin->createPipeline({.id = "basic", .vertex = shadersPipelineDesc.at(0).id, .fragment = shadersPipelineDesc.at(1).id}); } -void cae::Engine::run() const +void printFps(std::array& fpsBuffer, int& fpsIndex, float deltaTime) { + fpsBuffer[fpsIndex % 10] = 1.0f / deltaTime; + fpsIndex++; + + float avgFps = std::accumulate(fpsBuffer.begin(), fpsBuffer.end(), 0.0f) / 10.0f; + utl::Logger::log(std::format("FPS: {}", avgFps), utl::LogLevel::INFO); +} +void cae::Engine::run() const +{ + std::array fpsBuffer{}; + int fpsIndex = 0; while (!m_windowPlugin->shouldClose()) { - utl::Logger::log(std::format("FPS: {}", 1.0F / m_clock->getDeltaSeconds()), utl::LogLevel::INFO); - m_clock->restart(); m_rendererPlugin->draw(m_windowPlugin->getWindowSize()); m_windowPlugin->pollEvents(); + printFps(fpsBuffer, fpsIndex, m_clock->getDeltaSeconds()); + m_clock->restart(); } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index abfaebf..86b8fbc 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,30 +1,30 @@ option(BUILD_CAE_TESTS "Build tests" OFF) -if (NOT BUILD_CAE_TESTS) - return() -endif() +if (BUILD_CAE_TESTS) + + project(cae-tests + DESCRIPTION "CAE unit tests" + LANGUAGES CXX + ) -project(cae-tests - DESCRIPTION "CAE unit tests" - LANGUAGES CXX -) + include(FetchContent) + FetchContent_Declare( + googletest + GIT_REPOSITORY "https://github.com/google/googletest.git" + GIT_TAG "v1.17.0" + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + ) + FetchContent_MakeAvailable(googletest) -include(FetchContent) -FetchContent_Declare( - googletest - GIT_REPOSITORY "https://github.com/google/googletest.git" - GIT_TAG "v1.17.0" - GIT_SHALLOW TRUE - GIT_PROGRESS TRUE -) -FetchContent_MakeAvailable(googletest) + file(GLOB_RECURSE SOURCES "${PROJECT_SOURCE_DIR}/src/*.cpp") -file(GLOB_RECURSE SOURCES "${PROJECT_SOURCE_DIR}/src/*.cpp") + add_executable(${PROJECT_NAME} ${SOURCES}) -add_executable(${PROJECT_NAME} ${SOURCES}) + target_link_libraries(${PROJECT_NAME} PRIVATE GTest::gtest_main) + target_include_directories(${PROJECT_NAME} PRIVATE ${INCLUDE_DIR}) + include(GoogleTest) + gtest_discover_tests(${PROJECT_NAME}) + target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_23) + set_property(TARGET ${PROJECT_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON) -target_link_libraries(${PROJECT_NAME} PRIVATE GTest::gtest_main) -target_include_directories(${PROJECT_NAME} PRIVATE ${INCLUDE_DIR}) -include(GoogleTest) -gtest_discover_tests(${PROJECT_NAME}) -target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_23) -set_property(TARGET ${PROJECT_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON) +endif()