diff --git a/.github/workflows/windows-debug-build-test.yml b/.github/workflows/windows-debug-build-test.yml index 9759eb0..32a5875 100644 --- a/.github/workflows/windows-debug-build-test.yml +++ b/.github/workflows/windows-debug-build-test.yml @@ -12,11 +12,11 @@ jobs: name: Build and Test on Windows runs-on: windows-latest strategy: - matrix: + matrix: preset: [ "windows-x86_64-mingw64-debug", "windows-x86_64-llvm-debug", "windows-x86_64-msvc-debug",] - + steps: - name: Checkout code uses: actions/checkout@v4 @@ -31,23 +31,45 @@ jobs: echo "Repository: ${{ github.repository }}" echo "Commit SHA: ${{ github.sha }}" + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Debug Python paths + shell: pwsh + run: | + echo "🔍 Python paths:" + python --version + python -c "import sys; print('sys.executable ->', sys.executable)" + echo "--- where.exe python ---" + where.exe python + echo "--- where.exe python3 ---" + where.exe python3 + - name: Install dependencies + shell: pwsh run: | echo "📦 Installing Windows dependencies..." - + $py = python -c "import sys; print(sys.executable)" + echo "Python used for install: $py" + python -m pip install jinja2 echo "✅ Windows dependencies installed successfully" - name: Configure MSVC if: ${{ matrix.preset == 'windows-x86_64-msvc-debug' || matrix.preset == 'windows-x86_64-msvc-noabi-debug' }} uses: TheMrMilchmann/setup-msvc-dev@v4.0.0 - with: + with: arch: x64 - + - name: Configure CMake + shell: pwsh run: | echo "⚙️ Configuring CMake ..." echo "Using preset: ${{matrix.preset}}" - cmake --preset ${{matrix.preset}} + $PythonPath = python -c "import sys; print(sys.executable)" + echo "Using Python: $PythonPath" + cmake --preset ${{matrix.preset}} "-DPython_EXECUTABLE=$PythonPath" echo "✅ CMake configuration completed" - name: Configure and build project @@ -64,7 +86,7 @@ jobs: echo "🎉 Build process completed!" echo "Platform: ${{ runner.os }}" echo "Job status: ${{ job.status }}" - + # # List built files # echo "📁 Built files:" # find ${{ github.workspace }}/build -name "*.so" -o -name "*.dll" -o -name "*.a" -o -name "*.lib" 2>/dev/null || echo "No library files found" diff --git a/.gitignore b/.gitignore index 6836e70..52bfcb6 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,7 @@ AGENTS.md # binary files bin/ -build/ \ No newline at end of file +build/ + +# third party generated +third_party/glad_generated/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index f9db581..1cc7b9b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,3 +6,6 @@ path = third_party/sdl_image url = https://github.com/libsdl-org/SDL_image.git branch = release-3.4.x +[submodule "third_party/glad"] + path = third_party/glad + url = https://github.com/Dav1dde/glad.git diff --git a/include/gkit/graphic/Renderer.hpp b/include/gkit/graphic/Renderer.hpp new file mode 100644 index 0000000..591ba86 --- /dev/null +++ b/include/gkit/graphic/Renderer.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include "opengl/IndexBuffer.hpp" +#include "opengl/VertexArray.hpp" +#include "Shader.hpp" +#include "opengl/config.hpp" +#include "gkit/core/scene/singleton.hpp" + +#include + +/** + * @brief Renderer class providing public rendering interface + * + * The Renderer provides a unified interface for rendering operations. + * Uses singleton pattern for global access. + */ +namespace gkit::graphic { + + class Renderer : public core::scene::Singleton { + public: + Renderer() = default; + + /** + * @brief Clear the current framebuffer + * + * @param flags Bitmask specifying which buffers to clear (e.g., ClearFlags::Color | ClearFlags::Depth) + * Defaults to ClearFlags::All (clears all buffers) + */ + auto clear(opengl::ClearFlags flags = opengl::ClearFlags::All) const -> void; + + /** + * @brief Draw indexed geometry + * @param va Vertex array containing vertex data + * @param ib Index buffer containing indices + * @param shader Shader program to use for rendering + */ + auto draw(const gkit::graphic::opengl::VertexArray& va, const gkit::graphic::opengl::buffer::IndexBuffer& ib, const gkit::graphic::Shader& shader) const -> void; + + /** + * @brief Draw multiple instances of indexed geometry + * @param va Vertex array containing vertex data + * @param ib Index buffer containing indices + * @param shader Shader program to use for rendering + * @param instanceCount Number of instances to draw + */ + auto draw_instance(const gkit::graphic::opengl::VertexArray& va, const gkit::graphic::opengl::buffer::IndexBuffer& ib, const gkit::graphic::Shader& shader, uint32_t instanceCount) const -> void; + }; + +} // namespace gkit::graphic \ No newline at end of file diff --git a/include/gkit/graphic/Shader.hpp b/include/gkit/graphic/Shader.hpp new file mode 100644 index 0000000..549d3a1 --- /dev/null +++ b/include/gkit/graphic/Shader.hpp @@ -0,0 +1,158 @@ +#pragma once + +#include +#include +#include + +namespace gkit::graphic{ + /** + * @brief Structure holding parsed shader source code + */ + struct ShaderProgramSource { + std::string vertexShader; ///< Vertex shader source code + std::string fragmentShader; ///< Fragment shader source code (note: typo in original) + }; + + /** + * @brief Shader wrapper for OpenGL shader programs + * + * Provides functionality to load, compile, and manage shader programs + * including uniform variable manipulation. + */ + class Shader { + public: + Shader(const Shader&) = delete; + Shader& operator=(const Shader&) = delete; + + /** @brief Move constructor - transfers ownership of GL shader program + * @param other Source object to move from (will be invalidated) + */ + Shader(Shader&& other) noexcept; + + /** @brief Move assignment - transfers ownership of GL shader program + * @param other Source object to move from (will be invalidated) + * @note Releases any existing GL shader program before taking ownership + */ + auto operator=(Shader&& other) noexcept -> Shader&; + + private: + uint32_t m_RendererID; ///< OpenGL shader program ID + std::string m_FilePath; ///< Path to the shader file + std::unordered_map m_UniformLocationCach; ///< Cache for uniform locations + public: + /** + * @brief Construct a shader from a file + * @param filepath Path to the shader source file + */ + Shader(const std::string& filepath); + + /** + * @brief Destructor - deletes the shader program + */ + ~Shader(); + + /** + * @brief Bind this shader program to the current OpenGL context + */ + auto bind() const -> void; + + /** + * @brief Unbind this shader program from the current OpenGL context + */ + auto unbind() const -> void; + + // Uniform setters + + /** + * @brief Set an integer uniform variable + * @param name Name of the uniform + * @param value Integer value to set + */ + auto set_uniform_1i(const std::string& name, int value) -> void; + + /** + * @brief Set a float uniform variable + * @param name Name of the uniform + * @param value Float value to set + */ + auto set_uniform_1f(const std::string& name, float value) -> void; + + /** + * @brief Set a 4-component float uniform variable + * @param name Name of the uniform + * @param v0 First component + * @param v1 Second component + * @param f2 Third component + * @param f3 Fourth component + */ + auto set_uniform_4f(const std::string& name, float v0, float v1, float f2, float f3) -> void; + + /** + * @brief Set a 4-component float vector uniform + * @param name Name of the uniform + * @param vector4 Pointer to 4-component float array + */ + auto set_uniform_vec_4f(const std::string& name, const float* vector4) -> void; + + /** + * @brief Set a 3-component float vector uniform + * @param name Name of the uniform + * @param vector3 Pointer to 3-component float array + */ + auto set_uniform_vec_3f(const std::string& name, const float* vector3) -> void; + + /** + * @brief Set a 4x4 float matrix uniform + * @param name Name of the uniform + * @param matrix Pointer to 16-element float array (column-major) + */ + auto set_uniform_mat_4f(const std::string& name, const float* matrix) -> void; + + /** + * @brief Set a 3x3 float matrix uniform + * @param name Name of the uniform + * @param matrix Pointer to 9-element float array (column-major) + */ + auto set_uniform_mat_3f(const std::string& name, const float* matrix) -> void; + + /** + * @brief Set an array of integer uniform variables + * @param name Name of the uniform + * @param sz Number of elements + * @param ind Array of integer values + */ + auto set_uniform_1iv(const std::string& name, const int sz, const int* ind) -> void; + + private: + /** + * @brief Parse shader source from file + * @param filePath Path to the shader file + * @return Parsed shader source structure + */ + auto parse_shader(const std::string& filePath) -> ShaderProgramSource; + + /** + * @brief Compile a shader of the specified type + * @param type Shader type (GL_VERTEX_SHADER or GL_FRAGMENT_SHADER) + * @param source Shader source code + * @return Compiled shader ID + */ + auto compile_shader(uint32_t type, const std::string& source) -> uint32_t; + + /** + * @brief Create a shader program from vertex and fragment shaders + * @param vertexShader Vertex shader source + * @param fragmentShader Fragment shader source + * @return Linked shader program ID + */ + auto create_shader(const std::string& vertexShader, const std::string& fragmentShader) -> uint32_t; + + /** + * @brief Get the location of a uniform variable + * @param name Name of the uniform + * @return Location ID, or -1 if not found + */ + auto get_uniform_location(const std::string& name) -> int; + + }; +} \ No newline at end of file diff --git a/include/gkit/graphic/opengl/FrameBuffer.hpp b/include/gkit/graphic/opengl/FrameBuffer.hpp new file mode 100644 index 0000000..c4ae1e7 --- /dev/null +++ b/include/gkit/graphic/opengl/FrameBuffer.hpp @@ -0,0 +1,115 @@ +#pragma once + +#include "Texture.hpp" +#include "RenderBuffer.hpp" + +/** + * @brief Frame buffer wrapper for OpenGL framebuffer objects (FBO) + * + * A FrameBuffer is an off-screen rendering target that allows rendering + * to a texture instead of the screen. Commonly used for post-processing + * effects like color inversion, blur, bloom, etc. + */ +namespace gkit::graphic::opengl::buffer{ + + class FrameBuffer { + public: + FrameBuffer(const FrameBuffer&) = delete; + FrameBuffer& operator=(const FrameBuffer&) = delete; + + /** @brief Move constructor - transfers ownership of GL framebuffer + * @param other Source object to move from (will be invalidated) + */ + FrameBuffer(FrameBuffer&& other) noexcept; + + /** @brief Move assignment - transfers ownership of GL framebuffer + * @param other Source object to move from (will be invalidated) + * @note Releases any existing GL framebuffer before taking ownership + */ + auto operator=(FrameBuffer&& other) noexcept -> FrameBuffer&; + + private: + uint32_t m_RendererID; ///< OpenGL framebuffer ID + unsigned int fb_height; ///< Framebuffer height + unsigned int fb_width; ///< Framebuffer width + unsigned int leftX = 0; ///< Left coordinate + unsigned int bottomY = 0; ///< Bottom coordinate + public: + /** + * @brief Construct a framebuffer + */ + FrameBuffer(int width, int height); + + /** + * @brief Destructor - deletes the framebuffer + */ + ~FrameBuffer(); + + /** + * @brief Attach a color texture to this framebuffer + * @param texture The texture to attach as color attachment + * @param slot Attachment slot (typically 0 for GL_COLOR_ATTACHMENT0) + */ + auto attach_color_texture(const gkit::graphic::opengl::Texture& texture, int slot = 0) -> void; + + /** + * @brief Detach a color texture from this framebuffer + * @param slot Attachment slot to detach + */ + auto detach_color_texture(int slot = 0) -> void; + + /** + * @brief Attach a depth-stencil renderbuffer to this framebuffer + * @param rbo The renderbuffer to attach as depth-stencil attachment + */ + auto attach_depth_stencil(const gkit::graphic::opengl::buffer::RenderBuffer& rbo) -> void; + + /** + * @brief Detach the depth-stencil attachment from this framebuffer + */ + auto detach_depth_stencil() -> void; + + /** + * @brief Check if the framebuffer is complete + * + * Verifies that the framebuffer is properly configured and ready for rendering. + * Prints status message to console. + */ + auto check() -> void; + + /** + * @brief Set viewport to the framebuffer's default size + * + * Uses the stored framebuffer dimensions (fb_width, fb_height). + */ + auto set_viewport() -> void; + + /** + * @brief Set viewport to custom dimensions + * @param width Viewport width + * @param height Viewport height + */ + auto set_viewport(int width, int height) -> void; + + /** + * @brief Set custom viewport with offset + * @param x Left coordinate + * @param y Bottom coordinate + * @param width Viewport width + * @param height Viewport height + */ + auto set_viewport(int x, int y, int width, int height) -> void; + + /** + * @brief Bind this framebuffer as the rendering target + */ + auto bind() const -> void; + + /** + * @brief Unbind this framebuffer, revert to default framebuffer (screen) + */ + auto unbind() const -> void; + + }; + +} // namespace gkit::graphic::opengl::buffer \ No newline at end of file diff --git a/include/gkit/graphic/opengl/IndexBuffer.hpp b/include/gkit/graphic/opengl/IndexBuffer.hpp new file mode 100644 index 0000000..dc82d01 --- /dev/null +++ b/include/gkit/graphic/opengl/IndexBuffer.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include + +namespace gkit::graphic::opengl::buffer{ + /** + * @brief Index buffer wrapper for OpenGL element array buffers + * + * An IndexBuffer stores indices that determine the order in which vertices + * are drawn, enabling efficient rendering of indexed geometry. + */ + class IndexBuffer { + public: + IndexBuffer(const IndexBuffer&) = delete; + IndexBuffer& operator=(const IndexBuffer&) = delete; + + /** @brief Move constructor - transfers ownership of GL buffer + * @param other Source object to move from (will be invalidated) + */ + IndexBuffer(IndexBuffer&& other) noexcept; + + /** @brief Move assignment - transfers ownership of GL buffer + * @param other Source object to move from (will be invalidated) + * @note Releases any existing GL buffer before taking ownership + */ + auto operator=(IndexBuffer&& other) noexcept -> IndexBuffer&; + + private: + uint32_t m_RendererID; ///< OpenGL buffer ID + uint32_t m_Count; ///< Number of indices in the buffer + public: + /** + * @brief Construct an index buffer + * @param data Pointer to index data + * @param count Number of indices + */ + IndexBuffer(const uint32_t* data, uint32_t count); + + /** + * @brief Destructor - deletes the index buffer + */ + ~IndexBuffer(); + + /** + * @brief Bind this index buffer to the current OpenGL context + */ + auto bind() const -> void; + + /** + * @brief Unbind this index buffer from the current OpenGL context + */ + auto unbind() const -> void; + + /** + * @brief Get the number of indices in this buffer + * @return Number of indices + */ + [[nodiscard]] inline auto get_count() const -> uint32_t { return m_Count; } + }; + +} // namespace gkit::graphic::opengl::buffer \ No newline at end of file diff --git a/include/gkit/graphic/opengl/RenderBuffer.hpp b/include/gkit/graphic/opengl/RenderBuffer.hpp new file mode 100644 index 0000000..7edf75f --- /dev/null +++ b/include/gkit/graphic/opengl/RenderBuffer.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include + +/** + * @brief Render buffer wrapper for OpenGL renderbuffer objects + * + * A RenderBuffer is an off-screen buffer that stores rendered images but + * cannot be directly sampled by shaders. Typically used for depth and + * stencil attachments in a FrameBuffer. + */ +namespace gkit::graphic::opengl::buffer{ + + class RenderBuffer { + public: + RenderBuffer(const RenderBuffer&) = delete; + RenderBuffer& operator=(const RenderBuffer&) = delete; + + /** @brief Move constructor - transfers ownership of GL renderbuffer + * @param other Source object to move from (will be invalidated) + */ + RenderBuffer(RenderBuffer&& other) noexcept; + + /** @brief Move assignment - transfers ownership of GL renderbuffer + * @param other Source object to move from (will be invalidated) + * @note Releases any existing GL renderbuffer before taking ownership + */ + auto operator=(RenderBuffer&& other) noexcept -> RenderBuffer&; + + private: + uint32_t m_RendererID; ///< OpenGL renderbuffer ID + public: + /** + * @brief Construct a renderbuffer + */ + RenderBuffer(int width, int height); + + /** + * @brief Destructor - deletes the renderbuffer + */ + ~RenderBuffer(); + + /** + * @brief Bind this renderbuffer to the current OpenGL context + */ + auto bind() const -> void; + + /** + * @brief Unbind this renderbuffer from the current OpenGL context + */ + auto unbind() const -> void; + + [[nodiscard]] auto get_render_id() const -> uint32_t { return m_RendererID; } + }; + +} // namespace gkit::graphic::opengl::buffer \ No newline at end of file diff --git a/include/gkit/graphic/opengl/StateManager.hpp b/include/gkit/graphic/opengl/StateManager.hpp new file mode 100644 index 0000000..0a9b7af --- /dev/null +++ b/include/gkit/graphic/opengl/StateManager.hpp @@ -0,0 +1,184 @@ +#pragma once + +#include "config.hpp" +#include "gkit/core/scene/singleton.hpp" + +#include + +#include + +namespace gkit::graphic::opengl { + + /** + * @brief OpenGL state manager with dirty flag mechanism + * + * Tracks current OpenGL state and only calls GL functions when state actually changes. + * Uses singleton pattern for global access. + */ + class StateManager : public core::scene::Singleton { + public: + StateManager() = default; + + /** @brief Depth test state structure */ + struct DepthState { + bool enabled = false; ///< Whether depth test is enabled + CompareFunc compareFunc = CompareFunc::Less; ///< Depth comparison function + bool writeMask = true; ///< Depth write mask + }; + + /** @brief Blend state structure */ + struct BlendState { + bool enabled = false; ///< Whether blending is enabled + BlendFunc srcRGB = BlendFunc::One; ///< Source RGB blend factor + BlendFunc dstRGB = BlendFunc::Zero; ///< Destination RGB blend factor + BlendFunc srcAlpha = BlendFunc::One; ///< Source alpha blend factor + BlendFunc dstAlpha = BlendFunc::Zero; ///< Destination alpha blend factor + BlendEquation equation = BlendEquation::Add; ///< Blend equation + }; + + /** @brief Cull face state structure */ + struct CullFaceState { + bool enabled = false; ///< Whether cull face is enabled + CullFaceMode mode = CullFaceMode::Back; ///< Cull face mode + FrontFace frontFace = FrontFace::CounterClockwise;///< Front face winding order + }; + + /** @brief Stencil state structure */ + struct StencilState { + bool enabled = false; ///< Whether stencil test is enabled + CompareFunc compareFunc = CompareFunc::Always; ///< Stencil comparison function + uint32_t ref = 0; ///< Stencil reference value + uint32_t readMask = 0xFF; ///< Stencil read mask + uint32_t writeMask = 0xFF; ///< Stencil write mask + StencilOp fail = StencilOp::Keep; ///< Stencil fail operation + StencilOp zFail = StencilOp::Keep; ///< Stencil depth fail operation + StencilOp zPass = StencilOp::Keep; ///< Stencil depth pass operation + }; + + /** @brief Enable or disable depth testing + * @param enable True to enable depth testing, false to disable + */ + auto set_depth_test(bool enable) -> void; + + /** @brief Set depth test compare function + * @param func The comparison function to use + */ + auto set_depth_func(CompareFunc func) -> void; + + /** @brief Set depth write mask + * @param write True to enable depth writes, false to disable + */ + auto set_depth_mask(bool write) -> void; + + /** @brief Enable or disable blending + * @param enable True to enable blending, false to disable + */ + auto set_blend(bool enable) -> void; + + /** @brief Set blend factors for RGB and Alpha + * @param srcRGB Source RGB blend factor + * @param dstRGB Destination RGB blend factor + * @param srcAlpha Source alpha blend factor + * @param dstAlpha Destination alpha blend factor + */ + auto set_blend_func(BlendFunc srcRGB, BlendFunc dstRGB, BlendFunc srcAlpha, BlendFunc dstAlpha) -> void; + + /** @brief Set blend equation + * @param equation The blend equation to use + */ + auto set_blend_equation(BlendEquation equation) -> void; + + /** @brief Enable or disable face culling + * @param enable True to enable culling, false to disable + */ + auto set_cull_face(bool enable) -> void; + + /** @brief Set cull face mode + * @param mode The cull face mode + */ + auto set_cull_face_mode(CullFaceMode mode) -> void; + + /** @brief Set front face winding order + * @param frontFace The front face winding order + */ + auto set_front_face(FrontFace frontFace) -> void; + + /** @brief Enable or disable stencil testing + * @param enable True to enable stencil testing, false to disable + */ + auto set_stencil_test(bool enable) -> void; + + /** @brief Set stencil state + * @param func Stencil comparison function + * @param ref Stencil reference value + * @param mask Stencil read mask + */ + auto set_stencil(CompareFunc func, uint32_t ref, uint32_t mask) -> void; + + /** @brief Set stencil write mask + * @param mask Stencil write mask + */ + auto set_stencil_mask(uint32_t mask) -> void; + + /** @brief Set stencil operations + * @param fail Operation when stencil test fails + * @param zFail Operation when stencil passes but depth fails + * @param zPass Operation when both stencil and depth pass + */ + auto set_stencil_op(StencilOp fail, StencilOp zFail, StencilOp zPass) -> void; + + /** @brief Apply all dirty states to OpenGL */ + auto apply() -> void; + + /** @brief Force apply all states (ignore dirty flags) */ + auto force_apply_all() -> void; + + /** @brief Get current depth state + * @return Reference to the current depth state + */ + [[nodiscard]] auto get_depth_state() const -> const DepthState&; + + /** @brief Get current blend state + * @return Reference to the current blend state + */ + [[nodiscard]] auto get_blend_state() const -> const BlendState&; + + /** @brief Get current cull face state + * @return Reference to the current cull face state + */ + [[nodiscard]] auto get_cull_face_state() const -> const CullFaceState&; + + /** @brief Get current stencil state + * @return Reference to the current stencil state + */ + [[nodiscard]] auto get_stencil_state() const -> const StencilState&; + + private: + /** @brief Apply depth state if dirty */ + auto apply_depth_state() -> void; + + /** @brief Apply blend state if dirty */ + auto apply_blend_state() -> void; + + /** @brief Apply cull face state if dirty */ + auto apply_cull_face_state() -> void; + + /** @brief Apply stencil state if dirty */ + auto apply_stencil_state() -> void; + + /// @brief Current shadow states + DepthState m_DepthState; ///< Current depth state + BlendState m_BlendState; ///< Current blend state + CullFaceState m_CullFaceState; ///< Current cull face state + StencilState m_StencilState; ///< Current stencil state + + /// @brief Dirty flags bitmask + uint32_t m_DirtyFlags = 0; ///< Bitmask indicating which states need updating + + static constexpr uint32_t DIRTY_DEPTH = 1 << 0; ///< Dirty flag for depth state + static constexpr uint32_t DIRTY_BLEND = 1 << 1; ///< Dirty flag for blend state + static constexpr uint32_t DIRTY_CULL = 1 << 2; ///< Dirty flag for cull face state + static constexpr uint32_t DIRTY_STENCIL = 1 << 3; ///< Dirty flag for stencil state + }; + +} // namespace gkit::graphic::opengl diff --git a/include/gkit/graphic/opengl/Texture.hpp b/include/gkit/graphic/opengl/Texture.hpp new file mode 100644 index 0000000..39f03b2 --- /dev/null +++ b/include/gkit/graphic/opengl/Texture.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include "config.hpp" + +#include +#include +#include + +/** + * @brief Texture wrapper for OpenGL texture objects + * + * A Texture represents image data that can be sampled by shaders. + * Supports 2D textures, cube maps, and framebuffer textures. + */ +namespace gkit::graphic::opengl{ + + class Texture { + public: + Texture(const Texture&) = delete; + Texture& operator=(const Texture&) = delete; + + /** @brief Move constructor - transfers ownership of GL texture + * @param other Source object to move from (will be invalidated) + */ + Texture(Texture&& other) noexcept; + + /** @brief Move assignment - transfers ownership of GL texture + * @param other Source object to move from (will be invalidated) + * @note Releases any existing GL texture before taking ownership + */ + auto operator=(Texture&& other) noexcept -> Texture&; + + private: + uint32_t m_RendererID; ///< OpenGL texture ID + std::string m_FilePath; ///< Path to the texture file + unsigned char* m_LocalBuffer; ///< Local buffer for texture data + int m_Width; ///< Texture width in pixels + int m_Height; ///< Texture height in pixels + int m_BPP; ///< Bits per pixel + TextureType m_Type; ///< Texture type (2D, CubeMap, FrameBuffer) + + public: + /** + * @brief Construct a texture (deprecated) + * @param path Path to the texture file (can be empty for framebuffer textures) + * @param type Texture type (TEXTURE_2D, TEXTURE_CUBE_MAP, TEXTURE_FRAMEBUFFER) + * @deprecated Use TextureType enum to specify texture type instead + */ + [[deprecated("Use TextureType to specify texture type")]] + Texture(const std::string& path, TextureType type = TextureType::TEXTURE_2D); + + /** + * @brief Destructor - deletes the texture + */ + ~Texture(); + + /** + * @brief Bind this texture to a specific slot + * @param slot Texture slot (0-15) + */ + auto bind(unsigned int slot = 0) const -> void; + + /** + * @brief Unbind this texture + */ + auto unbind() const -> void; + + /** + * @brief Get the texture width + * @return Width in pixels + */ + [[nodiscard]] inline auto get_width() const -> int { return m_Width; } + + /** + * @brief Get the texture height + * @return Height in pixels + */ + [[nodiscard]] inline auto get_height() const -> int { return m_Height; } + + /** + * @brief Get the Render ID object + * @return ID + */ + [[nodiscard]] inline auto get_render_id() const -> uint32_t { return m_RendererID; } + + private: + /** + * @brief Cube map face file names + */ + inline static const std::vector faces = { + "right.jpg", + "left.jpg", + "top.jpg", + "bottom.jpg", + "front.jpg", + "back.jpg" + }; + }; + +} // namespace gkit::graphic::opengl \ No newline at end of file diff --git a/include/gkit/graphic/opengl/VertexArray.hpp b/include/gkit/graphic/opengl/VertexArray.hpp new file mode 100644 index 0000000..c7c6a59 --- /dev/null +++ b/include/gkit/graphic/opengl/VertexArray.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include "VertexBufferLayout.hpp" +#include "VertexBuffer.hpp" + +#include + +/** + * @brief Vertex array wrapper for OpenGL vertex array objects (VAO) + * + * A VertexArray stores the configuration of vertex attributes and their + * associated buffers, binding them together for efficient rendering. + */ +namespace gkit::graphic::opengl { + + class VertexArray { + public: + VertexArray(const VertexArray&) = delete; + VertexArray& operator=(const VertexArray&) = delete; + + /** @brief Move constructor - transfers ownership of GL vertex array + * @param other Source object to move from (will be invalidated) + */ + VertexArray(VertexArray&& other) noexcept; + + /** @brief Move assignment - transfers ownership of GL vertex array + * @param other Source object to move from (will be invalidated) + * @note Releases any existing GL vertex array before taking ownership + */ + auto operator=(VertexArray&& other) noexcept -> VertexArray&; + + public: + /** + * @brief Construct a vertex array object + */ + VertexArray(); + + /** + * @brief Destructor - deletes the vertex array object + */ + ~VertexArray(); + + /** + * @brief Add a vertex buffer with a layout + * @param vb Vertex buffer to add + * @param layout Layout defining the vertex attributes + */ + auto add_buffer(const buffer::VertexBuffer& vb, const buffer::VertexBufferLayout& layout) -> void; + + /** + * @brief Add an instance buffer for instanced rendering + * @param vb Vertex buffer containing instance data + */ + auto add_instance_buffer(const buffer::VertexBuffer& vb) -> void; + + /** + * @brief Bind this vertex array to the current OpenGL context + */ + auto bind() const -> void; + + /** + * @brief Unbind this vertex array from the current OpenGL context + */ + auto unbind() const -> void; + + private: + uint32_t m_RendererID; ///< OpenGL vertex array ID + uint32_t m_AttribIndex = 0; ///< Current attribute index for adding new attributes + }; + +} // namespace gkit::graphic::opengl \ No newline at end of file diff --git a/include/gkit/graphic/opengl/VertexBuffer.hpp b/include/gkit/graphic/opengl/VertexBuffer.hpp new file mode 100644 index 0000000..00e208a --- /dev/null +++ b/include/gkit/graphic/opengl/VertexBuffer.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include + +/** + * @brief Vertex buffer wrapper for OpenGL vertex buffer objects (VBO) + * + * A VertexBuffer stores vertex data on the GPU, containing attributes + * such as position, color, texture coordinates, etc. + */ +namespace gkit::graphic::opengl::buffer { + + class VertexBuffer { + public: + VertexBuffer(const VertexBuffer&) = delete; + VertexBuffer& operator=(const VertexBuffer&) = delete; + + /** @brief Move constructor - transfers ownership of GL buffer + * @param other Source object to move from (will be invalidated) + */ + VertexBuffer(VertexBuffer&& other) noexcept; + + /** @brief Move assignment - transfers ownership of GL buffer + * @param other Source object to move from (will be invalidated) + * @note Releases any existing GL buffer before taking ownership + */ + auto operator=(VertexBuffer&& other) noexcept -> VertexBuffer&; + + private: + uint32_t m_RendererID; ///< OpenGL buffer ID + uint32_t m_Size; ///< Buffer size in bytes + public: + /** + * @brief Construct a vertex buffer + * @param data Pointer to vertex data + * @param size Size of the data in bytes + * @param Dynamic If true, the buffer will be updated frequently (GL_DYNAMIC_DRAW) + */ + VertexBuffer(const void* data, uint32_t size, bool Dynamic = false); + + /** + * @brief Destructor - deletes the vertex buffer + */ + ~VertexBuffer(); + + /** + * @brief Bind this vertex buffer to the current OpenGL context + */ + auto bind() const -> void; + + /** + * @brief Unbind this vertex buffer from the current OpenGL context + */ + auto unbind() const -> void; + + /** + * @brief Update all buffer data + * @param data Pointer to the new data + * @param size Size of the data in bytes (if size is same, uses SubData; if different, reallocates) + */ + auto update_data(const void* data, uint32_t size) -> void; + + /** + * @brief Update partial buffer data + * @param offset Offset in bytes from the start of the buffer + * @param data Pointer to the new data + * @param size Size of the data in bytes + */ + auto update_sub_data(uint32_t offset, const void* data, uint32_t size) -> void; + + }; + +} // namespace gkit::graphic::opengl::buffer \ No newline at end of file diff --git a/include/gkit/graphic/opengl/VertexBufferLayout.hpp b/include/gkit/graphic/opengl/VertexBufferLayout.hpp new file mode 100644 index 0000000..915eb5b --- /dev/null +++ b/include/gkit/graphic/opengl/VertexBufferLayout.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include + +#include +#include +#include + +/** + * @brief Structure representing a single element in a vertex buffer layout + * + * Describes one attribute in the vertex data, including its type, count, + * and whether it should be normalized. + */ +namespace gkit::graphic::opengl::buffer { + + struct VertexBufferElement { + uint32_t type; ///< OpenGL data type (GL_FLOAT, GL_UNSIGNED_INT, etc.) + uint32_t count; ///< Number of components in this element + unsigned char normalized; ///< Whether the data should be normalized + + /** + * @brief Get the size in bytes of an OpenGL data type + * @param type OpenGL data type + * @return Size in bytes + */ + static constexpr auto get_size_of_type(uint32_t type) -> uint32_t { + switch (type) { + case GL_FLOAT: return 4; + case GL_UNSIGNED_INT: return 4; + case GL_UNSIGNED_BYTE: return 1; + default: assert(false && "Unknown vertex buffer element type"); return 0; + } + } + }; + + /** + * @brief Vertex buffer layout class for defining vertex attribute configurations + * + * Defines how vertex data is organized in memory, specifying the type and + * count of each attribute and the stride between consecutive vertices. + */ + class VertexBufferLayout { + private: + std::vector m_Elements; ///< List of vertex elements + uint32_t m_Stride = 0; ///< Bytes between consecutive vertices + public: + VertexBufferLayout() = default; + + /** + * @brief Add an element to the layout (unsupported type - compile-time error) + * @tparam T Data type + * @param count Number of components + */ + template + void push(uint32_t count) { + static_assert(sizeof(T) == 0, "Unsupported type for VertexBufferLayout::push"); + } + + /** + * @brief Get all elements in this layout (by const reference - zero copy) + * @return Const reference to vector of vertex buffer elements + */ + [[nodiscard]] inline auto get_elements() const -> const std::vector& { return m_Elements; } + + /** + * @brief Get the stride (bytes between vertices) + * @return Stride in bytes + */ + [[nodiscard]] inline auto get_stride() const -> uint32_t { return m_Stride; } + }; + + /** + * @brief Template specialization for pushing float elements + * @param count Number of float components (1-4) + */ + template<> + inline void VertexBufferLayout::push(uint32_t count) { + assert(count > 0 && "VertexBufferLayout::push count must be greater than 0"); + m_Elements.push_back({ .type = GL_FLOAT, .count = count, .normalized = GL_FALSE }); + m_Stride += VertexBufferElement::get_size_of_type(GL_FLOAT) * count; + } + + /** + * @brief Template specialization for pushing unsigned int elements + * @param count Number of unsigned int components + */ + template<> + inline void VertexBufferLayout::push(uint32_t count) { + assert(count > 0 && "VertexBufferLayout::push count must be greater than 0"); + m_Elements.push_back({ .type = GL_UNSIGNED_INT, .count = count, .normalized = GL_FALSE }); + m_Stride += VertexBufferElement::get_size_of_type(GL_UNSIGNED_INT) * count; + } + + /** + * @brief Template specialization for pushing unsigned byte elements + * @param count Number of unsigned byte components + */ + template<> + inline void VertexBufferLayout::push(uint32_t count) { + assert(count > 0 && "VertexBufferLayout::push count must be greater than 0"); + m_Elements.push_back({ .type = GL_UNSIGNED_BYTE, .count = count, .normalized = GL_TRUE }); + m_Stride += VertexBufferElement::get_size_of_type(GL_UNSIGNED_BYTE) * count; + } + +} // namespace gkit::graphic::opengl::buffer \ No newline at end of file diff --git a/include/gkit/graphic/opengl/config.hpp b/include/gkit/graphic/opengl/config.hpp new file mode 100644 index 0000000..7605798 --- /dev/null +++ b/include/gkit/graphic/opengl/config.hpp @@ -0,0 +1,136 @@ +#pragma once +#ifndef CONFIG_H +#define CONFIG_H + +namespace gkit::graphic::opengl { + + const unsigned int SCR_WIDTH = 500; + const unsigned int SCR_HEIGHT = 500; + + /** + * @brief texture pattern + */ + enum class TextureType { + TEXTURE_2D, + TEXTURE_CUBE_MAP, + TEXTURE_FRAMEBUFFER + }; + + /** + * @brief Compare Functions + */ + enum class CompareFunc { + Never = 0x0200, // GL_NEVER + Less = 0x0201, // GL_LESS + Equal = 0x0202, // GL_EQUAL + Lequal = 0x0203, // GL_LEQUAL + Greater = 0x0204, // GL_GREATER + Gequal = 0x0205, // GL_GEQUAL + Notequal = 0x0206, // GL_NOTEQUAL + Always = 0x0207, // GL_ALWAYS + }; + + /** + * @brief Blend Functions + */ + enum class BlendFunc { + Zero = 0, // GL_ZERO + One = 1, // GL_ONE + SrcColor = 0x0300, // GL_SRC_COLOR + OneMinusSrcColor = 0x0301, // GL_ONE_MINUS_SRC_COLOR + DstColor = 0x0306, // GL_DST_COLOR + OneMinusDstColor = 0x0307, // GL_ONE_MINUS_DST_COLOR + SrcAlpha = 0x0302, // GL_SRC_ALPHA + OneMinusSrcAlpha = 0x0303, // GL_ONE_MINUS_SRC_ALPHA + DstAlpha = 0x0304, // GL_DST_ALPHA + OneMinusDstAlpha = 0x0305, // GL_ONE_MINUS_DST_ALPHA + ConstantColor = 0x8001, // GL_CONSTANT_COLOR + OneMinusConstantColor = 0x8002, // GL_ONE_MINUS_CONSTANT_COLOR + ConstantAlpha = 0x8003, // GL_CONSTANT_ALPHA + OneMinusConstantAlpha = 0x8004, // GL_ONE_MINUS_CONSTANT_ALPHA + }; + + /** + * @brief Blend Equations + */ + enum class BlendEquation { + Add = 0x8006, // GL_FUNC_ADD + Subtract = 0x800A, // GL_FUNC_SUBTRACT + ReverseSubtract = 0x800B, // GL_FUNC_REVERSE_SUBTRACT + Min = 0x8007, // GL_MIN + Max = 0x8008, // GL_MAX + }; + + /** + * @brief Cull Face Modes + */ + enum class CullFaceMode { + Front = 0x0404, // GL_FRONT + Back = 0x0405, // GL_BACK + FrontAndBack = 0x0408, // GL_FRONT_AND_BACK + }; + + /** + * @brief Front Face Winding + */ + enum class FrontFace { + Clockwise = 0x0900, // GL_CW + CounterClockwise = 0x0901, // GL_CCW + }; + + /** + * @brief Stencil Operations + */ + enum class StencilOp { + Keep = 0x1E00, // GL_KEEP + Zero = 0, // GL_ZERO + Replace = 0x1E01, // GL_REPLACE + Incr = 0x1E02, // GL_INCR + IncrWrap = 0x8507, // GL_INCR_WRAP + Decr = 0x1E03, // GL_DECR + DecrWrap = 0x8508, // GL_DECR_WRAP + Invert = 0x150A, // GL_INVERT + }; + + /** + * @brief Clear Options + */ + enum class ClearFlags : unsigned int { + Color = 0x00004000, ///< Clear Color (GL_COLOR_BUFFER_BIT) + Depth = 0x00000100, ///< Clear Depth (GL_DEPTH_BUFFER_BIT) + Stencil = 0x00000400, ///< Clear Stencil (GL_STENCIL_BUFFER_BIT) + + ColorDepth = Color | Depth, ///< Color + Depth + All = Color | Depth | Stencil ///< Clear All + }; + + constexpr auto operator|(ClearFlags a, ClearFlags b) noexcept -> ClearFlags { + return static_cast( + static_cast(a) | static_cast(b) + ); + } + +} // namespace gkit::graphic::opengl + +namespace gkit::graphic::opengl::viewport { + + /** + * @brief Set viewport with custom position and size + * + * @param x Left coordinate + * @param y Bottom coordinate + * @param width Viewport width + * @param height Viewport height + */ + auto set_viewport(int x, int y, int width, int height) -> void; + + /** + * @brief Set viewport with custom size (origin at 0, 0) + * + * @param width Viewport width + * @param height Viewport height + */ + auto set_viewport(int width, int height) -> void; + +} // namespace gkit::graphic::opengl::viewport +#endif \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c76ee89..6fff473 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,6 +18,9 @@ list (APPEND GKIT_OBJS $) add_subdirectory(misc) list (APPEND GKIT_OBJS $) +add_subdirectory(graphic) +list (APPEND GKIT_OBJS $) + add_library(${PROJECT_NAME} SHARED ${GKIT_OBJS}) add_library(${PROJECT_NAME}_static STATIC ${GKIT_OBJS}) @@ -27,8 +30,9 @@ list (APPEND GKIT_LIBS ${PROJECT_NAME}_static) # link third party static libraries foreach(GKIT_LIB ${GKIT_LIBS}) - target_link_libraries(${GKIT_LIB} PRIVATE - SDL3::SDL3-static + target_link_libraries(${GKIT_LIB} PRIVATE + SDL3::SDL3-static SDL3_image::SDL3_image-static + glad_gl ) endforeach() diff --git a/src/graphic/CMakeLists.txt b/src/graphic/CMakeLists.txt new file mode 100644 index 0000000..fb5dcc7 --- /dev/null +++ b/src/graphic/CMakeLists.txt @@ -0,0 +1,24 @@ +set (GKIT_GRAPHIC "gkit_graphic") + +set (GRAPHIC_SRC + "./Renderer.cpp" + "./Shader.cpp" + + # OpenGL module + "./opengl/FrameBuffer.cpp" + "./opengl/IndexBuffer.cpp" + "./opengl/RenderBuffer.cpp" + "./opengl/StateManager.cpp" + "./opengl/Texture.cpp" + "./opengl/VertexArray.cpp" + "./opengl/VertexBuffer.cpp" + "./opengl/config.cpp" +) + +add_library(${GKIT_GRAPHIC} OBJECT ${GRAPHIC_SRC}) + +target_include_directories(${GKIT_GRAPHIC} PRIVATE ${CMAKE_SOURCE_DIR}/include) # include for header file +target_include_directories(${GKIT_GRAPHIC} PRIVATE ${CMAKE_SOURCE_DIR}/src) # top include for source file +target_include_directories(${GKIT_GRAPHIC} PRIVATE ${CMAKE_SOURCE_DIR}/third_party/glad_generated/include) # glad generated headers + +target_link_libraries(${GKIT_GRAPHIC} PRIVATE glad_gl) diff --git a/src/graphic/Renderer.cpp b/src/graphic/Renderer.cpp new file mode 100644 index 0000000..b731912 --- /dev/null +++ b/src/graphic/Renderer.cpp @@ -0,0 +1,26 @@ +#include "gkit/graphic/opengl/VertexArray.hpp" +#include "gkit/graphic/opengl/IndexBuffer.hpp" +#include "gkit/graphic/opengl/config.hpp" +#include "gkit/graphic/Renderer.hpp" +#include "gkit/graphic/Shader.hpp" + +#include + +auto gkit::graphic::Renderer::clear(opengl::ClearFlags flags) const -> void { + auto mask = static_cast(flags); + glClear(mask); +} + +auto gkit::graphic::Renderer::draw(const gkit::graphic::opengl::VertexArray& va, const gkit::graphic::opengl::buffer::IndexBuffer& ib, const gkit::graphic::Shader& shader) const -> void { + shader.bind(); + va.bind(); + ib.bind(); + glDrawElements(GL_TRIANGLES, ib.get_count(), GL_UNSIGNED_INT, nullptr); +} + +auto gkit::graphic::Renderer::draw_instance(const gkit::graphic::opengl::VertexArray& va, const gkit::graphic::opengl::buffer::IndexBuffer& ib, const gkit::graphic::Shader& shader, uint32_t instanceCount) const -> void { + shader.bind(); + va.bind(); + ib.bind(); + glDrawElementsInstanced(GL_TRIANGLES, ib.get_count(), GL_UNSIGNED_INT, nullptr, instanceCount); +} \ No newline at end of file diff --git a/src/graphic/Shader.cpp b/src/graphic/Shader.cpp new file mode 100644 index 0000000..fa7b908 --- /dev/null +++ b/src/graphic/Shader.cpp @@ -0,0 +1,215 @@ +#include "gkit/graphic/Shader.hpp" +#include "gkit/utils/log.hpp" + +#include + +#include +#include + +gkit::graphic::Shader::Shader(const std::string& filepath) + : m_FilePath(filepath), m_RendererID(0) { + ShaderProgramSource source = parse_shader(filepath); + m_RendererID = create_shader(source.vertexShader, source.fragmentShader); +} + +gkit::graphic::Shader::~Shader() { + glDeleteProgram(m_RendererID); +} + +gkit::graphic::Shader::Shader(Shader&& other) noexcept + : m_RendererID(other.m_RendererID) + , m_FilePath(std::move(other.m_FilePath)) + , m_UniformLocationCach(std::move(other.m_UniformLocationCach)) { + other.m_RendererID = 0; +} + +auto gkit::graphic::Shader::operator=(Shader&& other) noexcept -> Shader& { + if (this != &other) { + glDeleteProgram(m_RendererID); + m_RendererID = other.m_RendererID; + m_FilePath = std::move(other.m_FilePath); + m_UniformLocationCach = std::move(other.m_UniformLocationCach); + other.m_RendererID = 0; + } + return *this; +} + +auto gkit::graphic::Shader::create_shader(const std::string& vertexShader, const std::string& fragmentShader) -> uint32_t { + uint32_t program = glCreateProgram(); + uint32_t vs = compile_shader(GL_VERTEX_SHADER, vertexShader); + uint32_t fs = compile_shader(GL_FRAGMENT_SHADER, fragmentShader); + + if (vs == 0 || fs == 0) { + glDeleteProgram(program); + return 0; + } + + glAttachShader(program, vs); + glAttachShader(program, fs); + glLinkProgram(program); + + int linkResult; + glGetProgramiv(program, GL_LINK_STATUS, &linkResult); + if (linkResult == GL_FALSE) { + int length; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length); + char* message = (char*)alloca(length * sizeof(char)); + glGetProgramInfoLog(program, length, &length, message); + gkit::utils::Log::Message msg; + msg.level = gkit::utils::Log::LogLevel::Error; + msg.functions = static_cast(gkit::utils::Log::LogFunction::Both); + msg.message = "Failed to link shader program: " + std::string(message); + gkit::utils::Log::instance().log(std::move(msg)); + glDeleteProgram(program); + glDeleteShader(vs); + glDeleteShader(fs); + return 0; + } + + glDetachShader(program, vs); + glDetachShader(program, fs); + glDeleteShader(vs); + glDeleteShader(fs); + + return program; +} + +auto gkit::graphic::Shader::parse_shader(const std::string& filePath) -> ShaderProgramSource { + gkit::utils::Log::Message pathMsg; + pathMsg.level = gkit::utils::Log::LogLevel::Info; + pathMsg.functions = static_cast(gkit::utils::Log::LogFunction::Both); + pathMsg.message = "Parsing shader: " + filePath; + gkit::utils::Log::instance().log(std::move(pathMsg)); + std::ifstream stream(filePath); + + /** + * @brief shader enum type + */ + enum class ShaderType { + NONE = -1, + VERTEX = 0, + FRAGMENT = 1 + }; + + std::string line; + std::stringstream ss[2]; + ShaderType type = ShaderType::NONE; + while (getline(stream, line)) { + if (line.find("#shader") != std::string::npos) { + if (line.find("vertex") != std::string::npos) + type = ShaderType::VERTEX; + else if (line.find("fragment") != std::string::npos) + type = ShaderType::FRAGMENT; + } else if (type != ShaderType::NONE) { + ss[(int)type] << line << '\n'; + } + } + + if (ss[0].str().empty()) { + gkit::utils::Log::Message msg; + msg.level = gkit::utils::Log::LogLevel::Error; + msg.functions = static_cast(gkit::utils::Log::LogFunction::Both); + msg.message = "No vertex shader found in: " + filePath; + gkit::utils::Log::instance().log(std::move(msg)); + } + if (ss[1].str().empty()) { + gkit::utils::Log::Message msg; + msg.level = gkit::utils::Log::LogLevel::Error; + msg.functions = static_cast(gkit::utils::Log::LogFunction::Both); + msg.message = "No fragment shader found in: " + filePath; + gkit::utils::Log::instance().log(std::move(msg)); + } + + gkit::utils::Log::Message vsMsg; + vsMsg.level = gkit::utils::Log::LogLevel::Info; + vsMsg.functions = static_cast(gkit::utils::Log::LogFunction::Both); + vsMsg.message = ss[0].str(); + gkit::utils::Log::instance().log(std::move(vsMsg)); + + gkit::utils::Log::Message fsMsg; + fsMsg.level = gkit::utils::Log::LogLevel::Info; + fsMsg.functions = static_cast(gkit::utils::Log::LogFunction::Both); + fsMsg.message = ss[1].str(); + gkit::utils::Log::instance().log(std::move(fsMsg)); + + return { .vertexShader=ss[0].str(), .fragmentShader=ss[1].str() }; +} + +auto gkit::graphic::Shader::compile_shader(uint32_t type, const std::string& source) -> uint32_t { + uint32_t id = glCreateShader(type); + const char* src = source.c_str(); + glShaderSource(id, 1, &src, nullptr); + glCompileShader(id); + + int result; + glGetShaderiv(id, GL_COMPILE_STATUS, &result); + if (result == GL_FALSE) { + int length; + glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length); + char* message = (char*)alloca(length * sizeof(char)); + glGetShaderInfoLog(id, length, &length, message); + gkit::utils::Log::Message msg; + msg.level = gkit::utils::Log::LogLevel::Error; + msg.functions = static_cast(gkit::utils::Log::LogFunction::Both); + msg.message = "Failed to compile " + std::string(type == GL_VERTEX_SHADER ? "vertex" : "fragment") + " shader: " + message; + gkit::utils::Log::instance().log(std::move(msg)); + glDeleteShader(id); + return 0; + } + + return id; +} + +auto gkit::graphic::Shader::bind() const -> void { + glUseProgram(m_RendererID); +} +auto gkit::graphic::Shader::unbind() const -> void { + glUseProgram(0); +} + +auto gkit::graphic::Shader::set_uniform_1i(const std::string& name, int value) -> void { + glUniform1i(get_uniform_location(name), value); +} + +auto gkit::graphic::Shader::set_uniform_1f(const std::string& name, float value) -> void { + glUniform1f(get_uniform_location(name), value); +} + +auto gkit::graphic::Shader::set_uniform_4f(const std::string& name, float v0, float v1, float v2, float v3) -> void { + glUniform4f(get_uniform_location(name), v0, v1, v2, v3); +} + +auto gkit::graphic::Shader::set_uniform_mat_4f(const std::string& name, const float* matrix) -> void { + glUniformMatrix4fv(get_uniform_location(name), 1, GL_FALSE, matrix); +} + +auto gkit::graphic::Shader::set_uniform_mat_3f(const std::string& name, const float* matrix) -> void { + glUniformMatrix3fv(get_uniform_location(name), 1, GL_FALSE, matrix); +} + +auto gkit::graphic::Shader::set_uniform_vec_4f(const std::string& name, const float* vector4) -> void { + glUniform4fv(get_uniform_location(name), 1, vector4); +} + +auto gkit::graphic::Shader::set_uniform_vec_3f(const std::string& name, const float* vector3) -> void { + glUniform3fv(get_uniform_location(name), 1, vector3); +} + +auto gkit::graphic::Shader::set_uniform_1iv(const std::string& name, const int sz, const int* ind) -> void { + glUniform1iv(get_uniform_location(name), sz, ind); +} + +auto gkit::graphic::Shader::get_uniform_location(const std::string& name) -> int { + if (m_UniformLocationCach.find(name) != m_UniformLocationCach.end()) + return m_UniformLocationCach[name]; + int location = glGetUniformLocation(m_RendererID, name.c_str()); + if (location == -1) { + gkit::utils::Log::Message msg; + msg.level = gkit::utils::Log::LogLevel::Warning; + msg.functions = static_cast(gkit::utils::Log::LogFunction::Both); + msg.message = "Warning: uniform '" + name + "' doesn't exist!"; + gkit::utils::Log::instance().log(std::move(msg)); + } + m_UniformLocationCach[name] = location; + return location; +} \ No newline at end of file diff --git a/src/graphic/opengl/FrameBuffer.cpp b/src/graphic/opengl/FrameBuffer.cpp new file mode 100644 index 0000000..9b87a9d --- /dev/null +++ b/src/graphic/opengl/FrameBuffer.cpp @@ -0,0 +1,98 @@ +#include "gkit/graphic/opengl/FrameBuffer.hpp" +#include "gkit/graphic/opengl/Texture.hpp" +#include "gkit/utils/log.hpp" + +#include + +gkit::graphic::opengl::buffer::FrameBuffer::FrameBuffer(int width, int height) + : fb_width(width), fb_height(height) { + glGenFramebuffers(1, &m_RendererID); +} + +gkit::graphic::opengl::buffer::FrameBuffer::~FrameBuffer() { + if(m_RendererID != 0) { + glDeleteFramebuffers(1, &m_RendererID); + m_RendererID = 0; + } +} + +gkit::graphic::opengl::buffer::FrameBuffer::FrameBuffer(FrameBuffer&& other) noexcept + : m_RendererID(other.m_RendererID) + , fb_height(other.fb_height) + , fb_width(other.fb_width) + , leftX(other.leftX) + , bottomY(other.bottomY) { + other.m_RendererID = 0; +} + +auto gkit::graphic::opengl::buffer::FrameBuffer::operator=(FrameBuffer&& other) noexcept -> FrameBuffer& { + if (this != &other) { + if (m_RendererID != 0) { + glDeleteFramebuffers(1, &m_RendererID); + } + m_RendererID = other.m_RendererID; + fb_height = other.fb_height; + fb_width = other.fb_width; + leftX = other.leftX; + bottomY = other.bottomY; + other.m_RendererID = 0; + } + return *this; +} + +auto gkit::graphic::opengl::buffer::FrameBuffer::attach_color_texture(const gkit::graphic::opengl::Texture& texture, int slot) -> void { + bind(); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + slot, GL_TEXTURE_2D, texture.get_render_id(), 0); +} + +auto gkit::graphic::opengl::buffer::FrameBuffer::detach_color_texture(int slot) -> void { + bind(); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + slot, GL_TEXTURE_2D, 0, 0); +} + +auto gkit::graphic::opengl::buffer::FrameBuffer::attach_depth_stencil(const RenderBuffer& rbo) -> void { + bind(); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, rbo.get_render_id()); +} + +auto gkit::graphic::opengl::buffer::FrameBuffer::detach_depth_stencil() -> void { + bind(); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, 0); +} + +auto gkit::graphic::opengl::buffer::FrameBuffer::check() -> void { + bind(); + gkit::utils::Log::Message msg; + msg.functions = static_cast(gkit::utils::Log::LogFunction::Both); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + msg.level = gkit::utils::Log::LogLevel::Error; + msg.message = "ERROR::FRAMEBUFFER:: Framebuffer is not complete!"; + } else { + msg.level = gkit::utils::Log::LogLevel::Info; + msg.message = "FRAMEBUFFER:: Framebuffer is complete!"; + } + gkit::utils::Log::instance().log(std::move(msg)); + unbind(); +} + +auto gkit::graphic::opengl::buffer::FrameBuffer::set_viewport(int x, int y, int width, int height) -> void { + glViewport(x, y, width, height); +} + +auto gkit::graphic::opengl::buffer::FrameBuffer::set_viewport(int width, int height) -> void { + glViewport(leftX, bottomY, width, height); +} + +auto gkit::graphic::opengl::buffer::FrameBuffer::set_viewport() -> void { + glViewport(leftX, bottomY, fb_width, fb_height); +} + +auto gkit::graphic::opengl::buffer::FrameBuffer::bind() const -> void { + glBindFramebuffer(GL_FRAMEBUFFER, m_RendererID); +} + +auto gkit::graphic::opengl::buffer::FrameBuffer::unbind() const -> void { + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} \ No newline at end of file diff --git a/src/graphic/opengl/IndexBuffer.cpp b/src/graphic/opengl/IndexBuffer.cpp new file mode 100644 index 0000000..da1c27c --- /dev/null +++ b/src/graphic/opengl/IndexBuffer.cpp @@ -0,0 +1,42 @@ +#include "gkit/graphic/opengl/IndexBuffer.hpp" + +#include + +gkit::graphic::opengl::buffer::IndexBuffer::IndexBuffer(const uint32_t* data, uint32_t count) + : m_Count(count) { + glGenBuffers(1, &m_RendererID); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * sizeof(uint32_t), data, GL_STATIC_DRAW); +} +gkit::graphic::opengl::buffer::IndexBuffer::~IndexBuffer() { + if(m_RendererID != 0) { + glDeleteBuffers(1, &m_RendererID); + m_RendererID = 0; + } +} + +gkit::graphic::opengl::buffer::IndexBuffer::IndexBuffer(IndexBuffer&& other) noexcept + : m_RendererID(other.m_RendererID) + , m_Count(other.m_Count) { + other.m_RendererID = 0; +} + +auto gkit::graphic::opengl::buffer::IndexBuffer::operator=(IndexBuffer&& other) noexcept -> IndexBuffer& { + if (this != &other) { + if (m_RendererID != 0) { + glDeleteBuffers(1, &m_RendererID); + } + m_RendererID = other.m_RendererID; + m_Count = other.m_Count; + other.m_RendererID = 0; + } + return *this; +} + +auto gkit::graphic::opengl::buffer::IndexBuffer::bind() const -> void { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID); +} + +auto gkit::graphic::opengl::buffer::IndexBuffer::unbind() const -> void { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} \ No newline at end of file diff --git a/src/graphic/opengl/RenderBuffer.cpp b/src/graphic/opengl/RenderBuffer.cpp new file mode 100644 index 0000000..f7c2116 --- /dev/null +++ b/src/graphic/opengl/RenderBuffer.cpp @@ -0,0 +1,41 @@ +#include "gkit/graphic/opengl/RenderBuffer.hpp" + +#include + +gkit::graphic::opengl::buffer::RenderBuffer::RenderBuffer(int width, int height) { + glGenRenderbuffers(1, &m_RendererID); + glBindRenderbuffer(GL_RENDERBUFFER, m_RendererID); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); + unbind(); +} + +gkit::graphic::opengl::buffer::RenderBuffer::~RenderBuffer() { + if(m_RendererID != 0) { + glDeleteRenderbuffers(1, &m_RendererID); + m_RendererID = 0; + } +} + +gkit::graphic::opengl::buffer::RenderBuffer::RenderBuffer(RenderBuffer&& other) noexcept + : m_RendererID(other.m_RendererID) { + other.m_RendererID = 0; +} + +auto gkit::graphic::opengl::buffer::RenderBuffer::operator=(RenderBuffer&& other) noexcept -> RenderBuffer& { + if (this != &other) { + if (m_RendererID != 0) { + glDeleteRenderbuffers(1, &m_RendererID); + } + m_RendererID = other.m_RendererID; + other.m_RendererID = 0; + } + return *this; +} + +auto gkit::graphic::opengl::buffer::RenderBuffer::bind() const -> void { + glBindRenderbuffer(GL_RENDERBUFFER, m_RendererID); +} + +auto gkit::graphic::opengl::buffer::RenderBuffer::unbind() const -> void { + glBindRenderbuffer(GL_RENDERBUFFER, 0); +} \ No newline at end of file diff --git a/src/graphic/opengl/StateManager.cpp b/src/graphic/opengl/StateManager.cpp new file mode 100644 index 0000000..49b926e --- /dev/null +++ b/src/graphic/opengl/StateManager.cpp @@ -0,0 +1,190 @@ +#include "gkit/graphic/opengl/StateManager.hpp" + +namespace gkit::graphic::opengl { + + auto StateManager::set_depth_test(bool enable) -> void { + if (m_DepthState.enabled != enable) { + m_DepthState.enabled = enable; + m_DirtyFlags |= DIRTY_DEPTH; + } + } + + auto StateManager::set_depth_func(CompareFunc func) -> void { + if (m_DepthState.compareFunc != func) { + m_DepthState.compareFunc = func; + m_DirtyFlags |= DIRTY_DEPTH; + } + } + + auto StateManager::set_depth_mask(bool write) -> void { + if (m_DepthState.writeMask != write) { + m_DepthState.writeMask = write; + m_DirtyFlags |= DIRTY_DEPTH; + } + } + + auto StateManager::set_blend(bool enable) -> void { + if (m_BlendState.enabled != enable) { + m_BlendState.enabled = enable; + m_DirtyFlags |= DIRTY_BLEND; + } + } + + auto StateManager::set_blend_func(BlendFunc srcRGB, BlendFunc dstRGB, BlendFunc srcAlpha, BlendFunc dstAlpha) -> void { + if (m_BlendState.srcRGB != srcRGB || m_BlendState.dstRGB != dstRGB || + m_BlendState.srcAlpha != srcAlpha || m_BlendState.dstAlpha != dstAlpha) { + m_BlendState.srcRGB = srcRGB; + m_BlendState.dstRGB = dstRGB; + m_BlendState.srcAlpha = srcAlpha; + m_BlendState.dstAlpha = dstAlpha; + m_DirtyFlags |= DIRTY_BLEND; + } + } + + auto StateManager::set_blend_equation(BlendEquation equation) -> void { + if (m_BlendState.equation != equation) { + m_BlendState.equation = equation; + m_DirtyFlags |= DIRTY_BLEND; + } + } + + auto StateManager::set_cull_face(bool enable) -> void { + if (m_CullFaceState.enabled != enable) { + m_CullFaceState.enabled = enable; + m_DirtyFlags |= DIRTY_CULL; + } + } + + auto StateManager::set_cull_face_mode(CullFaceMode mode) -> void { + if (m_CullFaceState.mode != mode) { + m_CullFaceState.mode = mode; + m_DirtyFlags |= DIRTY_CULL; + } + } + + auto StateManager::set_front_face(FrontFace frontFace) -> void { + if (m_CullFaceState.frontFace != frontFace) { + m_CullFaceState.frontFace = frontFace; + m_DirtyFlags |= DIRTY_CULL; + } + } + + auto StateManager::set_stencil_test(bool enable) -> void { + if (m_StencilState.enabled != enable) { + m_StencilState.enabled = enable; + m_DirtyFlags |= DIRTY_STENCIL; + } + } + + auto StateManager::set_stencil(CompareFunc func, uint32_t ref, uint32_t mask) -> void { + if (m_StencilState.compareFunc != func || m_StencilState.ref != ref || m_StencilState.readMask != mask) { + m_StencilState.compareFunc = func; + m_StencilState.ref = ref; + m_StencilState.readMask = mask; + m_DirtyFlags |= DIRTY_STENCIL; + } + } + + auto StateManager::set_stencil_op(StencilOp fail, StencilOp zFail, StencilOp zPass) -> void { + if (m_StencilState.fail != fail || m_StencilState.zFail != zFail || m_StencilState.zPass != zPass) { + m_StencilState.fail = fail; + m_StencilState.zFail = zFail; + m_StencilState.zPass = zPass; + m_DirtyFlags |= DIRTY_STENCIL; + } + } + + auto StateManager::set_stencil_mask(uint32_t mask) -> void { + if (m_StencilState.writeMask != mask) { + m_StencilState.writeMask = mask; + m_DirtyFlags |= DIRTY_STENCIL; + } + } + + auto StateManager::apply() -> void { + if (m_DirtyFlags & DIRTY_DEPTH) { + apply_depth_state(); + } + if (m_DirtyFlags & DIRTY_BLEND) { + apply_blend_state(); + } + if (m_DirtyFlags & DIRTY_CULL) { + apply_cull_face_state(); + } + if (m_DirtyFlags & DIRTY_STENCIL) { + apply_stencil_state(); + } + m_DirtyFlags = 0; + } + + auto StateManager::force_apply_all() -> void { + m_DirtyFlags = DIRTY_DEPTH | DIRTY_BLEND | DIRTY_CULL | DIRTY_STENCIL; + apply(); + } + + auto StateManager::apply_depth_state() -> void { + if (m_DepthState.enabled) { + glEnable(GL_DEPTH_TEST); + } else { + glDisable(GL_DEPTH_TEST); + } + glDepthFunc(static_cast(m_DepthState.compareFunc)); + glDepthMask(m_DepthState.writeMask ? GL_TRUE : GL_FALSE); + } + + auto StateManager::apply_blend_state() -> void { + if (m_BlendState.enabled) { + glEnable(GL_BLEND); + } else { + glDisable(GL_BLEND); + } + glBlendFuncSeparate( + static_cast(m_BlendState.srcRGB), static_cast(m_BlendState.dstRGB), + static_cast(m_BlendState.srcAlpha), static_cast(m_BlendState.dstAlpha)); + glBlendEquation(static_cast(m_BlendState.equation)); + } + + auto StateManager::apply_cull_face_state() -> void { + if (m_CullFaceState.enabled) { + glEnable(GL_CULL_FACE); + } else { + glDisable(GL_CULL_FACE); + } + glCullFace(static_cast(m_CullFaceState.mode)); + glFrontFace(static_cast(m_CullFaceState.frontFace)); + } + + auto StateManager::apply_stencil_state() -> void { + if (m_StencilState.enabled) { + glEnable(GL_STENCIL_TEST); + } else { + glDisable(GL_STENCIL_TEST); + } + glStencilFunc( + static_cast(m_StencilState.compareFunc), + m_StencilState.ref, + m_StencilState.readMask); + glStencilOp( + static_cast(m_StencilState.fail), + static_cast(m_StencilState.zFail), + static_cast(m_StencilState.zPass)); + glStencilMask(m_StencilState.writeMask); + } + + auto StateManager::get_depth_state() const -> const DepthState& { + return m_DepthState; + } + + auto StateManager::get_blend_state() const -> const BlendState& { + return m_BlendState; + } + + auto StateManager::get_cull_face_state() const -> const CullFaceState& { + return m_CullFaceState; + } + + auto StateManager::get_stencil_state() const -> const StencilState& { + return m_StencilState; + } + +} // namespace gkit::graphic::opengl diff --git a/src/graphic/opengl/Texture.cpp b/src/graphic/opengl/Texture.cpp new file mode 100644 index 0000000..9b503b3 --- /dev/null +++ b/src/graphic/opengl/Texture.cpp @@ -0,0 +1,65 @@ +#include "gkit/graphic/opengl/Texture.hpp" + +//#include +#include + +//#include + +gkit::graphic::opengl::Texture::Texture(const std::string& path, TextureType type) + :m_RendererID(0), m_FilePath(path), m_LocalBuffer(nullptr), + m_Width(0), m_Height(0), m_BPP(0), m_Type(type) { + +} + +gkit::graphic::opengl::Texture::~Texture() { + if (m_RendererID != 0) { + glDeleteTextures(1, &m_RendererID); + m_RendererID = 0; + } +} + +gkit::graphic::opengl::Texture::Texture(Texture&& other) noexcept + : m_RendererID(other.m_RendererID) + , m_FilePath(std::move(other.m_FilePath)) + , m_LocalBuffer(other.m_LocalBuffer) + , m_Width(other.m_Width) + , m_Height(other.m_Height) + , m_BPP(other.m_BPP) + , m_Type(other.m_Type) { + other.m_RendererID = 0; + other.m_LocalBuffer = nullptr; +} + +auto gkit::graphic::opengl::Texture::operator=(Texture&& other) noexcept -> Texture& { + if (this != &other) { + glDeleteTextures(1, &m_RendererID); + m_RendererID = other.m_RendererID; + m_FilePath = std::move(other.m_FilePath); + m_LocalBuffer = other.m_LocalBuffer; + m_Width = other.m_Width; + m_Height = other.m_Height; + m_BPP = other.m_BPP; + m_Type = other.m_Type; + other.m_RendererID = 0; + other.m_LocalBuffer = nullptr; + } + return *this; +} + +auto gkit::graphic::opengl::Texture::bind(unsigned int slot) const -> void { + if (m_Type == TextureType::TEXTURE_CUBE_MAP) { + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_CUBE_MAP, m_RendererID); + } else if (m_Type == TextureType::TEXTURE_2D || m_Type == TextureType::TEXTURE_FRAMEBUFFER) { + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_2D, m_RendererID); + } +} + +auto gkit::graphic::opengl::Texture::unbind() const -> void { + if (m_Type == TextureType::TEXTURE_CUBE_MAP) { + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); + } else if (m_Type == TextureType::TEXTURE_2D || m_Type == TextureType::TEXTURE_FRAMEBUFFER) { + glBindTexture(GL_TEXTURE_2D, 0); + } +} \ No newline at end of file diff --git a/src/graphic/opengl/VertexArray.cpp b/src/graphic/opengl/VertexArray.cpp new file mode 100644 index 0000000..5e06c2b --- /dev/null +++ b/src/graphic/opengl/VertexArray.cpp @@ -0,0 +1,80 @@ +#include "gkit/graphic/opengl/VertexArray.hpp" +#include "gkit/graphic/opengl/VertexBuffer.hpp" +#include "gkit/graphic/opengl/VertexBufferLayout.hpp" +#include "gkit/math/vector4.hpp" +#include "gkit/math/matrix4.hpp" + +gkit::graphic::opengl::VertexArray::VertexArray() { + glGenVertexArrays(1, &m_RendererID); +} + +gkit::graphic::opengl::VertexArray::~VertexArray() { + if(m_RendererID != 0) { + glDeleteVertexArrays(1, &m_RendererID); + m_RendererID = 0; + } +} + +gkit::graphic::opengl::VertexArray::VertexArray(VertexArray&& other) noexcept + : m_RendererID(other.m_RendererID) + , m_AttribIndex(other.m_AttribIndex) { + other.m_RendererID = 0; +} + +auto gkit::graphic::opengl::VertexArray::operator=(VertexArray&& other) noexcept -> VertexArray& { + if (this != &other) { + if (m_RendererID != 0) { + glDeleteVertexArrays(1, &m_RendererID); + } + m_RendererID = other.m_RendererID; + m_AttribIndex = other.m_AttribIndex; + other.m_RendererID = 0; + } + return *this; +} + +auto gkit::graphic::opengl::VertexArray::add_buffer(const buffer::VertexBuffer& vb, const buffer::VertexBufferLayout& layout) -> void { + bind(); + vb.bind(); + const auto& elements = layout.get_elements(); + size_t offset = 0; + for (int i = 0; i < elements.size(); i++) { + const auto& element = elements[i]; + glEnableVertexAttribArray(m_AttribIndex); + //Specify the reading rules for the incoming data, the meaning of the arrays (coordinates, textures, etc.) + glVertexAttribPointer(m_AttribIndex, element.count, element.type, + element.normalized, layout.get_stride(), (const void*)offset); + offset += element.count * buffer::VertexBufferElement::get_size_of_type(element.type); + m_AttribIndex++; + } + +} + +auto gkit::graphic::opengl::VertexArray::add_instance_buffer(const buffer::VertexBuffer& vb) -> void { + bind(); + vb.bind(); + + size_t vec4Size = sizeof(gkit::math::Vector4); + + for (uint32_t i = 0; i < 4; i++) { + glEnableVertexAttribArray(m_AttribIndex); + glVertexAttribPointer( + m_AttribIndex, + 4, + GL_FLOAT, + GL_FALSE, + sizeof(gkit::math::Matrix4), + (void*)(i * vec4Size) + ); + glVertexAttribDivisor(m_AttribIndex, 1); + m_AttribIndex++; + } +} + +auto gkit::graphic::opengl::VertexArray::bind() const -> void { + glBindVertexArray(m_RendererID); +} + +auto gkit::graphic::opengl::VertexArray::unbind() const -> void { + glBindVertexArray(0); +} diff --git a/src/graphic/opengl/VertexBuffer.cpp b/src/graphic/opengl/VertexBuffer.cpp new file mode 100644 index 0000000..8eeb6dc --- /dev/null +++ b/src/graphic/opengl/VertexBuffer.cpp @@ -0,0 +1,62 @@ +#include "gkit/graphic/opengl/VertexBuffer.hpp" + +#include + +gkit::graphic::opengl::buffer::VertexBuffer::VertexBuffer(const void* data, uint32_t size, bool Dynamic) { + glGenBuffers(1, &m_RendererID); + glBindBuffer(GL_ARRAY_BUFFER, m_RendererID); + if (!Dynamic){ + glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW); + } else { + glBufferData(GL_ARRAY_BUFFER, size, nullptr, GL_DYNAMIC_DRAW); + } + m_Size = size; +} + +gkit::graphic::opengl::buffer::VertexBuffer::~VertexBuffer() { + if(m_RendererID != 0) { + glDeleteBuffers(1, &m_RendererID); + m_RendererID = 0; + } +} + +gkit::graphic::opengl::buffer::VertexBuffer::VertexBuffer(VertexBuffer&& other) noexcept + : m_RendererID(other.m_RendererID) + , m_Size(other.m_Size) { + other.m_RendererID = 0; +} + +auto gkit::graphic::opengl::buffer::VertexBuffer::operator=(VertexBuffer&& other) noexcept -> VertexBuffer& { + if (this != &other) { + if (m_RendererID != 0) { + glDeleteBuffers(1, &m_RendererID); + } + m_RendererID = other.m_RendererID; + m_Size = other.m_Size; + other.m_RendererID = 0; + } + return *this; +} + +auto gkit::graphic::opengl::buffer::VertexBuffer::update_data(const void* data, uint32_t size) -> void { + glBindBuffer(GL_ARRAY_BUFFER, m_RendererID); + if (size == m_Size) { + glBufferSubData(GL_ARRAY_BUFFER, 0, size, data); + } else { + glBufferData(GL_ARRAY_BUFFER, size, data, GL_DYNAMIC_DRAW); + m_Size = size; + } +} + +auto gkit::graphic::opengl::buffer::VertexBuffer::update_sub_data(uint32_t offset, const void* data, uint32_t size) -> void { + glBindBuffer(GL_ARRAY_BUFFER, m_RendererID); + glBufferSubData(GL_ARRAY_BUFFER, offset, size, data); +} + +auto gkit::graphic::opengl::buffer::VertexBuffer::bind() const -> void { + glBindBuffer(GL_ARRAY_BUFFER, m_RendererID); +} + +auto gkit::graphic::opengl::buffer::VertexBuffer::unbind() const -> void { + glBindBuffer(GL_ARRAY_BUFFER, 0); +} diff --git a/src/graphic/opengl/config.cpp b/src/graphic/opengl/config.cpp new file mode 100644 index 0000000..b73242a --- /dev/null +++ b/src/graphic/opengl/config.cpp @@ -0,0 +1,14 @@ +#include "gkit/graphic/opengl/config.hpp" + +#include + +namespace gkit::graphic::opengl::viewport { + + auto set_viewport(int x, int y, int width, int height) -> void { + glViewport(x, y, width, height); + } + + auto set_viewport(int width, int height) -> void { + glViewport(0, 0, width, height); + } +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 18b5da1..45c46e8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,6 +3,7 @@ set (EXECUTABLE_OUTPUT_PATH ${BIN_FOLDER}/test) include_directories(${CMAKE_SOURCE_DIR}/src) include_directories(${CMAKE_SOURCE_DIR}/include) include_directories(${CMAKE_SOURCE_DIR}/src/c_abi/include) +include_directories(${CMAKE_SOURCE_DIR}/third_party/glad_generated/include) # build core tests file(GLOB_RECURSE TEST_SRCS @@ -10,6 +11,7 @@ file(GLOB_RECURSE TEST_SRCS "math/*.cpp" "utils/*.cpp" "resource/*.cpp" + "graphic/*.cpp" ) foreach(test_file ${TEST_SRCS}) diff --git a/test/graphic/basic.shader b/test/graphic/basic.shader new file mode 100644 index 0000000..7163700 --- /dev/null +++ b/test/graphic/basic.shader @@ -0,0 +1,15 @@ +#shader vertex +#version 450 core +layout(location = 0) in vec3 aPos; +void main() +{ + gl_Position = vec4(aPos, 1.0); +} + +#shader fragment +#version 450 core +out vec4 FragColor; +void main() +{ + FragColor = vec4(0.0f, 0.0f, 0.0f, 1.0f); +} \ No newline at end of file diff --git a/test/graphic/container2.png b/test/graphic/container2.png new file mode 100644 index 0000000..596e8da Binary files /dev/null and b/test/graphic/container2.png differ diff --git a/test/graphic/post_process.shader b/test/graphic/post_process.shader new file mode 100644 index 0000000..d371275 --- /dev/null +++ b/test/graphic/post_process.shader @@ -0,0 +1,36 @@ +#shader vertex +#version 450 core + +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec2 aTexCoord; + +out vec2 TexCoord; + +void main() +{ + gl_Position = vec4(aPosition, 1.0); + TexCoord = aTexCoord; +} + +#shader fragment +#version 450 core + +in vec2 TexCoord; +out vec4 FragColor; + +uniform sampler2D screenTexture; + +void main() +{ + vec3 color = texture(screenTexture, TexCoord).rgb; + + if(length(color) < 0.001) + discard; + + // Invert color + FragColor = vec4(1 - color, 1.0); + + // // grey + // float temp = color.r + color.g + color.b; + // FragColor = vec4(temp/3, temp/3, temp/3, 1.0); +} \ No newline at end of file diff --git a/test/graphic/test_window.cpp b/test/graphic/test_window.cpp new file mode 100644 index 0000000..ab2eee7 --- /dev/null +++ b/test/graphic/test_window.cpp @@ -0,0 +1,210 @@ +#include "gkit/graphic/opengl/StateManager.hpp" +#include "gkit/graphic/opengl/VertexArray.hpp" +#include "gkit/graphic/opengl/IndexBuffer.hpp" +#include "gkit/graphic/opengl/FrameBuffer.hpp" +#include "gkit/graphic/opengl/RenderBuffer.hpp" +#include "gkit/graphic/Shader.hpp" +#include "gkit/graphic/Renderer.hpp" + +#include +#include "SDL3/SDL.h" + +#include +#include + +int main(int argc, char* argv[]) +{ + // Get executable directory for resource paths + std::filesystem::path exePath = argv[0]; + // exe at bin/.../test/test_window.exe, go up 4 levels to reach project root + std::filesystem::path resourceBase = exePath.parent_path().parent_path().parent_path().parent_path() / "test" ; + + #pragma region Init + // Initialize SDL + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl; + return 1; + } + + // Request OpenGL 4.6 Core Profile + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + + // Create window + int screenWidth = gkit::graphic::opengl::SCR_WIDTH; + int screenHeight = gkit::graphic::opengl::SCR_HEIGHT; + + SDL_Window* window = SDL_CreateWindow( + "OpenGL Window", + screenWidth, + screenHeight, + SDL_WINDOW_OPENGL + ); + + if (window == nullptr) { + std::cerr << "Window could not be created! SDL_Error: " << SDL_GetError() << std::endl; + SDL_Quit(); + return 1; + } + + // Create OpenGL context + SDL_GLContext gl_context = SDL_GL_CreateContext(window); + if (gl_context == nullptr) { + std::cerr << "OpenGL context could not be created! SDL_Error: " << SDL_GetError() << std::endl; + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + // Initialize GLAD + if (!gladLoadGL(SDL_GL_GetProcAddress)) { + std::cerr << "Failed to initialize GLAD!" << std::endl; + SDL_GL_DestroyContext(gl_context); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + // Print OpenGL version + std::cout << "OpenGL Version: " << glGetString(GL_VERSION) << std::endl; + std::cout << "GLSL Version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl; + std::cout << "Renderer: " << glGetString(GL_RENDERER) << std::endl; + + #pragma endregion + + { + #pragma region square + // Triangle vertex data + float picVertices[] = { + // positions // tex coordsc + -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, + 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, + -0.5f, 0.5f, 0.0f, 0.0f, 1.0f + }; + + // index data + unsigned int picIndices[] = { 0, 1, 2, 2, 3, 0 }; + + gkit::graphic::opengl::VertexArray picVAO; + gkit::graphic::opengl::buffer::VertexBuffer picVBO(picVertices, sizeof(picVertices)); + gkit::graphic::opengl::buffer::IndexBuffer picIBO(picIndices, 6); + + gkit::graphic::opengl::buffer::VertexBufferLayout picLayout; + picLayout.push(3); + picLayout.push(2); + picVAO.add_buffer(picVBO, picLayout); + + // load shader source + gkit::graphic::Shader picShader((resourceBase / "graphic" / "texture.shader").string()); + gkit::graphic::opengl::Texture mainTexture((resourceBase / "graphic" / "container2.png").string(), gkit::graphic::opengl::TextureType::TEXTURE_2D); + + // Full-screen quad vertex data (post-processing) + float quadVertices[] = { + // positions // tex coords + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f + }; + + unsigned int quadIndices[] = { 0, 1, 2, 2, 3, 0 }; + + gkit::graphic::opengl::VertexArray quadVAO; + gkit::graphic::opengl::buffer::VertexBuffer quadVB(quadVertices, sizeof(quadVertices)); + gkit::graphic::opengl::buffer::IndexBuffer quadIB(quadIndices, 6); + + gkit::graphic::opengl::buffer::VertexBufferLayout quadLayout; + quadLayout.push(3); + quadLayout.push(2); + quadVAO.add_buffer(quadVB, quadLayout); + + // load post-processing shader + gkit::graphic::Shader postShader((resourceBase / "graphic" / "post_process.shader").string()); + #pragma endregion + + #pragma region framebuffer + gkit::graphic::opengl::buffer::FrameBuffer fbo(gkit::graphic::opengl::SCR_WIDTH, gkit::graphic::opengl::SCR_HEIGHT); + gkit::graphic::opengl::Texture fboTexture(" ", gkit::graphic::opengl::TextureType::TEXTURE_FRAMEBUFFER); + gkit::graphic::opengl::buffer::RenderBuffer rbo(gkit::graphic::opengl::SCR_WIDTH, gkit::graphic::opengl::SCR_HEIGHT); + fbo.attach_color_texture(fboTexture, 0); + fbo.attach_depth_stencil(rbo); + fbo.check(); + #pragma endregion + + auto& renderer = gkit::graphic::Renderer::instance(); + auto& stateManager = gkit::graphic::opengl::StateManager::instance(); + + // Main loop + bool quit = false; + SDL_Event event; + while (!quit) { + while (SDL_PollEvent(&event)) { + if (event.type == SDL_EVENT_QUIT) { + quit = true; + } + if (event.type == SDL_EVENT_KEY_DOWN) { + if (event.key.key == SDLK_ESCAPE) { + quit = true; + } + } + } + + fbo.bind(); + fbo.set_viewport(0, 0, screenWidth, screenHeight); + renderer.clear(gkit::graphic::opengl::ClearFlags::All); + // 1. Render to framebuffer + picShader.bind(); + mainTexture.bind(0); + renderer.draw(picVAO, picIBO, picShader); + + // 2. Render to screen (post-processing) + fbo.unbind(); + renderer.clear(gkit::graphic::opengl::ClearFlags::All); + gkit::graphic::opengl::viewport::set_viewport(0, 0, screenWidth/2, screenHeight/2); + stateManager.set_stencil_test(true); + stateManager.set_stencil(gkit::graphic::opengl::CompareFunc::Always, 1, 0xFF); + stateManager.set_stencil_op( + gkit::graphic::opengl::StencilOp::Keep, + gkit::graphic::opengl::StencilOp::Keep, + gkit::graphic::opengl::StencilOp::Replace); + stateManager.apply(); + picShader.bind(); + mainTexture.bind(0); + renderer.draw(picVAO, picIBO, picShader); + + gkit::graphic::opengl::viewport::set_viewport(0, 0, screenWidth, screenHeight); + stateManager.set_stencil(gkit::graphic::opengl::CompareFunc::Equal, 1, 0xFF); + stateManager.set_stencil_op( + gkit::graphic::opengl::StencilOp::Keep, + gkit::graphic::opengl::StencilOp::Keep, + gkit::graphic::opengl::StencilOp::Keep); + stateManager.apply(); + postShader.bind(); + fboTexture.bind(0); + postShader.set_uniform_1i("screenTexture", 0); + renderer.draw(quadVAO, quadIB, postShader); + + gkit::graphic::opengl::viewport::set_viewport(0, 0, screenWidth/4, screenHeight/4); + postShader.bind(); + fboTexture.bind(0); + postShader.set_uniform_1i("screenTexture", 0); + renderer.draw(quadVAO, quadIB, postShader); + + stateManager.set_stencil_test(false); + stateManager.apply(); + + // Swap buffers + SDL_GL_SwapWindow(window); + } + } + + // Cleanup + SDL_GL_DestroyContext(gl_context); + SDL_DestroyWindow(window); + SDL_Quit(); + + std::cout << "Window closed successfully" << std::endl; + return 0; +} diff --git a/test/graphic/texture.shader b/test/graphic/texture.shader new file mode 100644 index 0000000..98d5bfc --- /dev/null +++ b/test/graphic/texture.shader @@ -0,0 +1,25 @@ +#shader vertex +#version 450 core +layout(location = 0) in vec3 aPos; +layout(location = 1) in vec2 TexCoord; + +out vec2 v_TexCoord; + +void main() +{ + gl_Position = vec4(aPos, 1.0); + v_TexCoord = TexCoord; +} + +#shader fragment +#version 450 core +out vec4 FragColor; + +in vec2 v_TexCoord; + +uniform sampler2D u_mainTexture; + +void main() +{ + FragColor = texture(u_mainTexture, v_TexCoord); +} \ No newline at end of file diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index f067776..c458427 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -1,5 +1,18 @@ set (LIBRARY_OUTPUT_PATH ${BIN_FOLDER}/third_party) +# build glad (generated sources into third_party/glad_generated folder) +add_subdirectory(glad/cmake) + +glad_add_library(glad_gl STATIC + API "gl:core=4.6" + REPRODUCIBLE + LOCATION "${CMAKE_SOURCE_DIR}/third_party/glad_generated" +) + +target_include_directories(glad_gl INTERFACE + ${CMAKE_SOURCE_DIR}/third_party/glad_generated/include +) + # build sdl (build for static lib) set (SDL_STATIC ON) set (SDL_SHARED OFF) diff --git a/third_party/glad b/third_party/glad new file mode 160000 index 0000000..cef3f89 --- /dev/null +++ b/third_party/glad @@ -0,0 +1 @@ +Subproject commit cef3f890c450a14701407c9fe50f5fd02f7440b7