From c033eb90f3f21420d8d99e5f3fbf0a2b51975ce3 Mon Sep 17 00:00:00 2001 From: bobi Date: Mon, 15 Dec 2025 17:26:41 +0100 Subject: [PATCH 1/4] Feat: First shader plugin with glsl and spirv --- assets/shaders/animated.frag | 15 ++ assets/shaders/animated.vert | 13 ++ assets/shaders/basic_color.frag | 9 ++ assets/shaders/basic_color.vert | 12 ++ assets/shaders/texture.frag | 12 ++ assets/shaders/texture.vert | 12 ++ assets/shaders/transform.frag | 9 ++ assets/shaders/transform.vert | 18 +++ assets/shaders/uniform_color.frag | 12 ++ assets/shaders/uniform_color.vert | 8 + include/CAE/Application.hpp | 2 +- include/CAE/Engine/Engine.hpp | 2 + .../include/Interfaces/Input/Key/Keyboard.hpp | 2 +- .../include/Interfaces/Renderer/ARenderer.hpp | 39 +++++ .../include/Interfaces/Renderer/IRenderer.hpp | 10 +- .../include/Interfaces/Renderer/IShader.hpp | 34 +++++ .../include/Utils/Interfaces/IPlugin.hpp | 5 +- modules/utils/include/Utils/Logger.hpp | 26 ++-- modules/utils/include/Utils/Utils.hpp | 4 +- modules/utils/src/utils.cpp | 13 +- plugins/CMakeLists.txt | 1 + plugins/Renderer/OpenGL/include/OPGL/OPGL.hpp | 7 +- plugins/Renderer/OpenGL/src/opgl.cpp | 76 +++++----- .../Renderer/Vulkan/include/VULKN/VULKN.hpp | 6 +- plugins/Shader/CMakeLists.txt | 1 + plugins/Shader/SPIR-V/CMakeLists.txt | 58 +++++++ .../Shader/SPIR-V/include/SPIR-V/SPIR-V.hpp | 141 ++++++++++++++++++ plugins/Shader/SPIR-V/src/entrypoint.cpp | 8 + plugins/Shader/SPIR-V/src/spirv.cpp | 43 ++++++ src/application.cpp | 18 ++- src/engine/engine.cpp | 5 +- 31 files changed, 556 insertions(+), 65 deletions(-) create mode 100644 assets/shaders/animated.frag create mode 100644 assets/shaders/animated.vert create mode 100644 assets/shaders/basic_color.frag create mode 100644 assets/shaders/basic_color.vert create mode 100644 assets/shaders/texture.frag create mode 100644 assets/shaders/texture.vert create mode 100644 assets/shaders/transform.frag create mode 100644 assets/shaders/transform.vert create mode 100644 assets/shaders/uniform_color.frag create mode 100644 assets/shaders/uniform_color.vert create mode 100644 modules/Interfaces/include/Interfaces/Renderer/ARenderer.hpp create mode 100644 plugins/Shader/SPIR-V/CMakeLists.txt create mode 100644 plugins/Shader/SPIR-V/include/SPIR-V/SPIR-V.hpp create mode 100644 plugins/Shader/SPIR-V/src/entrypoint.cpp create mode 100644 plugins/Shader/SPIR-V/src/spirv.cpp 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..055ae7c 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,13 @@ 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 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..0961373 100644 --- a/modules/Interfaces/include/Interfaces/Renderer/IShader.hpp +++ b/modules/Interfaces/include/Interfaces/Renderer/IShader.hpp @@ -6,8 +6,37 @@ #pragma once +#include + #include "Utils/Interfaces/IPlugin.hpp" +enum class ShaderSourceType : uint8_t +{ + GLSL, + HLSL, + SPIRV, + MSL, + WGSL, + UNDEFINED = 255 +}; + +enum class ShaderStage : uint8_t +{ + VERTEX, + FRAGMENT, + GEOMETRY, + COMPUTE, + UNDEFINED = 255 +}; + +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 +51,11 @@ namespace cae public: ~IShader() override = default; + virtual void addShader(const std::string& name, const std::string& source, ShaderStage stage) = 0; + virtual bool compileAll() = 0; + virtual const ShaderData& getShader(const std::string& name) const = 0; + virtual bool isCompiled(const std::string& 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..bc5d250 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::string &filename); + [[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..2210a84 100644 --- a/modules/utils/src/utils.cpp +++ b/modules/utils/src/utils.cpp @@ -2,7 +2,7 @@ #include "Utils/Utils.hpp" -std::vector utl::readFile(const std::string &filename) +std::vector utl::fileToVector(const std::string &filename) { std::ifstream file(filename, std::ios::binary | std::ios::ate); if (!file.is_open()) @@ -22,3 +22,14 @@ std::vector utl::readFile(const std::string &filename) } 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..9b1aa54 100644 --- a/plugins/Renderer/OpenGL/include/OPGL/OPGL.hpp +++ b/plugins/Renderer/OpenGL/include/OPGL/OPGL.hpp @@ -9,7 +9,7 @@ #include "OPGL/glad/glad.h" #include "Context/IContext.hpp" -#include "Interfaces/Renderer/IRenderer.hpp" +#include "Interfaces/Renderer/ARenderer.hpp" namespace cae { @@ -19,7 +19,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 +34,7 @@ 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 draw(const WindowSize &windowSize) override; void setVSyncEnabled(bool enabled) override; @@ -42,6 +42,7 @@ namespace cae private: std::unique_ptr m_context; + std::shared_ptr m_shader; GLuint gVAO = 0; GLuint gVBO = 0; diff --git a/plugins/Renderer/OpenGL/src/opgl.cpp b/plugins/Renderer/OpenGL/src/opgl.cpp index 601bda7..d4f00c8 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,8 +12,9 @@ #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 @@ -51,52 +52,53 @@ bool cae::OPGL::isVSyncEnabled() const { return m_context->isVSyncEnabled(); } void cae::OPGL::createShaderProgram() { - 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 - { - 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); + m_shader->addShader( + "vertex", + utl::fileToString("assets/shaders/uniform_color.vert"), + ShaderStage::VERTEX + ); + + m_shader->addShader( + "fragment", + utl::fileToString("assets/shaders/uniform_color.frag"), + ShaderStage::FRAGMENT + ); + + if (!m_shader->compileAll()) { + throw std::runtime_error("Failed to compile shaders"); +} + + const auto& vertSPV = m_shader->getShader("vertex").spirv; + const auto& fragSPV = m_shader->getShader("fragment").spirv; + + if (vertSPV.empty() || fragSPV.empty()) { + throw std::runtime_error("Shader SPIR-V is empty, compilation must have failed"); +} gShaderProgram = glCreateProgram(); - glAttachShader(gShaderProgram, vertex); - glAttachShader(gShaderProgram, fragment); + + const GLuint vertShader = glCreateShader(GL_VERTEX_SHADER); + glShaderBinary(1, &vertShader, GL_SHADER_BINARY_FORMAT_SPIR_V, vertSPV.data(), vertSPV.size() * sizeof(uint32_t)); + glSpecializeShader(vertShader, "main", 0, nullptr, nullptr); + + const GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderBinary(1, &fragShader, GL_SHADER_BINARY_FORMAT_SPIR_V, fragSPV.data(), fragSPV.size() * sizeof(uint32_t)); + glSpecializeShader(fragShader, "main", 0, nullptr, nullptr); + + glAttachShader(gShaderProgram, vertShader); + glAttachShader(gShaderProgram, fragShader); glLinkProgram(gShaderProgram); GLint success = 0; glGetProgramiv(gShaderProgram, GL_LINK_STATUS, &success); - if (success == 0) - { + if (success == 0) { char log[512]; glGetProgramInfoLog(gShaderProgram, 512, nullptr, log); throw std::runtime_error(log); } - glDeleteShader(vertex); - glDeleteShader(fragment); + glDeleteShader(vertShader); + glDeleteShader(fragShader); } void cae::OPGL::createTriangle() diff --git a/plugins/Renderer/Vulkan/include/VULKN/VULKN.hpp b/plugins/Renderer/Vulkan/include/VULKN/VULKN.hpp index cda878b..00fb41b 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,7 @@ 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 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..16f0dcb --- /dev/null +++ b/plugins/Shader/SPIR-V/CMakeLists.txt @@ -0,0 +1,58 @@ +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 + ) + FetchContent_MakeAvailable(spirv-headers) + FetchContent_Declare( + spirv-tools + GIT_REPOSITORY https://github.com/KhronosGroup/SPIRV-Tools.git + GIT_TAG main + ) + 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 + ) + 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..cd3739a --- /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 std::string& name, const std::string& source, ShaderStage stage) 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..79dd152 --- /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 std::string& name, const std::string& source, const ShaderStage stage) +{ + ShaderData data; + data.type = ShaderSourceType::GLSL; + data.source = source; + data.stage = stage; + data.entryPoint = "main"; + m_shaders[name] = 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..92fec55 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -6,9 +6,10 @@ cae::Engine::Engine(const EngineConfig &config, 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,7 +25,7 @@ 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()); + m_rendererPlugin->initialize(m_windowPlugin->getNativeHandle(), m_shaderPlugin); } void cae::Engine::run() const From caae4fa36ce14be2e0ee5a407ef06f4e9448ff23 Mon Sep 17 00:00:00 2001 From: bobi Date: Thu, 18 Dec 2025 13:44:17 +0100 Subject: [PATCH 2/4] Fix: improve usage of shader plugin from application/engine to renderer plugin without hard coded shaders in renderer --- .../include/Interfaces/Renderer/IRenderer.hpp | 2 + .../include/Interfaces/Renderer/IShader.hpp | 23 +++- plugins/Renderer/OpenGL/include/OPGL/OPGL.hpp | 9 +- plugins/Renderer/OpenGL/src/opgl.cpp | 104 +++++++++--------- .../Renderer/Vulkan/include/VULKN/VULKN.hpp | 1 + .../Shader/SPIR-V/include/SPIR-V/SPIR-V.hpp | 2 +- plugins/Shader/SPIR-V/src/spirv.cpp | 8 +- src/engine/engine.cpp | 14 ++- 8 files changed, 101 insertions(+), 62 deletions(-) diff --git a/modules/Interfaces/include/Interfaces/Renderer/IRenderer.hpp b/modules/Interfaces/include/Interfaces/Renderer/IRenderer.hpp index 055ae7c..174dcbe 100644 --- a/modules/Interfaces/include/Interfaces/Renderer/IRenderer.hpp +++ b/modules/Interfaces/include/Interfaces/Renderer/IRenderer.hpp @@ -31,6 +31,8 @@ namespace cae 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 0961373..ed2ef8f 100644 --- a/modules/Interfaces/include/Interfaces/Renderer/IShader.hpp +++ b/modules/Interfaces/include/Interfaces/Renderer/IShader.hpp @@ -10,6 +10,8 @@ #include "Utils/Interfaces/IPlugin.hpp" +using ShaderID = std::string; + enum class ShaderSourceType : uint8_t { GLSL, @@ -29,6 +31,21 @@ enum class ShaderStage : uint8_t 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 @@ -51,10 +68,10 @@ namespace cae public: ~IShader() override = default; - virtual void addShader(const std::string& name, const std::string& source, ShaderStage stage) = 0; + virtual void addShader(const ShaderModuleDesc& pipelineDesc) = 0; virtual bool compileAll() = 0; - virtual const ShaderData& getShader(const std::string& name) const = 0; - virtual bool isCompiled(const std::string& name) const = 0; + virtual const ShaderData& getShader(const ShaderID& name) const = 0; + virtual bool isCompiled(const ShaderID& name) const = 0; }; // interface IShader diff --git a/plugins/Renderer/OpenGL/include/OPGL/OPGL.hpp b/plugins/Renderer/OpenGL/include/OPGL/OPGL.hpp index 9b1aa54..2bf4588 100644 --- a/plugins/Renderer/OpenGL/include/OPGL/OPGL.hpp +++ b/plugins/Renderer/OpenGL/include/OPGL/OPGL.hpp @@ -11,6 +11,8 @@ #include "Context/IContext.hpp" #include "Interfaces/Renderer/ARenderer.hpp" +#include + namespace cae { @@ -35,6 +37,7 @@ namespace cae [[nodiscard]] utl::PluginPlatform getPlatform() const override { return utl::PluginPlatform::ALL; } 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; @@ -44,11 +47,13 @@ namespace cae 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 d4f00c8..afd9755 100644 --- a/plugins/Renderer/OpenGL/src/opgl.cpp +++ b/plugins/Renderer/OpenGL/src/opgl.cpp @@ -21,15 +21,12 @@ void cae::OPGL::initialize(const NativeWindowHandle &window, const std::shared_p 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(); } @@ -38,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); @@ -50,55 +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) { - m_shader->addShader( - "vertex", - utl::fileToString("assets/shaders/uniform_color.vert"), - ShaderStage::VERTEX - ); - - m_shader->addShader( - "fragment", - utl::fileToString("assets/shaders/uniform_color.frag"), - ShaderStage::FRAGMENT - ); - - if (!m_shader->compileAll()) { - throw std::runtime_error("Failed to compile shaders"); -} - - const auto& vertSPV = m_shader->getShader("vertex").spirv; - const auto& fragSPV = m_shader->getShader("fragment").spirv; - - if (vertSPV.empty() || fragSPV.empty()) { - throw std::runtime_error("Shader SPIR-V is empty, compilation must have failed"); -} - - gShaderProgram = glCreateProgram(); - - const GLuint vertShader = glCreateShader(GL_VERTEX_SHADER); - glShaderBinary(1, &vertShader, GL_SHADER_BINARY_FORMAT_SPIR_V, vertSPV.data(), vertSPV.size() * sizeof(uint32_t)); - glSpecializeShader(vertShader, "main", 0, nullptr, nullptr); - - const GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER); - glShaderBinary(1, &fragShader, GL_SHADER_BINARY_FORMAT_SPIR_V, fragSPV.data(), fragSPV.size() * sizeof(uint32_t)); - glSpecializeShader(fragShader, "main", 0, nullptr, nullptr); - - glAttachShader(gShaderProgram, vertShader); - glAttachShader(gShaderProgram, fragShader); - 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); + if (!m_shader->isCompiled(pipeline.vertex) || + !m_shader->isCompiled(pipeline.fragment)) + { + throw std::runtime_error("Pipeline uses uncompiled shaders"); } - glDeleteShader(vertShader); - glDeleteShader(fragShader); + const GLuint program = createProgramFromPipeline(pipeline); + m_programs[pipeline.id] = program; } void cae::OPGL::createTriangle() @@ -121,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 00fb41b..47fad56 100644 --- a/plugins/Renderer/Vulkan/include/VULKN/VULKN.hpp +++ b/plugins/Renderer/Vulkan/include/VULKN/VULKN.hpp @@ -33,6 +33,7 @@ namespace cae [[nodiscard]] utl::PluginPlatform getPlatform() const override { return utl::PluginPlatform::ALL; } 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/SPIR-V/include/SPIR-V/SPIR-V.hpp b/plugins/Shader/SPIR-V/include/SPIR-V/SPIR-V.hpp index cd3739a..9edfe47 100644 --- a/plugins/Shader/SPIR-V/include/SPIR-V/SPIR-V.hpp +++ b/plugins/Shader/SPIR-V/include/SPIR-V/SPIR-V.hpp @@ -41,7 +41,7 @@ namespace cae [[nodiscard]] utl::PluginPlatform getPlatform() const override { return utl::PluginPlatform::LINUX; } - void addShader(const std::string& name, const std::string& source, ShaderStage stage) override; + 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; diff --git a/plugins/Shader/SPIR-V/src/spirv.cpp b/plugins/Shader/SPIR-V/src/spirv.cpp index 79dd152..3ccfb27 100644 --- a/plugins/Shader/SPIR-V/src/spirv.cpp +++ b/plugins/Shader/SPIR-V/src/spirv.cpp @@ -1,14 +1,14 @@ #include "SPIR-V/SPIR-V.hpp" #include "Utils/Logger.hpp" -void cae::SPIRV::addShader(const std::string& name, const std::string& source, const ShaderStage stage) +void cae::SPIRV::addShader(const ShaderModuleDesc& pipelineDesc) { ShaderData data; data.type = ShaderSourceType::GLSL; - data.source = source; - data.stage = stage; + data.source = pipelineDesc.source; + data.stage = pipelineDesc.stage; data.entryPoint = "main"; - m_shaders[name] = std::move(data); + m_shaders[pipelineDesc.id] = std::move(data); } bool cae::SPIRV::compileAll() diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 92fec55..1e3c927 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1,6 +1,7 @@ #include "Utils/Logger.hpp" #include "CAE/Engine/Engine.hpp" +#include "Utils/Utils.hpp" cae::Engine::Engine(const EngineConfig &config, const std::function()> &audioFactory, const std::function()> &inputFactory, @@ -25,12 +26,23 @@ cae::Engine::Engine(const EngineConfig &config, const std::functioncreate(config.window_name, {.width = config.window_width, .height = config.window_height}); + 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 { - while (!m_windowPlugin->shouldClose()) { utl::Logger::log(std::format("FPS: {}", 1.0F / m_clock->getDeltaSeconds()), utl::LogLevel::INFO); From fa43b803486d57236afc09fe282d327ea0d9bf5d Mon Sep 17 00:00:00 2001 From: bobi Date: Thu, 18 Dec 2025 14:19:02 +0100 Subject: [PATCH 3/4] Fix: missing git shallow and git progress for fetch_content deps --- .github/workflows/ci.yml | 2 +- modules/utils/include/Utils/Utils.hpp | 2 +- modules/utils/src/utils.cpp | 11 ++++++----- plugins/Shader/SPIR-V/CMakeLists.txt | 12 +++++++++--- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c49c7c..d374a70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,7 @@ 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 -S . -G "Ninja" -B cmake-build-release -DCMAKE_BUILD_TYPE=Release -DBUILD_CAE_TESTS=ON cmake --build cmake-build-tests binary_test: cmake-build-tests\bin\cae-tests.exe deps: choco install cmake -y diff --git a/modules/utils/include/Utils/Utils.hpp b/modules/utils/include/Utils/Utils.hpp index bc5d250..0731e6e 100644 --- a/modules/utils/include/Utils/Utils.hpp +++ b/modules/utils/include/Utils/Utils.hpp @@ -11,6 +11,6 @@ namespace utl { - [[nodiscard]] std::vector fileToVector(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 2210a84..8051675 100644 --- a/modules/utils/src/utils.cpp +++ b/modules/utils/src/utils.cpp @@ -1,24 +1,25 @@ #include +#include #include "Utils/Utils.hpp" -std::vector utl::fileToVector(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; } diff --git a/plugins/Shader/SPIR-V/CMakeLists.txt b/plugins/Shader/SPIR-V/CMakeLists.txt index 16f0dcb..4bb834b 100644 --- a/plugins/Shader/SPIR-V/CMakeLists.txt +++ b/plugins/Shader/SPIR-V/CMakeLists.txt @@ -10,14 +10,18 @@ if (NOT SPIRV-Tools_FOUND) include(FetchContent) FetchContent_Declare( spirv-headers - GIT_REPOSITORY https://github.com/KhronosGroup/SPIRV-Headers.git + 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_REPOSITORY "https://github.com/KhronosGroup/SPIRV-Tools.git" GIT_TAG main + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE ) FetchContent_MakeAvailable(spirv-tools) endif () @@ -30,8 +34,10 @@ if (NOT glslang_FOUND) FetchContent_Declare( glslang - GIT_REPOSITORY https://github.com/KhronosGroup/glslang.git + GIT_REPOSITORY "https://github.com/KhronosGroup/glslang.git" GIT_TAG main + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE ) FetchContent_MakeAvailable(glslang) endif () From d57fa272a40bf266b5cfc4a30f2d1dd49b04ce6c Mon Sep 17 00:00:00 2001 From: bobi Date: Tue, 23 Dec 2025 12:21:36 +0100 Subject: [PATCH 4/4] Fix:only build tests when flag, smooth fps logging --- .github/workflows/ci.yml | 4 ++-- src/engine/engine.cpp | 17 ++++++++++++-- tests/CMakeLists.txt | 48 ++++++++++++++++++++-------------------- 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d374a70..3a855ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,8 +49,8 @@ jobs: binary: cmake-build-release\bin\cae.exe build_test: | cmake -S . -G "Ninja" -B cmake-build-release -DCMAKE_BUILD_TYPE=Release -DBUILD_CAE_TESTS=ON - cmake --build cmake-build-tests - binary_test: cmake-build-tests\bin\cae-tests.exe + 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/src/engine/engine.cpp b/src/engine/engine.cpp index 1e3c927..65a5028 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3,6 +3,8 @@ #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, @@ -41,14 +43,25 @@ cae::Engine::Engine(const EngineConfig &config, const std::functioncreatePipeline({.id = "basic", .vertex = shadersPipelineDesc.at(0).id, .fragment = shadersPipelineDesc.at(1).id}); } +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()