From 9125886b399fbd389930436c00d09729eb915d3a Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Tue, 4 Aug 2026 14:50:32 +0800 Subject: [PATCH 01/17] feat(graphic): add frontend RenderState value type - Add RenderState.hpp with DepthState/BlendState/CullFaceState/StencilState moved as frontend value types (was nested in opengl StateManager) - RenderState combines the four states with operator==/!= for sorting keys and incremental state application (rendering queue design Step 1) - StateManager migration to reference these frontend types happens in Step 2 --- include/gkit/graphic/RenderState.hpp | 82 ++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 include/gkit/graphic/RenderState.hpp diff --git a/include/gkit/graphic/RenderState.hpp b/include/gkit/graphic/RenderState.hpp new file mode 100644 index 0000000..0dd1c80 --- /dev/null +++ b/include/gkit/graphic/RenderState.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include "gkit/graphic/config.hpp" + +#include + +namespace gkit::graphic { + + /** + * @brief Depth test state + */ + struct DepthState { + bool enabled = false; // Whether depth test is enabled + CompareFunc compare_func = CompareFunc::Less; // Depth comparison function + bool write_mask = true; // Depth write mask + }; + + /** + * @brief Blend state + */ + struct BlendState { + bool enabled = false; // Whether blending is enabled + BlendFunc src_rgb = BlendFunc::One; // Source RGB blend factor + BlendFunc dst_rgb = BlendFunc::Zero; // Destination RGB blend factor + BlendFunc src_alpha = BlendFunc::One; // Source alpha blend factor + BlendFunc dst_alpha = BlendFunc::Zero; // Destination alpha blend factor + BlendEquation equation = BlendEquation::Add; // Blend equation + }; + + /** + * @brief Cull face state + */ + struct CullFaceState { + bool enabled = false; // Whether cull face is enabled + CullFaceMode mode = CullFaceMode::Back; // Cull face mode + FrontFace front_face = FrontFace::CounterClockwise; // Front face winding order + }; + + /** + * @brief Stencil state + */ + struct StencilState { + bool enabled = false; // Whether stencil test is enabled + CompareFunc compare_func = CompareFunc::Always; // Stencil comparison function + uint32_t ref = 0; // Stencil reference value + uint32_t read_mask = 0xFF; // Stencil read mask + uint32_t write_mask = 0xFF; // Stencil write mask + StencilOp fail = StencilOp::Keep; // Stencil fail operation + StencilOp z_fail = StencilOp::Keep; // Stencil depth fail operation + StencilOp z_pass = StencilOp::Keep; // Stencil depth pass operation + }; + + /** + * @brief Composite render state snapshot (排序键, 命令自携带) + * + * 把深度/混合/剔除/模板四个状态打包成一个快照, + * 供 RenderCommand 携带、排序去重、以及 StateManager 增量应用。 + */ + struct RenderState { + DepthState depth; // Depth test state + BlendState blend; // Blend state + CullFaceState cull_face; // Cull face state + StencilState stencil; // Stencil state + + auto operator==(const RenderState& other) const -> bool { + const auto& a = *this; + return a.depth.enabled == other.depth.enabled && a.depth.compare_func == other.depth.compare_func && + a.depth.write_mask == other.depth.write_mask && a.blend.enabled == other.blend.enabled && + a.blend.src_rgb == other.blend.src_rgb && a.blend.dst_rgb == other.blend.dst_rgb && + a.blend.src_alpha == other.blend.src_alpha && a.blend.dst_alpha == other.blend.dst_alpha && + a.blend.equation == other.blend.equation && a.cull_face.enabled == other.cull_face.enabled && + a.cull_face.mode == other.cull_face.mode && a.cull_face.front_face == other.cull_face.front_face && + a.stencil.enabled == other.stencil.enabled && a.stencil.compare_func == other.stencil.compare_func && + a.stencil.ref == other.stencil.ref && a.stencil.read_mask == other.stencil.read_mask && + a.stencil.write_mask == other.stencil.write_mask && a.stencil.fail == other.stencil.fail && + a.stencil.z_fail == other.stencil.z_fail && a.stencil.z_pass == other.stencil.z_pass; + } + + auto operator!=(const RenderState& other) const -> bool { return !(*this == other); } + }; + +} // namespace gkit::graphic From a9f72e8f12fa03b78140244dc19910c664d87eaf Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Tue, 4 Aug 2026 14:52:40 +0800 Subject: [PATCH 02/17] refactor(graphic): hide StateManager inside Device, add apply_state - RenderDevice: add virtual apply_state(const RenderState&) so the frontend executor/backend apply state through the abstract device only - StateManager: drop Singleton inheritance, become a plain class owned by opengl::Device; replace set_* + apply() with incremental apply(RenderState) that only applies changed components (depth/blend/cull/stencil) - StateManager uses the frontend RenderState types (Step 1); removes nested state structs, dirty flags, and get_* accessors (no external users) - opengl::Device holds a StateManager member and forwards apply_state Rendering queue design Step 2 --- include/gkit/graphic/RenderDevice.hpp | 9 + src/graphic/backend/opengl/Device.cpp | 4 + src/graphic/backend/opengl/Device.hpp | 7 +- src/graphic/backend/opengl/StateManager.cpp | 158 +++----------- src/graphic/backend/opengl/StateManager.hpp | 224 +++----------------- 5 files changed, 79 insertions(+), 323 deletions(-) diff --git a/include/gkit/graphic/RenderDevice.hpp b/include/gkit/graphic/RenderDevice.hpp index 3b5c2c6..78d6f79 100644 --- a/include/gkit/graphic/RenderDevice.hpp +++ b/include/gkit/graphic/RenderDevice.hpp @@ -3,6 +3,7 @@ #include "gkit/graphic/FrameBuffer.hpp" #include "gkit/graphic/IndexBuffer.hpp" #include "gkit/graphic/RenderBuffer.hpp" +#include "gkit/graphic/RenderState.hpp" #include "gkit/graphic/Shader.hpp" #include "gkit/graphic/Texture.hpp" #include "gkit/graphic/VertexArray.hpp" @@ -62,6 +63,14 @@ namespace gkit::graphic { */ virtual auto clear(ClearFlags flags) -> void = 0; + /** + * @brief Apply a render state snapshot incrementally + * + * Backends compare against the previously applied state and only change + * what differs (see RHI design doc §6 / render queue design §3.4). + */ + virtual auto apply_state(const RenderState& state) -> void = 0; + /** * @brief Draw indexed geometry */ diff --git a/src/graphic/backend/opengl/Device.cpp b/src/graphic/backend/opengl/Device.cpp index 2d7be8e..4d7f740 100644 --- a/src/graphic/backend/opengl/Device.cpp +++ b/src/graphic/backend/opengl/Device.cpp @@ -49,6 +49,10 @@ namespace gkit::graphic::opengl { glClear(mask); } + auto Device::apply_state(const graphic::RenderState& state) -> void { + this->state_manager.apply(state); + } + auto Device::draw(const graphic::VertexArray& va, const graphic::IndexBuffer& ib, const graphic::Shader& shader) -> void { shader.bind(); diff --git a/src/graphic/backend/opengl/Device.hpp b/src/graphic/backend/opengl/Device.hpp index f6dec67..cd43765 100644 --- a/src/graphic/backend/opengl/Device.hpp +++ b/src/graphic/backend/opengl/Device.hpp @@ -1,6 +1,7 @@ #pragma once #include "gkit/graphic/RenderDevice.hpp" +#include "graphic/backend/opengl/StateManager.hpp" #include #include @@ -9,7 +10,7 @@ * @brief OpenGL backend render device * * Inherits frontend `graphic::RenderDevice`; implements the resource factory - * and GL render commands. + * and GL render commands. Owns a StateManager for incremental state application. */ namespace gkit::graphic::opengl { @@ -29,12 +30,16 @@ namespace gkit::graphic::opengl { auto create_render_buffer(int width, int height) -> std::unique_ptr override; auto clear(ClearFlags flags) -> void override; + auto apply_state(const graphic::RenderState& state) -> void override; auto draw(const graphic::VertexArray& va, const graphic::IndexBuffer& ib, const graphic::Shader& shader) -> void override; auto draw_instance(const graphic::VertexArray& va, const graphic::IndexBuffer& ib, const graphic::Shader& shader, uint32_t instance_count) -> void override; + + private: + StateManager state_manager; // Incremental GL state application }; } // namespace gkit::graphic::opengl diff --git a/src/graphic/backend/opengl/StateManager.cpp b/src/graphic/backend/opengl/StateManager.cpp index 77fbbed..a35e93c 100644 --- a/src/graphic/backend/opengl/StateManager.cpp +++ b/src/graphic/backend/opengl/StateManager.cpp @@ -4,127 +4,49 @@ namespace gkit::graphic::opengl { - auto StateManager::set_depth_test(bool enable) -> void { - if (this->depth_state.enabled != enable) { - this->depth_state.enabled = enable; - this->dirty_flags |= DIRTY_DEPTH; - } - } - - auto StateManager::set_depth_func(CompareFunc func) -> void { - if (this->depth_state.compare_func != func) { - this->depth_state.compare_func = func; - this->dirty_flags |= DIRTY_DEPTH; - } - } - - auto StateManager::set_depth_mask(bool write) -> void { - if (this->depth_state.write_mask != write) { - this->depth_state.write_mask = write; - this->dirty_flags |= DIRTY_DEPTH; - } - } - - auto StateManager::set_blend(bool enable) -> void { - if (this->blend_state.enabled != enable) { - this->blend_state.enabled = enable; - this->dirty_flags |= DIRTY_BLEND; - } - } - - auto StateManager::set_blend_func(BlendFunc src_rgb, BlendFunc dst_rgb, BlendFunc src_alpha, BlendFunc dst_alpha) - -> void { - if (this->blend_state.src_rgb != src_rgb || this->blend_state.dst_rgb != dst_rgb || - this->blend_state.src_alpha != src_alpha || this->blend_state.dst_alpha != dst_alpha) { - this->blend_state.src_rgb = src_rgb; - this->blend_state.dst_rgb = dst_rgb; - this->blend_state.src_alpha = src_alpha; - this->blend_state.dst_alpha = dst_alpha; - this->dirty_flags |= DIRTY_BLEND; - } - } - - auto StateManager::set_blend_equation(BlendEquation equation) -> void { - if (this->blend_state.equation != equation) { - this->blend_state.equation = equation; - this->dirty_flags |= DIRTY_BLEND; - } - } - - auto StateManager::set_cull_face(bool enable) -> void { - if (this->cull_face_state.enabled != enable) { - this->cull_face_state.enabled = enable; - this->dirty_flags |= DIRTY_CULL; - } - } - - auto StateManager::set_cull_face_mode(CullFaceMode mode) -> void { - if (this->cull_face_state.mode != mode) { - this->cull_face_state.mode = mode; - this->dirty_flags |= DIRTY_CULL; - } - } - - auto StateManager::set_front_face(FrontFace front_face) -> void { - if (this->cull_face_state.front_face != front_face) { - this->cull_face_state.front_face = front_face; - this->dirty_flags |= DIRTY_CULL; - } - } - - auto StateManager::set_stencil_test(bool enable) -> void { - if (this->stencil_state.enabled != enable) { - this->stencil_state.enabled = enable; - this->dirty_flags |= DIRTY_STENCIL; - } - } - - auto StateManager::set_stencil(CompareFunc func, uint32_t ref, uint32_t mask) -> void { - if (this->stencil_state.compare_func != func || this->stencil_state.ref != ref || - this->stencil_state.read_mask != mask) { - this->stencil_state.compare_func = func; - this->stencil_state.ref = ref; - this->stencil_state.read_mask = mask; - this->dirty_flags |= DIRTY_STENCIL; - } - } - - auto StateManager::set_stencil_op(StencilOp fail, StencilOp z_fail, StencilOp z_pass) -> void { - if (this->stencil_state.fail != fail || this->stencil_state.z_fail != z_fail || - this->stencil_state.z_pass != z_pass) { - this->stencil_state.fail = fail; - this->stencil_state.z_fail = z_fail; - this->stencil_state.z_pass = z_pass; - this->dirty_flags |= DIRTY_STENCIL; - } - } - - auto StateManager::set_stencil_mask(uint32_t mask) -> void { - if (this->stencil_state.write_mask != mask) { - this->stencil_state.write_mask = mask; - this->dirty_flags |= DIRTY_STENCIL; - } - } - - auto StateManager::apply() -> void { - if (this->dirty_flags & DIRTY_DEPTH) { + auto StateManager::apply(const graphic::RenderState& state) -> void { + // Depth + if (this->depth_state.enabled != state.depth.enabled || + this->depth_state.compare_func != state.depth.compare_func || + this->depth_state.write_mask != state.depth.write_mask) { + this->depth_state = state.depth; apply_depth_state(); } - if (this->dirty_flags & DIRTY_BLEND) { + + // Blend + if (this->blend_state.enabled != state.blend.enabled || this->blend_state.src_rgb != state.blend.src_rgb || + this->blend_state.dst_rgb != state.blend.dst_rgb || this->blend_state.src_alpha != state.blend.src_alpha || + this->blend_state.dst_alpha != state.blend.dst_alpha || + this->blend_state.equation != state.blend.equation) { + this->blend_state = state.blend; apply_blend_state(); } - if (this->dirty_flags & DIRTY_CULL) { + + // Cull face + if (this->cull_face_state.enabled != state.cull_face.enabled || + this->cull_face_state.mode != state.cull_face.mode || + this->cull_face_state.front_face != state.cull_face.front_face) { + this->cull_face_state = state.cull_face; apply_cull_face_state(); } - if (this->dirty_flags & DIRTY_STENCIL) { + + // Stencil + if (this->stencil_state.enabled != state.stencil.enabled || + this->stencil_state.compare_func != state.stencil.compare_func || + this->stencil_state.ref != state.stencil.ref || this->stencil_state.read_mask != state.stencil.read_mask || + this->stencil_state.write_mask != state.stencil.write_mask || + this->stencil_state.fail != state.stencil.fail || this->stencil_state.z_fail != state.stencil.z_fail || + this->stencil_state.z_pass != state.stencil.z_pass) { + this->stencil_state = state.stencil; apply_stencil_state(); } - this->dirty_flags = 0; } auto StateManager::force_apply_all() -> void { - this->dirty_flags = DIRTY_DEPTH | DIRTY_BLEND | DIRTY_CULL | DIRTY_STENCIL; - apply(); + apply_depth_state(); + apply_blend_state(); + apply_cull_face_state(); + apply_stencil_state(); } auto StateManager::apply_depth_state() -> void { @@ -175,20 +97,4 @@ namespace gkit::graphic::opengl { glStencilMask(this->stencil_state.write_mask); } - auto StateManager::get_depth_state() const -> const DepthState& { - return this->depth_state; - } - - auto StateManager::get_blend_state() const -> const BlendState& { - return this->blend_state; - } - - auto StateManager::get_cull_face_state() const -> const CullFaceState& { - return this->cull_face_state; - } - - auto StateManager::get_stencil_state() const -> const StencilState& { - return this->stencil_state; - } - } // namespace gkit::graphic::opengl diff --git a/src/graphic/backend/opengl/StateManager.hpp b/src/graphic/backend/opengl/StateManager.hpp index 04cb7a9..b842ba7 100644 --- a/src/graphic/backend/opengl/StateManager.hpp +++ b/src/graphic/backend/opengl/StateManager.hpp @@ -1,222 +1,54 @@ #pragma once -#include "gkit/core/templates/singleton.hpp" -#include "gkit/graphic/config.hpp" +#include "gkit/graphic/RenderState.hpp" #include +/** + * @brief OpenGL state manager with dirty flag mechanism + * + * Owned by opengl::Device. Applies a frontend RenderState snapshot + * incrementally: only calls GL functions for the parts that changed + * since the last applied state. + */ 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::templates::Singleton { - friend class core::templates::Singleton; - - private: - StateManager() = default; - + class StateManager { public: - /** - * @brief Depth test state structure - */ - struct DepthState { - bool enabled = false; // Whether depth test is enabled - CompareFunc compare_func = CompareFunc::Less; // Depth comparison function - bool write_mask = true; // Depth write mask - }; - - /** - * @brief Blend state structure - */ - struct BlendState { - bool enabled = false; // Whether blending is enabled - BlendFunc src_rgb = BlendFunc::One; // Source RGB blend factor - BlendFunc dst_rgb = BlendFunc::Zero; // Destination RGB blend factor - BlendFunc src_alpha = BlendFunc::One; // Source alpha blend factor - BlendFunc dst_alpha = 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 front_face = FrontFace::CounterClockwise; // Front face winding order - }; - - /** - * @brief Stencil state structure - */ - struct StencilState { - bool enabled = false; // Whether stencil test is enabled - CompareFunc compare_func = CompareFunc::Always; // Stencil comparison function - uint32_t ref = 0; // Stencil reference value - uint32_t read_mask = 0xFF; // Stencil read mask - uint32_t write_mask = 0xFF; // Stencil write mask - StencilOp fail = StencilOp::Keep; // Stencil fail operation - StencilOp z_fail = StencilOp::Keep; // Stencil depth fail operation - StencilOp z_pass = 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 src_rgb Source RGB blend factor - * @param dst_rgb Destination RGB blend factor - * @param src_alpha Source alpha blend factor - * @param dst_alpha Destination alpha blend factor - */ - auto set_blend_func(BlendFunc src_rgb, BlendFunc dst_rgb, BlendFunc src_alpha, BlendFunc dst_alpha) -> 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 front_face The front face winding order - */ - auto set_front_face(FrontFace front_face) -> 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 z_fail Operation when stencil passes but depth fails - * @param z_pass Operation when both stencil and depth pass - */ - auto set_stencil_op(StencilOp fail, StencilOp z_fail, StencilOp z_pass) -> void; + StateManager() = default; /** - * @brief Apply all dirty states to OpenGL - */ - auto apply() -> void; + * @brief Apply a render state snapshot incrementally + * + * Compares against the previously applied state and only applies the + * changed components (depth/blend/cull/stencil). + * @param state Frontend render state snapshot + */ + auto apply(const graphic::RenderState& state) -> void; /** - * @brief Force apply all states (ignore dirty flags) - */ + * @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 - */ + /// @brief Apply depth state if changed auto apply_depth_state() -> void; - /** - * @brief Apply blend state if dirty - */ + /// @brief Apply blend state if changed auto apply_blend_state() -> void; - /** - * @brief Apply cull face state if dirty - */ + /// @brief Apply cull face state if changed auto apply_cull_face_state() -> void; - /** - * @brief Apply stencil state if dirty - */ + /// @brief Apply stencil state if changed auto apply_stencil_state() -> void; - /// @brief Current shadow states - DepthState depth_state; // Current depth state - BlendState blend_state; // Current blend state - CullFaceState cull_face_state; // Current cull face state - StencilState stencil_state; // Current stencil state - - /// @brief Dirty flags bitmask - uint8_t dirty_flags = 0; // Bitmask indicating which states need updating - - static constexpr uint8_t DIRTY_DEPTH = 1 << 0; // Dirty flag for depth state - static constexpr uint8_t DIRTY_BLEND = 1 << 1; // Dirty flag for blend state - static constexpr uint8_t DIRTY_CULL = 1 << 2; // Dirty flag for cull face state - static constexpr uint8_t DIRTY_STENCIL = 1 << 3; // Dirty flag for stencil state + /// @brief Currently applied states (shadow state) + graphic::DepthState depth_state; // Current depth state + graphic::BlendState blend_state; // Current blend state + graphic::CullFaceState cull_face_state; // Current cull face state + graphic::StencilState stencil_state; // Current stencil state }; } // namespace gkit::graphic::opengl From 566854e0f0bf37fb0d15064eaa5ea4e6200ebd9a Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Tue, 4 Aug 2026 14:53:25 +0800 Subject: [PATCH 03/17] feat(graphic): add uniform types (UniformValue/UniformData/UboBlock) - UniformBuffer.hpp now defines the type-erased UniformValue variant (int/float/Vector3/Vector4/Matrix3/Matrix4), the simple-path UniformData (name->value list) and the batch-path UboBlock (struct reference + binding) - UniformBuffer class stays a placeholder pending backend impl - Rendering queue design Step 3 --- include/gkit/graphic/UniformBuffer.hpp | 43 +++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/include/gkit/graphic/UniformBuffer.hpp b/include/gkit/graphic/UniformBuffer.hpp index 7e25d41..3505bc5 100644 --- a/include/gkit/graphic/UniformBuffer.hpp +++ b/include/gkit/graphic/UniformBuffer.hpp @@ -1,16 +1,51 @@ #pragma once #include "gkit/graphic/Buffer.hpp" +#include "gkit/math/matrix3.hpp" +#include "gkit/math/matrix4.hpp" +#include "gkit/math/vector3.hpp" +#include "gkit/math/vector4.hpp" + +#include +#include +#include +#include +#include +#include namespace gkit::graphic { /** - * @brief Uniform data buffer (placeholder, not implemented yet) + * @brief A single uniform value (type-erased) + */ + using UniformValue = std::variant; + + /** + * @brief Simple uniform set (逐条赋值) * - * Buffer that stores uniform data (UBO) for batching shader constants. + * 简单路径: 命令携带 name→value 列表, 执行器逐个 set_uniform_*。 + */ + struct UniformData { + std::vector> values; + }; + + /** + * @brief UBO block reference (批量上传) + * + * 批量路径: 命令携带用户参数结构体的引用, 执行器一次上传整个 block。 + * 持引用不拥有 —— 用户结构体须存活到 flush 结束(生命周期契约)。 + */ + struct UboBlock { + const void* data = nullptr; // Pointer to the user struct (e.g. SceneParams) + size_t size = 0; // Size of the struct in bytes + uint32_t binding = 0; // UBO binding point + }; + + /** + * @brief Uniform buffer object (UBO) base class (placeholder) * - * TODO(future): implement the backend (opengl::UniformBuffer) and the Device - * factory method create_uniform_buffer(). + * Buffer that stores uniform data (UBO) for batching shader constants. + * Backend (opengl::UniformBuffer) + Device factory not implemented yet. */ // class UniformBuffer : public Buffer { // public: From 1a65498b1d83d809f9c2abe4a4c98dd4563c6d4e Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Tue, 4 Aug 2026 14:55:29 +0800 Subject: [PATCH 04/17] feat(graphic): add RenderCommand and RenderQueue - RenderCommand: value-type draw command carrying target/va/ib/shader/ RenderState/UniformData/UboBlock/texture slots/transparent/instance_count (references resources, does not own); MAX_TEXTURE_SLOTS = 8 - RenderQueue: submit() collects commands, flush() applies state via RenderDevice::apply_state and draws via device.draw/draw_instance - Basic flush executes in submission order; sorting lands in Step 5 Rendering queue design Step 4 --- include/gkit/graphic/RenderCommand.hpp | 46 +++++++++++++++++++++++++ include/gkit/graphic/RenderQueue.hpp | 43 +++++++++++++++++++++++ src/graphic/CMakeLists.txt | 1 + src/graphic/RenderQueue.cpp | 47 ++++++++++++++++++++++++++ 4 files changed, 137 insertions(+) create mode 100644 include/gkit/graphic/RenderCommand.hpp create mode 100644 include/gkit/graphic/RenderQueue.hpp create mode 100644 src/graphic/RenderQueue.cpp diff --git a/include/gkit/graphic/RenderCommand.hpp b/include/gkit/graphic/RenderCommand.hpp new file mode 100644 index 0000000..c3c4534 --- /dev/null +++ b/include/gkit/graphic/RenderCommand.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "gkit/graphic/FrameBuffer.hpp" +#include "gkit/graphic/IndexBuffer.hpp" +#include "gkit/graphic/RenderState.hpp" +#include "gkit/graphic/Shader.hpp" +#include "gkit/graphic/Texture.hpp" +#include "gkit/graphic/UniformBuffer.hpp" +#include "gkit/graphic/VertexArray.hpp" + +#include +#include + +namespace gkit::graphic { + + /** + * @brief Engine-declared texture slot limit (fixed conservative value) + * + * Most shaders fit within 8 slots; not chasing hardware limits + * (GL_MAX_TEXTURE_IMAGE_UNITS varies by GPU, usually >= 32). + * Start up may assert the hardware supports at least this many. + */ + static constexpr uint32_t MAX_TEXTURE_SLOTS = 8; + + /** + * @brief A single draw command carrying its complete render state + * + * Value type; references (not owns) resources. Sorting keys and state are + * self-contained so the queue can reorder without global mutable state. + */ + struct RenderCommand { + const FrameBuffer* target = nullptr; // Render target (nullptr = screen) + const VertexArray* vertex_array = nullptr; + const IndexBuffer* index_buffer = nullptr; + const Shader* shader = nullptr; + RenderState state; // State snapshot (sorting key) + UniformData uniforms; // Simple-path per-name uniforms (see design §5.1) + UboBlock ubo; // Batch-path UBO reference (see design §5.2) + std::array textures = {}; // Texture slots (slot ↔ shader sampler unit) + uint32_t texture_count = 0; // Number of slots actually used + uint32_t instance_count = 1; // 1 = non-instanced + bool transparent = false; // Sort front-to-back (opaque) or back-to-front (transparent) + float depth_key = 0.0f; // Depth sort key (filled by upper layer) + }; + +} // namespace gkit::graphic diff --git a/include/gkit/graphic/RenderQueue.hpp b/include/gkit/graphic/RenderQueue.hpp new file mode 100644 index 0000000..27db9f6 --- /dev/null +++ b/include/gkit/graphic/RenderQueue.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "gkit/graphic/RenderCommand.hpp" +#include "gkit/graphic/RenderDevice.hpp" + +#include + +namespace gkit::graphic { + + /** + * @brief Render queue: collects commands, sorts, and executes at flush time + * + * Collects RenderCommand values during a frame and executes them in + * `flush()` after sorting. State application and drawing go through the + * frontend RenderDevice abstraction; the queue never touches GL directly. + */ + class RenderQueue { + public: + /** + * @brief Enqueue a command (copied; command is a value type) + */ + // cmd is deliberately taken by value: the command is copied into the queue. + auto submit(RenderCommand cmd) -> void { this->commands.push_back(cmd); } + + /** + * @brief Sort and execute all queued commands, then clear + * @param device Backend device used to apply state and draw + */ + auto flush(RenderDevice& device) -> void; + + /** + * @brief Drop all queued commands without executing + */ + auto clear() -> void { this->commands.clear(); } + + /// @brief Number of queued commands + [[nodiscard]] auto size() const -> size_t { return this->commands.size(); } + + private: + std::vector commands; + }; + +} // namespace gkit::graphic diff --git a/src/graphic/CMakeLists.txt b/src/graphic/CMakeLists.txt index d7a39ce..0cc1c7c 100644 --- a/src/graphic/CMakeLists.txt +++ b/src/graphic/CMakeLists.txt @@ -2,6 +2,7 @@ set (GKIT_GRAPHIC "gkit_graphic") set (GRAPHIC_SRC "./Renderer.cpp" + "./RenderQueue.cpp" "./create_device.cpp" # Backend implementations (one subdirectory per graphics API) diff --git a/src/graphic/RenderQueue.cpp b/src/graphic/RenderQueue.cpp new file mode 100644 index 0000000..758e691 --- /dev/null +++ b/src/graphic/RenderQueue.cpp @@ -0,0 +1,47 @@ +#include "gkit/graphic/RenderQueue.hpp" + +namespace gkit::graphic { + + namespace { + + /// @brief Bind a texture slot to the shader sampler unit (stub for slot wiring) + auto bind_textures(const RenderCommand& cmd) -> void { + for (uint32_t i = 0; i < cmd.texture_count && i < MAX_TEXTURE_SLOTS; ++i) { + if (cmd.textures[i] != nullptr) { + cmd.textures[i]->bind(i); + } + } + } + + } // namespace + + auto RenderQueue::flush(RenderDevice& device) -> void { + // TODO(Step 5): sort commands (state grouping / front-to-back / back-to-front). + for (const auto& cmd : this->commands) { + if (cmd.target != nullptr) { + cmd.target->bind(); + } else { + // Default framebuffer (screen). FBO unbind reverts to screen. + } + + device.apply_state(cmd.state); + + if (cmd.shader != nullptr) { + cmd.shader->bind(); + } + bind_textures(cmd); + + // TODO(Step 5): apply uniforms (UniformData/UboBlock) via the shader. + + if (cmd.vertex_array != nullptr && cmd.index_buffer != nullptr && cmd.shader != nullptr) { + if (cmd.instance_count > 1) { + device.draw_instance(*cmd.vertex_array, *cmd.index_buffer, *cmd.shader, cmd.instance_count); + } else { + device.draw(*cmd.vertex_array, *cmd.index_buffer, *cmd.shader); + } + } + } + this->commands.clear(); + } + +} // namespace gkit::graphic From e7421846271b5ef1a1dfd96eb1c931ee68ac51db Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Tue, 4 Aug 2026 14:56:58 +0800 Subject: [PATCH 05/17] feat(graphic): sort render queue and apply uniforms - RenderQueue::flush sorts commands: opaque front-to-back, transparent back-to-front, grouped by state/transparency to reduce state switches - Apply simple-path uniforms (UniformData) via std::visit dispatch to the shader's set_uniform_* (int/float/Vector3/Vector4/Matrix3/Matrix4) - RenderCommand::shader is now non-const (uniforms are mutated during exec) - UBO batch upload left as TODO pending UniformBuffer backend Rendering queue design Step 5 --- include/gkit/graphic/RenderCommand.hpp | 2 +- src/graphic/RenderQueue.cpp | 62 ++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/include/gkit/graphic/RenderCommand.hpp b/include/gkit/graphic/RenderCommand.hpp index c3c4534..25bd240 100644 --- a/include/gkit/graphic/RenderCommand.hpp +++ b/include/gkit/graphic/RenderCommand.hpp @@ -32,7 +32,7 @@ namespace gkit::graphic { const FrameBuffer* target = nullptr; // Render target (nullptr = screen) const VertexArray* vertex_array = nullptr; const IndexBuffer* index_buffer = nullptr; - const Shader* shader = nullptr; + Shader* shader = nullptr; // Non-const: uniforms are mutated during execution RenderState state; // State snapshot (sorting key) UniformData uniforms; // Simple-path per-name uniforms (see design §5.1) UboBlock ubo; // Batch-path UBO reference (see design §5.2) diff --git a/src/graphic/RenderQueue.cpp b/src/graphic/RenderQueue.cpp index 758e691..ea15d40 100644 --- a/src/graphic/RenderQueue.cpp +++ b/src/graphic/RenderQueue.cpp @@ -1,10 +1,13 @@ #include "gkit/graphic/RenderQueue.hpp" +#include +#include + namespace gkit::graphic { namespace { - /// @brief Bind a texture slot to the shader sampler unit (stub for slot wiring) + /// @brief Bind a texture slot to the shader sampler unit auto bind_textures(const RenderCommand& cmd) -> void { for (uint32_t i = 0; i < cmd.texture_count && i < MAX_TEXTURE_SLOTS; ++i) { if (cmd.textures[i] != nullptr) { @@ -13,10 +16,62 @@ namespace gkit::graphic { } } + /// @brief Apply a single uniform value through the shader's set_uniform_* + auto apply_uniform_value(Shader& shader, const std::string& name, const UniformValue& value) -> void { + std::visit( + [&](const auto& v) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + shader.set_uniform_1i(name, v); + } else if constexpr (std::is_same_v) { + shader.set_uniform_1f(name, v); + } else if constexpr (std::is_same_v) { + shader.set_uniform_vec_4f(name, v); + } else if constexpr (std::is_same_v) { + shader.set_uniform_vec_3f(name, v); + } else if constexpr (std::is_same_v) { + shader.set_uniform_mat_4f(name, v); + } else if constexpr (std::is_same_v) { + shader.set_uniform_mat_3f(name, v); + } + }, + value); + } + + /// @brief Apply simple-path uniforms (per-name list) + auto apply_uniforms(const RenderCommand& cmd) -> void { + if (cmd.shader == nullptr) { + return; + } + for (const auto& [name, value] : cmd.uniforms.values) { + apply_uniform_value(*cmd.shader, name, value); + } + // TODO(Step 6+/UBO): upload cmd.ubo via a UniformBuffer backend once implemented. + } + + /// @brief Sort comparator: opaque front-to-back, transparent back-to-front, group by state/shader + auto sort_key(const RenderCommand& cmd) -> uint64_t { + // Group primarily by state (reduces state switches), then by shader, + // then by transparency class; depth decides order within a class. + return (static_cast(cmd.state.blend.enabled) << 48) | + (static_cast(cmd.transparent) << 40); + } + } // namespace auto RenderQueue::flush(RenderDevice& device) -> void { - // TODO(Step 5): sort commands (state grouping / front-to-back / back-to-front). + // Sort: opaque front-to-back, transparent back-to-front; group state/shader. + std::stable_sort( + this->commands.begin(), this->commands.end(), [](const RenderCommand& a, const RenderCommand& b) { + if (sort_key(a) != sort_key(b)) { + return sort_key(a) < sort_key(b); + } + // Within the same transparency class: opaque nearer-first, transparent farther-first. + const float key_a = a.transparent ? -a.depth_key : a.depth_key; + const float key_b = b.transparent ? -b.depth_key : b.depth_key; + return key_a < key_b; + }); + for (const auto& cmd : this->commands) { if (cmd.target != nullptr) { cmd.target->bind(); @@ -30,8 +85,7 @@ namespace gkit::graphic { cmd.shader->bind(); } bind_textures(cmd); - - // TODO(Step 5): apply uniforms (UniformData/UboBlock) via the shader. + apply_uniforms(cmd); if (cmd.vertex_array != nullptr && cmd.index_buffer != nullptr && cmd.shader != nullptr) { if (cmd.instance_count > 1) { From e49f93e0957fc4756a5769829258e67115bf51f3 Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Tue, 4 Aug 2026 15:00:39 +0800 Subject: [PATCH 06/17] feat(graphic): route Renderer::draw through the render queue - Renderer now enqueues RenderCommand instead of drawing immediately - add Renderer::flush() that executes the queued commands via RenderQueue - draw/draw_instance take non-const Shader& (uniforms mutated on execute) - test_window: build commands with target/textures/uniforms and flush each frame (post-processing pipeline now expressed as queued commands) Rendering queue design Step 6 --- include/gkit/graphic/Renderer.hpp | 17 ++++-- src/graphic/Renderer.cpp | 25 ++++++--- test/graphic/test_window.cpp | 87 ++++++++++++++++++++++--------- 3 files changed, 93 insertions(+), 36 deletions(-) diff --git a/include/gkit/graphic/Renderer.hpp b/include/gkit/graphic/Renderer.hpp index 27d78f1..ee25f25 100644 --- a/include/gkit/graphic/Renderer.hpp +++ b/include/gkit/graphic/Renderer.hpp @@ -2,6 +2,7 @@ #include "gkit/core/templates/singleton.hpp" #include "gkit/graphic/RenderDevice.hpp" +#include "gkit/graphic/RenderQueue.hpp" #include "gkit/graphic/config.hpp" #include @@ -38,23 +39,30 @@ namespace gkit::graphic { auto clear(ClearFlags flags = ClearFlags::All) -> void; /** - * @brief Draw indexed geometry + * @brief Enqueue an indexed draw * @param va Vertex array containing vertex data * @param ib Index buffer containing indices * @param shader Shader program to use for rendering + * @note Enqueued into the render queue; executed on flush(). Shader is + * non-const because uniforms are mutated during execution. */ - auto draw(const VertexArray& va, const IndexBuffer& ib, const Shader& shader) -> void; + auto draw(const VertexArray& va, const IndexBuffer& ib, Shader& shader) -> void; /** - * @brief Draw multiple instances of indexed geometry + * @brief Enqueue an instanced indexed draw * @param va Vertex array containing vertex data * @param ib Index buffer containing indices * @param shader Shader program to use for rendering * @param instance_count Number of instances to draw */ - auto draw_instance(const VertexArray& va, const IndexBuffer& ib, const Shader& shader, uint32_t instance_count) + auto draw_instance(const VertexArray& va, const IndexBuffer& ib, Shader& shader, uint32_t instance_count) -> void; + /** + * @brief Execute the queued render commands (sort + apply state + draw) + */ + auto flush() -> void; + /** * @brief Access the current render device */ @@ -62,6 +70,7 @@ namespace gkit::graphic { private: std::unique_ptr device; + RenderQueue queue; }; } // namespace gkit::graphic diff --git a/src/graphic/Renderer.cpp b/src/graphic/Renderer.cpp index 9fe99b2..954824b 100644 --- a/src/graphic/Renderer.cpp +++ b/src/graphic/Renderer.cpp @@ -12,15 +12,26 @@ namespace gkit::graphic { this->get_device().clear(flags); } - auto Renderer::draw(const VertexArray& va, const IndexBuffer& ib, const Shader& shader) -> void { - this->get_device().draw(va, ib, shader); + auto Renderer::draw(const VertexArray& va, const IndexBuffer& ib, Shader& shader) -> void { + RenderCommand cmd; + cmd.vertex_array = &va; + cmd.index_buffer = &ib; + cmd.shader = &shader; + this->queue.submit(cmd); } - auto Renderer::draw_instance(const VertexArray& va, - const IndexBuffer& ib, - const Shader& shader, - uint32_t instance_count) -> void { - this->get_device().draw_instance(va, ib, shader, instance_count); + auto Renderer::draw_instance(const VertexArray& va, const IndexBuffer& ib, Shader& shader, uint32_t instance_count) + -> void { + RenderCommand cmd; + cmd.vertex_array = &va; + cmd.index_buffer = &ib; + cmd.shader = &shader; + cmd.instance_count = instance_count; + this->queue.submit(cmd); + } + + auto Renderer::flush() -> void { + this->queue.flush(this->get_device()); } auto Renderer::get_device() -> RenderDevice& { diff --git a/test/graphic/test_window.cpp b/test/graphic/test_window.cpp index 095b169..8ca3ff3 100644 --- a/test/graphic/test_window.cpp +++ b/test/graphic/test_window.cpp @@ -70,12 +70,29 @@ int main(int argc, char* argv[]) { auto& device = renderer.get_device(); + gkit::graphic::RenderQueue command_queue; + #pragma region triangle // Colored triangle vertex data (position + color) float tri_vertices[] = {// positions // colors - 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // top: red - -0.4f, -0.25f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom-left: green - 0.4f, -0.25f, 0.0f, 0.0f, 0.0f, 1.0f}; // bottom-right: blue + 0.0f, + 0.5f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, // top: red + -0.4f, + -0.25f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, // bottom-left: green + 0.4f, + -0.25f, + 0.0f, + 0.0f, + 0.0f, + 1.0f}; // bottom-right: blue // index data unsigned int tri_indices[] = {0, 1, 2}; @@ -136,30 +153,50 @@ int main(int argc, char* argv[]) { } } - fbo->bind(); fbo->set_viewport(0, 0, screen_width, screen_height); renderer.clear(gkit::graphic::ClearFlags::All); - // 1. Render to framebuffer - tri_shader->bind(); - renderer.draw(*tri_vao, *tri_ibo, *tri_shader); - - // 2. Render to screen (post-processing) - fbo->unbind(); - gkit::graphic::opengl::viewport::set_viewport(0, 0, screen_width, screen_height); - post_shader->bind(); - fbo_texture.bind(0); - post_shader->set_uniform_1i("screenTexture", 0); - renderer.draw(*quad_vao, *quad_ib, *post_shader); - - gkit::graphic::opengl::viewport::set_viewport(0, 0, screen_width / 2, screen_height / 2); - tri_shader->bind(); - renderer.draw(*tri_vao, *tri_ibo, *tri_shader); - - gkit::graphic::opengl::viewport::set_viewport(0, 0, screen_width / 4, screen_height / 4); - post_shader->bind(); - fbo_texture.bind(0); - post_shader->set_uniform_1i("screenTexture", 0); - renderer.draw(*quad_vao, *quad_ib, *post_shader); + + // 1. Render triangle to framebuffer (target = fbo) + { + gkit::graphic::RenderCommand cmd; + cmd.target = fbo.get(); + cmd.vertex_array = tri_vao.get(); + cmd.index_buffer = tri_ibo.get(); + cmd.shader = tri_shader.get(); + cmd.textures[0] = nullptr; + cmd.texture_count = 0; + cmd.transparent = false; + command_queue.submit(cmd); + } + + // 2. Render post-processing quad to screen (target = nullptr, sample fbo_texture) + { + gkit::graphic::RenderCommand cmd; + cmd.target = nullptr; + cmd.vertex_array = quad_vao.get(); + cmd.index_buffer = quad_ib.get(); + cmd.shader = post_shader.get(); + cmd.textures[0] = &fbo_texture; + cmd.texture_count = 1; + cmd.transparent = false; + cmd.uniforms.values.push_back({"screenTexture", 0}); + command_queue.submit(cmd); + } + + // 3. Small triangle overlay (screen) + { + gkit::graphic::RenderCommand cmd; + cmd.target = nullptr; + cmd.vertex_array = tri_vao.get(); + cmd.index_buffer = tri_ibo.get(); + cmd.shader = tri_shader.get(); + cmd.textures[0] = nullptr; + cmd.texture_count = 0; + cmd.transparent = false; + command_queue.submit(cmd); + } + + command_queue.flush(renderer.get_device()); // Swap buffers SDL_GL_SwapWindow(window); From 3827ab0e97aec06e0eb7a776a1b13a55054bfbe3 Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Tue, 4 Aug 2026 15:03:19 +0800 Subject: [PATCH 07/17] feat(graphic): add RenderObject reusable draw unit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RenderObject: geometry + material + state as a reusable draw unit (fields mirror RenderCommand, to_command() builds a command per frame) - Renderer::draw(const RenderObject&) enqueues via to_command() - Upper-layer draw interface evolution (design §7.1, direction 3) Rendering queue design Step 7 --- include/gkit/graphic/RenderObject.hpp | 58 +++++++++++++++++++++++++++ include/gkit/graphic/Renderer.hpp | 7 ++++ src/graphic/Renderer.cpp | 4 ++ test/graphic/test_window.cpp | 21 ++-------- 4 files changed, 72 insertions(+), 18 deletions(-) create mode 100644 include/gkit/graphic/RenderObject.hpp diff --git a/include/gkit/graphic/RenderObject.hpp b/include/gkit/graphic/RenderObject.hpp new file mode 100644 index 0000000..404d8fe --- /dev/null +++ b/include/gkit/graphic/RenderObject.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "gkit/graphic/FrameBuffer.hpp" +#include "gkit/graphic/IndexBuffer.hpp" +#include "gkit/graphic/RenderCommand.hpp" +#include "gkit/graphic/RenderState.hpp" +#include "gkit/graphic/Shader.hpp" +#include "gkit/graphic/Texture.hpp" +#include "gkit/graphic/UniformBuffer.hpp" +#include "gkit/graphic/VertexArray.hpp" + +#include +#include + +namespace gkit::graphic { + + /** + * @brief A reusable draw unit (geometry + material + state) + * + * Encapsulates everything needed to draw one object. A RenderCommand is + * built from it per frame. Fields mirror RenderCommand so the conversion + * is a straightforward copy. Shader is non-const (uniforms are mutated + * during execution), matching RenderCommand. + */ + struct RenderObject { + const FrameBuffer* target = nullptr; // Render target (nullptr = screen) + const VertexArray* vertex_array = nullptr; + const IndexBuffer* index_buffer = nullptr; + Shader* shader = nullptr; // Non-const: uniforms mutated on execute + RenderState state; // State snapshot + UniformData uniforms; // Simple-path per-name uniforms + UboBlock ubo; // Batch-path UBO reference + std::array textures = {}; // Texture slots + uint32_t texture_count = 0; // Number of slots actually used + uint32_t instance_count = 1; // 1 = non-instanced + bool transparent = false; // Sorting class + float depth_key = 0.0f; // Depth sort key + + /// @brief Build a draw command from this object + auto to_command() const -> RenderCommand { + RenderCommand cmd; + cmd.target = this->target; + cmd.vertex_array = this->vertex_array; + cmd.index_buffer = this->index_buffer; + cmd.shader = this->shader; + cmd.state = this->state; + cmd.uniforms = this->uniforms; + cmd.ubo = this->ubo; + cmd.textures = this->textures; + cmd.texture_count = this->texture_count; + cmd.instance_count = this->instance_count; + cmd.transparent = this->transparent; + cmd.depth_key = this->depth_key; + return cmd; + } + }; + +} // namespace gkit::graphic diff --git a/include/gkit/graphic/Renderer.hpp b/include/gkit/graphic/Renderer.hpp index ee25f25..0061ad7 100644 --- a/include/gkit/graphic/Renderer.hpp +++ b/include/gkit/graphic/Renderer.hpp @@ -2,6 +2,7 @@ #include "gkit/core/templates/singleton.hpp" #include "gkit/graphic/RenderDevice.hpp" +#include "gkit/graphic/RenderObject.hpp" #include "gkit/graphic/RenderQueue.hpp" #include "gkit/graphic/config.hpp" @@ -48,6 +49,12 @@ namespace gkit::graphic { */ auto draw(const VertexArray& va, const IndexBuffer& ib, Shader& shader) -> void; + /** + * @brief Enqueue a draw from a reusable render object + * @param obj Render object (geometry + material + state) + */ + auto draw(const RenderObject& obj) -> void; + /** * @brief Enqueue an instanced indexed draw * @param va Vertex array containing vertex data diff --git a/src/graphic/Renderer.cpp b/src/graphic/Renderer.cpp index 954824b..69f6afa 100644 --- a/src/graphic/Renderer.cpp +++ b/src/graphic/Renderer.cpp @@ -20,6 +20,10 @@ namespace gkit::graphic { this->queue.submit(cmd); } + auto Renderer::draw(const RenderObject& obj) -> void { + this->queue.submit(obj.to_command()); + } + auto Renderer::draw_instance(const VertexArray& va, const IndexBuffer& ib, Shader& shader, uint32_t instance_count) -> void { RenderCommand cmd; diff --git a/test/graphic/test_window.cpp b/test/graphic/test_window.cpp index 8ca3ff3..e2c3b3b 100644 --- a/test/graphic/test_window.cpp +++ b/test/graphic/test_window.cpp @@ -75,24 +75,9 @@ int main(int argc, char* argv[]) { #pragma region triangle // Colored triangle vertex data (position + color) float tri_vertices[] = {// positions // colors - 0.0f, - 0.5f, - 0.0f, - 1.0f, - 0.0f, - 0.0f, // top: red - -0.4f, - -0.25f, - 0.0f, - 0.0f, - 1.0f, - 0.0f, // bottom-left: green - 0.4f, - -0.25f, - 0.0f, - 0.0f, - 0.0f, - 1.0f}; // bottom-right: blue + 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // top: red + -0.4f, -0.25f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom-left: green + 0.4f, -0.25f, 0.0f, 0.0f, 0.0f, 1.0f}; // bottom-right: blue // index data unsigned int tri_indices[] = {0, 1, 2}; From 43a0137e7e18569972854efe6db204612b4544d0 Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Tue, 4 Aug 2026 15:17:48 +0800 Subject: [PATCH 08/17] refactor(graphic): split graphic headers into resource/ and render/ - include/gkit/graphic/resource/: GPU resource abstractions (Buffer, VertexBuffer, IndexBuffer, VertexArray, Shader, Texture, FrameBuffer, RenderBuffer, StorageBuffer, UniformBuffer) - include/gkit/graphic/render/: render pipeline types (Renderer, RenderDevice, RenderQueue, RenderCommand, RenderState, RenderObject) - graphic root keeps only config.hpp and VertexBufferLayout.hpp - update all includes across src/include/test to new paths --- include/gkit/gkit.hpp | 82 +++++++++---------- .../graphic/{ => render}/RenderCommand.hpp | 14 ++-- .../graphic/{ => render}/RenderDevice.hpp | 16 ++-- .../graphic/{ => render}/RenderObject.hpp | 16 ++-- .../gkit/graphic/{ => render}/RenderQueue.hpp | 4 +- .../gkit/graphic/{ => render}/RenderState.hpp | 0 .../gkit/graphic/{ => render}/Renderer.hpp | 6 +- .../gkit/graphic/{ => resource}/Buffer.hpp | 0 .../graphic/{ => resource}/FrameBuffer.hpp | 4 +- .../graphic/{ => resource}/IndexBuffer.hpp | 2 +- .../graphic/{ => resource}/RenderBuffer.hpp | 0 .../gkit/graphic/{ => resource}/Shader.hpp | 0 .../graphic/{ => resource}/StorageBuffer.hpp | 2 +- .../gkit/graphic/{ => resource}/Texture.hpp | 0 .../graphic/{ => resource}/UniformBuffer.hpp | 2 +- .../graphic/{ => resource}/VertexArray.hpp | 2 +- .../graphic/{ => resource}/VertexBuffer.hpp | 2 +- src/graphic/RenderQueue.cpp | 15 ++-- src/graphic/Renderer.cpp | 4 +- src/graphic/backend/opengl/Device.hpp | 2 +- src/graphic/backend/opengl/FrameBuffer.hpp | 6 +- src/graphic/backend/opengl/IndexBuffer.hpp | 2 +- src/graphic/backend/opengl/RenderBuffer.hpp | 2 +- src/graphic/backend/opengl/Shader.hpp | 2 +- src/graphic/backend/opengl/StateManager.hpp | 2 +- src/graphic/backend/opengl/Texture.hpp | 2 +- src/graphic/backend/opengl/VertexArray.cpp | 2 +- src/graphic/backend/opengl/VertexArray.hpp | 4 +- src/graphic/backend/opengl/VertexBuffer.hpp | 2 +- src/graphic/create_device.cpp | 2 +- test/graphic/test_window.cpp | 74 +++++++---------- 31 files changed, 132 insertions(+), 141 deletions(-) rename include/gkit/graphic/{ => render}/RenderCommand.hpp (83%) rename include/gkit/graphic/{ => render}/RenderDevice.hpp (89%) rename include/gkit/graphic/{ => render}/RenderObject.hpp (84%) rename include/gkit/graphic/{ => render}/RenderQueue.hpp (92%) rename include/gkit/graphic/{ => render}/RenderState.hpp (100%) rename include/gkit/graphic/{ => render}/Renderer.hpp (94%) rename include/gkit/graphic/{ => resource}/Buffer.hpp (100%) rename include/gkit/graphic/{ => resource}/FrameBuffer.hpp (95%) rename include/gkit/graphic/{ => resource}/IndexBuffer.hpp (95%) rename include/gkit/graphic/{ => resource}/RenderBuffer.hpp (100%) rename include/gkit/graphic/{ => resource}/Shader.hpp (100%) rename include/gkit/graphic/{ => resource}/StorageBuffer.hpp (93%) rename include/gkit/graphic/{ => resource}/Texture.hpp (100%) rename include/gkit/graphic/{ => resource}/UniformBuffer.hpp (97%) rename include/gkit/graphic/{ => resource}/VertexArray.hpp (96%) rename include/gkit/graphic/{ => resource}/VertexBuffer.hpp (95%) diff --git a/include/gkit/gkit.hpp b/include/gkit/gkit.hpp index f2c45af..afc2d2c 100644 --- a/include/gkit/gkit.hpp +++ b/include/gkit/gkit.hpp @@ -1,41 +1,41 @@ -#pragma once - -/** core **/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/** graphic **/ -#include -#include - -/** math **/ -#include -#include -#include -#include -#include -#include -#include -#include - -/** resource **/ -#include -#include -#include -#include - -/** scene **/ -#include -#include -#include -#include - -namespace gkit {} // namespace gkit +#pragma once + +/** core **/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** graphic **/ +#include +#include + +/** math **/ +#include +#include +#include +#include +#include +#include +#include +#include + +/** resource **/ +#include +#include +#include +#include + +/** scene **/ +#include +#include +#include +#include + +namespace gkit {} // namespace gkit diff --git a/include/gkit/graphic/RenderCommand.hpp b/include/gkit/graphic/render/RenderCommand.hpp similarity index 83% rename from include/gkit/graphic/RenderCommand.hpp rename to include/gkit/graphic/render/RenderCommand.hpp index 25bd240..0922228 100644 --- a/include/gkit/graphic/RenderCommand.hpp +++ b/include/gkit/graphic/render/RenderCommand.hpp @@ -1,12 +1,12 @@ #pragma once -#include "gkit/graphic/FrameBuffer.hpp" -#include "gkit/graphic/IndexBuffer.hpp" -#include "gkit/graphic/RenderState.hpp" -#include "gkit/graphic/Shader.hpp" -#include "gkit/graphic/Texture.hpp" -#include "gkit/graphic/UniformBuffer.hpp" -#include "gkit/graphic/VertexArray.hpp" +#include "gkit/graphic/render/RenderState.hpp" +#include "gkit/graphic/resource/FrameBuffer.hpp" +#include "gkit/graphic/resource/IndexBuffer.hpp" +#include "gkit/graphic/resource/Shader.hpp" +#include "gkit/graphic/resource/Texture.hpp" +#include "gkit/graphic/resource/UniformBuffer.hpp" +#include "gkit/graphic/resource/VertexArray.hpp" #include #include diff --git a/include/gkit/graphic/RenderDevice.hpp b/include/gkit/graphic/render/RenderDevice.hpp similarity index 89% rename from include/gkit/graphic/RenderDevice.hpp rename to include/gkit/graphic/render/RenderDevice.hpp index 78d6f79..cb7c69b 100644 --- a/include/gkit/graphic/RenderDevice.hpp +++ b/include/gkit/graphic/render/RenderDevice.hpp @@ -1,14 +1,14 @@ #pragma once -#include "gkit/graphic/FrameBuffer.hpp" -#include "gkit/graphic/IndexBuffer.hpp" -#include "gkit/graphic/RenderBuffer.hpp" -#include "gkit/graphic/RenderState.hpp" -#include "gkit/graphic/Shader.hpp" -#include "gkit/graphic/Texture.hpp" -#include "gkit/graphic/VertexArray.hpp" -#include "gkit/graphic/VertexBuffer.hpp" #include "gkit/graphic/config.hpp" +#include "gkit/graphic/render/RenderState.hpp" +#include "gkit/graphic/resource/FrameBuffer.hpp" +#include "gkit/graphic/resource/IndexBuffer.hpp" +#include "gkit/graphic/resource/RenderBuffer.hpp" +#include "gkit/graphic/resource/Shader.hpp" +#include "gkit/graphic/resource/Texture.hpp" +#include "gkit/graphic/resource/VertexArray.hpp" +#include "gkit/graphic/resource/VertexBuffer.hpp" #include #include diff --git a/include/gkit/graphic/RenderObject.hpp b/include/gkit/graphic/render/RenderObject.hpp similarity index 84% rename from include/gkit/graphic/RenderObject.hpp rename to include/gkit/graphic/render/RenderObject.hpp index 404d8fe..560c33e 100644 --- a/include/gkit/graphic/RenderObject.hpp +++ b/include/gkit/graphic/render/RenderObject.hpp @@ -1,13 +1,13 @@ #pragma once -#include "gkit/graphic/FrameBuffer.hpp" -#include "gkit/graphic/IndexBuffer.hpp" -#include "gkit/graphic/RenderCommand.hpp" -#include "gkit/graphic/RenderState.hpp" -#include "gkit/graphic/Shader.hpp" -#include "gkit/graphic/Texture.hpp" -#include "gkit/graphic/UniformBuffer.hpp" -#include "gkit/graphic/VertexArray.hpp" +#include "gkit/graphic/render/RenderCommand.hpp" +#include "gkit/graphic/render/RenderState.hpp" +#include "gkit/graphic/resource/FrameBuffer.hpp" +#include "gkit/graphic/resource/IndexBuffer.hpp" +#include "gkit/graphic/resource/Shader.hpp" +#include "gkit/graphic/resource/Texture.hpp" +#include "gkit/graphic/resource/UniformBuffer.hpp" +#include "gkit/graphic/resource/VertexArray.hpp" #include #include diff --git a/include/gkit/graphic/RenderQueue.hpp b/include/gkit/graphic/render/RenderQueue.hpp similarity index 92% rename from include/gkit/graphic/RenderQueue.hpp rename to include/gkit/graphic/render/RenderQueue.hpp index 27db9f6..b322b59 100644 --- a/include/gkit/graphic/RenderQueue.hpp +++ b/include/gkit/graphic/render/RenderQueue.hpp @@ -1,7 +1,7 @@ #pragma once -#include "gkit/graphic/RenderCommand.hpp" -#include "gkit/graphic/RenderDevice.hpp" +#include "gkit/graphic/render/RenderCommand.hpp" +#include "gkit/graphic/render/RenderDevice.hpp" #include diff --git a/include/gkit/graphic/RenderState.hpp b/include/gkit/graphic/render/RenderState.hpp similarity index 100% rename from include/gkit/graphic/RenderState.hpp rename to include/gkit/graphic/render/RenderState.hpp diff --git a/include/gkit/graphic/Renderer.hpp b/include/gkit/graphic/render/Renderer.hpp similarity index 94% rename from include/gkit/graphic/Renderer.hpp rename to include/gkit/graphic/render/Renderer.hpp index 0061ad7..f5521f7 100644 --- a/include/gkit/graphic/Renderer.hpp +++ b/include/gkit/graphic/render/Renderer.hpp @@ -1,10 +1,10 @@ #pragma once #include "gkit/core/templates/singleton.hpp" -#include "gkit/graphic/RenderDevice.hpp" -#include "gkit/graphic/RenderObject.hpp" -#include "gkit/graphic/RenderQueue.hpp" #include "gkit/graphic/config.hpp" +#include "gkit/graphic/render/RenderDevice.hpp" +#include "gkit/graphic/render/RenderObject.hpp" +#include "gkit/graphic/render/RenderQueue.hpp" #include #include diff --git a/include/gkit/graphic/Buffer.hpp b/include/gkit/graphic/resource/Buffer.hpp similarity index 100% rename from include/gkit/graphic/Buffer.hpp rename to include/gkit/graphic/resource/Buffer.hpp diff --git a/include/gkit/graphic/FrameBuffer.hpp b/include/gkit/graphic/resource/FrameBuffer.hpp similarity index 95% rename from include/gkit/graphic/FrameBuffer.hpp rename to include/gkit/graphic/resource/FrameBuffer.hpp index 37d5dfa..105d736 100644 --- a/include/gkit/graphic/FrameBuffer.hpp +++ b/include/gkit/graphic/resource/FrameBuffer.hpp @@ -1,7 +1,7 @@ #pragma once -#include "gkit/graphic/RenderBuffer.hpp" -#include "gkit/graphic/Texture.hpp" +#include "gkit/graphic/resource/RenderBuffer.hpp" +#include "gkit/graphic/resource/Texture.hpp" namespace gkit::graphic { diff --git a/include/gkit/graphic/IndexBuffer.hpp b/include/gkit/graphic/resource/IndexBuffer.hpp similarity index 95% rename from include/gkit/graphic/IndexBuffer.hpp rename to include/gkit/graphic/resource/IndexBuffer.hpp index 4621117..28c9ddd 100644 --- a/include/gkit/graphic/IndexBuffer.hpp +++ b/include/gkit/graphic/resource/IndexBuffer.hpp @@ -1,6 +1,6 @@ #pragma once -#include "gkit/graphic/Buffer.hpp" +#include "gkit/graphic/resource/Buffer.hpp" #include #include diff --git a/include/gkit/graphic/RenderBuffer.hpp b/include/gkit/graphic/resource/RenderBuffer.hpp similarity index 100% rename from include/gkit/graphic/RenderBuffer.hpp rename to include/gkit/graphic/resource/RenderBuffer.hpp diff --git a/include/gkit/graphic/Shader.hpp b/include/gkit/graphic/resource/Shader.hpp similarity index 100% rename from include/gkit/graphic/Shader.hpp rename to include/gkit/graphic/resource/Shader.hpp diff --git a/include/gkit/graphic/StorageBuffer.hpp b/include/gkit/graphic/resource/StorageBuffer.hpp similarity index 93% rename from include/gkit/graphic/StorageBuffer.hpp rename to include/gkit/graphic/resource/StorageBuffer.hpp index 64f609b..f502ca9 100644 --- a/include/gkit/graphic/StorageBuffer.hpp +++ b/include/gkit/graphic/resource/StorageBuffer.hpp @@ -1,6 +1,6 @@ #pragma once -#include "gkit/graphic/Buffer.hpp" +#include "gkit/graphic/resource/Buffer.hpp" namespace gkit::graphic { diff --git a/include/gkit/graphic/Texture.hpp b/include/gkit/graphic/resource/Texture.hpp similarity index 100% rename from include/gkit/graphic/Texture.hpp rename to include/gkit/graphic/resource/Texture.hpp diff --git a/include/gkit/graphic/UniformBuffer.hpp b/include/gkit/graphic/resource/UniformBuffer.hpp similarity index 97% rename from include/gkit/graphic/UniformBuffer.hpp rename to include/gkit/graphic/resource/UniformBuffer.hpp index 3505bc5..aea0df6 100644 --- a/include/gkit/graphic/UniformBuffer.hpp +++ b/include/gkit/graphic/resource/UniformBuffer.hpp @@ -1,6 +1,6 @@ #pragma once -#include "gkit/graphic/Buffer.hpp" +#include "gkit/graphic/resource/Buffer.hpp" #include "gkit/math/matrix3.hpp" #include "gkit/math/matrix4.hpp" #include "gkit/math/vector3.hpp" diff --git a/include/gkit/graphic/VertexArray.hpp b/include/gkit/graphic/resource/VertexArray.hpp similarity index 96% rename from include/gkit/graphic/VertexArray.hpp rename to include/gkit/graphic/resource/VertexArray.hpp index fdc480a..8e11a11 100644 --- a/include/gkit/graphic/VertexArray.hpp +++ b/include/gkit/graphic/resource/VertexArray.hpp @@ -1,7 +1,7 @@ #pragma once -#include "gkit/graphic/VertexBuffer.hpp" #include "gkit/graphic/VertexBufferLayout.hpp" +#include "gkit/graphic/resource/VertexBuffer.hpp" /** * @brief Vertex array (frontend abstract interface) diff --git a/include/gkit/graphic/VertexBuffer.hpp b/include/gkit/graphic/resource/VertexBuffer.hpp similarity index 95% rename from include/gkit/graphic/VertexBuffer.hpp rename to include/gkit/graphic/resource/VertexBuffer.hpp index 905cd63..3c45b34 100644 --- a/include/gkit/graphic/VertexBuffer.hpp +++ b/include/gkit/graphic/resource/VertexBuffer.hpp @@ -1,6 +1,6 @@ #pragma once -#include "gkit/graphic/Buffer.hpp" +#include "gkit/graphic/resource/Buffer.hpp" #include diff --git a/src/graphic/RenderQueue.cpp b/src/graphic/RenderQueue.cpp index ea15d40..f95a90d 100644 --- a/src/graphic/RenderQueue.cpp +++ b/src/graphic/RenderQueue.cpp @@ -1,4 +1,4 @@ -#include "gkit/graphic/RenderQueue.hpp" +#include "gkit/graphic/render/RenderQueue.hpp" #include #include @@ -49,12 +49,15 @@ namespace gkit::graphic { // TODO(Step 6+/UBO): upload cmd.ubo via a UniformBuffer backend once implemented. } - /// @brief Sort comparator: opaque front-to-back, transparent back-to-front, group by state/shader + /// @brief Sort comparator: group by render target, then by state/transparency auto sort_key(const RenderCommand& cmd) -> uint64_t { - // Group primarily by state (reduces state switches), then by shader, - // then by transparency class; depth decides order within a class. - return (static_cast(cmd.state.blend.enabled) << 48) | - (static_cast(cmd.transparent) << 40); + // Render target first: commands targeting the same FBO/screen must stay + // together (target switching is costly and order-sensitive). + // Then group by state (reduce state switches), then transparency class. + const uint64_t target_hash = + (cmd.target != nullptr) ? (reinterpret_cast(cmd.target) & 0xFFFF) : 0; + return (target_hash << 48) | (static_cast(cmd.state.blend.enabled) << 40) | + (static_cast(cmd.transparent) << 32); } } // namespace diff --git a/src/graphic/Renderer.cpp b/src/graphic/Renderer.cpp index 69f6afa..722d462 100644 --- a/src/graphic/Renderer.cpp +++ b/src/graphic/Renderer.cpp @@ -1,6 +1,6 @@ -#include "gkit/graphic/Renderer.hpp" +#include "gkit/graphic/render/Renderer.hpp" -#include "gkit/graphic/RenderDevice.hpp" +#include "gkit/graphic/render/RenderDevice.hpp" namespace gkit::graphic { diff --git a/src/graphic/backend/opengl/Device.hpp b/src/graphic/backend/opengl/Device.hpp index cd43765..58d64c3 100644 --- a/src/graphic/backend/opengl/Device.hpp +++ b/src/graphic/backend/opengl/Device.hpp @@ -1,6 +1,6 @@ #pragma once -#include "gkit/graphic/RenderDevice.hpp" +#include "gkit/graphic/render/RenderDevice.hpp" #include "graphic/backend/opengl/StateManager.hpp" #include diff --git a/src/graphic/backend/opengl/FrameBuffer.hpp b/src/graphic/backend/opengl/FrameBuffer.hpp index 28e3944..9d02f52 100644 --- a/src/graphic/backend/opengl/FrameBuffer.hpp +++ b/src/graphic/backend/opengl/FrameBuffer.hpp @@ -1,8 +1,8 @@ #pragma once -#include "gkit/graphic/FrameBuffer.hpp" -#include "gkit/graphic/RenderBuffer.hpp" -#include "gkit/graphic/Texture.hpp" +#include "gkit/graphic/resource/FrameBuffer.hpp" +#include "gkit/graphic/resource/RenderBuffer.hpp" +#include "gkit/graphic/resource/Texture.hpp" #include diff --git a/src/graphic/backend/opengl/IndexBuffer.hpp b/src/graphic/backend/opengl/IndexBuffer.hpp index 6355875..d82e933 100644 --- a/src/graphic/backend/opengl/IndexBuffer.hpp +++ b/src/graphic/backend/opengl/IndexBuffer.hpp @@ -1,6 +1,6 @@ #pragma once -#include "gkit/graphic/IndexBuffer.hpp" +#include "gkit/graphic/resource/IndexBuffer.hpp" #include diff --git a/src/graphic/backend/opengl/RenderBuffer.hpp b/src/graphic/backend/opengl/RenderBuffer.hpp index 5094ece..e85140d 100644 --- a/src/graphic/backend/opengl/RenderBuffer.hpp +++ b/src/graphic/backend/opengl/RenderBuffer.hpp @@ -1,6 +1,6 @@ #pragma once -#include "gkit/graphic/RenderBuffer.hpp" +#include "gkit/graphic/resource/RenderBuffer.hpp" #include diff --git a/src/graphic/backend/opengl/Shader.hpp b/src/graphic/backend/opengl/Shader.hpp index 08da1c6..441e71d 100644 --- a/src/graphic/backend/opengl/Shader.hpp +++ b/src/graphic/backend/opengl/Shader.hpp @@ -1,6 +1,6 @@ #pragma once -#include "gkit/graphic/Shader.hpp" +#include "gkit/graphic/resource/Shader.hpp" #include #include diff --git a/src/graphic/backend/opengl/StateManager.hpp b/src/graphic/backend/opengl/StateManager.hpp index b842ba7..77c7591 100644 --- a/src/graphic/backend/opengl/StateManager.hpp +++ b/src/graphic/backend/opengl/StateManager.hpp @@ -1,6 +1,6 @@ #pragma once -#include "gkit/graphic/RenderState.hpp" +#include "gkit/graphic/render/RenderState.hpp" #include diff --git a/src/graphic/backend/opengl/Texture.hpp b/src/graphic/backend/opengl/Texture.hpp index 2fa098e..10842aa 100644 --- a/src/graphic/backend/opengl/Texture.hpp +++ b/src/graphic/backend/opengl/Texture.hpp @@ -1,7 +1,7 @@ #pragma once -#include "gkit/graphic/Texture.hpp" #include "gkit/graphic/config.hpp" +#include "gkit/graphic/resource/Texture.hpp" #include #include diff --git a/src/graphic/backend/opengl/VertexArray.cpp b/src/graphic/backend/opengl/VertexArray.cpp index 4b146b8..f7777df 100644 --- a/src/graphic/backend/opengl/VertexArray.cpp +++ b/src/graphic/backend/opengl/VertexArray.cpp @@ -1,8 +1,8 @@ #include "graphic/backend/opengl/VertexArray.hpp" -#include "graphic/backend/opengl/VertexBuffer.hpp" #include "gkit/math/matrix4.hpp" #include "gkit/math/vector4.hpp" +#include "graphic/backend/opengl/VertexBuffer.hpp" #include diff --git a/src/graphic/backend/opengl/VertexArray.hpp b/src/graphic/backend/opengl/VertexArray.hpp index 4bcd4d4..505d489 100644 --- a/src/graphic/backend/opengl/VertexArray.hpp +++ b/src/graphic/backend/opengl/VertexArray.hpp @@ -1,8 +1,8 @@ #pragma once -#include "gkit/graphic/VertexArray.hpp" -#include "gkit/graphic/VertexBuffer.hpp" #include "gkit/graphic/VertexBufferLayout.hpp" +#include "gkit/graphic/resource/VertexArray.hpp" +#include "gkit/graphic/resource/VertexBuffer.hpp" #include diff --git a/src/graphic/backend/opengl/VertexBuffer.hpp b/src/graphic/backend/opengl/VertexBuffer.hpp index aedb94c..9e94b32 100644 --- a/src/graphic/backend/opengl/VertexBuffer.hpp +++ b/src/graphic/backend/opengl/VertexBuffer.hpp @@ -1,6 +1,6 @@ #pragma once -#include "gkit/graphic/VertexBuffer.hpp" +#include "gkit/graphic/resource/VertexBuffer.hpp" #include diff --git a/src/graphic/create_device.cpp b/src/graphic/create_device.cpp index c10edab..da431f9 100644 --- a/src/graphic/create_device.cpp +++ b/src/graphic/create_device.cpp @@ -1,4 +1,4 @@ -#include "gkit/graphic/RenderDevice.hpp" +#include "gkit/graphic/render/RenderDevice.hpp" #include "graphic/backend/opengl/Device.hpp" namespace gkit::graphic { diff --git a/test/graphic/test_window.cpp b/test/graphic/test_window.cpp index e2c3b3b..6815a55 100644 --- a/test/graphic/test_window.cpp +++ b/test/graphic/test_window.cpp @@ -1,5 +1,5 @@ -#include "gkit/graphic/Renderer.hpp" #include "gkit/graphic/VertexBufferLayout.hpp" +#include "gkit/graphic/render/Renderer.hpp" #include "graphic/backend/opengl/Texture.hpp" #include "graphic/backend/opengl/config.hpp" @@ -70,8 +70,6 @@ int main(int argc, char* argv[]) { auto& device = renderer.get_device(); - gkit::graphic::RenderQueue command_queue; - #pragma region triangle // Colored triangle vertex data (position + color) float tri_vertices[] = {// positions // colors @@ -123,6 +121,31 @@ int main(int argc, char* argv[]) { fbo->check(); #pragma endregion +#pragma region render_objects + // Reusable draw units (geometry + material + state), built once and + // submitted each frame via Renderer::draw(RenderObject). + gkit::graphic::RenderObject triangle_to_fbo; + triangle_to_fbo.target = fbo.get(); + triangle_to_fbo.vertex_array = tri_vao.get(); + triangle_to_fbo.index_buffer = tri_ibo.get(); + triangle_to_fbo.shader = tri_shader.get(); + + gkit::graphic::RenderObject post_quad; + post_quad.target = nullptr; // screen + post_quad.vertex_array = quad_vao.get(); + post_quad.index_buffer = quad_ib.get(); + post_quad.shader = post_shader.get(); + post_quad.textures[0] = &fbo_texture; + post_quad.texture_count = 1; + post_quad.uniforms.values.push_back({"screenTexture", 0}); + + gkit::graphic::RenderObject overlay_triangle; + overlay_triangle.target = nullptr; // screen + overlay_triangle.vertex_array = tri_vao.get(); + overlay_triangle.index_buffer = tri_ibo.get(); + overlay_triangle.shader = tri_shader.get(); +#pragma endregion + // Main loop bool quit = false; SDL_Event event; @@ -141,47 +164,12 @@ int main(int argc, char* argv[]) { fbo->set_viewport(0, 0, screen_width, screen_height); renderer.clear(gkit::graphic::ClearFlags::All); - // 1. Render triangle to framebuffer (target = fbo) - { - gkit::graphic::RenderCommand cmd; - cmd.target = fbo.get(); - cmd.vertex_array = tri_vao.get(); - cmd.index_buffer = tri_ibo.get(); - cmd.shader = tri_shader.get(); - cmd.textures[0] = nullptr; - cmd.texture_count = 0; - cmd.transparent = false; - command_queue.submit(cmd); - } - - // 2. Render post-processing quad to screen (target = nullptr, sample fbo_texture) - { - gkit::graphic::RenderCommand cmd; - cmd.target = nullptr; - cmd.vertex_array = quad_vao.get(); - cmd.index_buffer = quad_ib.get(); - cmd.shader = post_shader.get(); - cmd.textures[0] = &fbo_texture; - cmd.texture_count = 1; - cmd.transparent = false; - cmd.uniforms.values.push_back({"screenTexture", 0}); - command_queue.submit(cmd); - } - - // 3. Small triangle overlay (screen) - { - gkit::graphic::RenderCommand cmd; - cmd.target = nullptr; - cmd.vertex_array = tri_vao.get(); - cmd.index_buffer = tri_ibo.get(); - cmd.shader = tri_shader.get(); - cmd.textures[0] = nullptr; - cmd.texture_count = 0; - cmd.transparent = false; - command_queue.submit(cmd); - } + // Submit reusable render objects; Renderer enqueues them and flush() executes. + renderer.draw(triangle_to_fbo); // 1. Triangle to framebuffer + renderer.draw(post_quad); // 2. Post-processing quad to screen (samples fbo) + renderer.draw(overlay_triangle); // 3. Small triangle overlay - command_queue.flush(renderer.get_device()); + renderer.flush(); // Swap buffers SDL_GL_SwapWindow(window); From 3432b06bcdad2a5f934a7beab30b16c7a412c21f Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Tue, 4 Aug 2026 18:57:02 +0800 Subject: [PATCH 09/17] feat(graphic): per-command viewport; cleanup Renderer draw API - Add Viewport value type; RenderCommand/RenderObject carry a per-target viewport so FBO commands use FBO size and screen commands use window size (GL viewport is global state, must be set per command) - RenderDevice::set_viewport(Viewport) abstract interface; opengl Device implements via glViewport; RenderQueue sets it before each command - Renderer: keep only draw(const RenderObject&); remove simplified draw(va,ib,shader) and draw_instance (covered by RenderObject) - test_window: FBO is half the window (400x400), each RenderObject carries its viewport; drop manual fbo->set_viewport in the loop - RenderQueue sorts FBO-targeted commands before screen commands and unbinds the previous FBO when switching targets --- include/gkit/graphic/render/RenderCommand.hpp | 15 +++++++ include/gkit/graphic/render/RenderDevice.hpp | 6 +++ include/gkit/graphic/render/RenderObject.hpp | 2 + include/gkit/graphic/render/Renderer.hpp | 23 +---------- src/graphic/RenderQueue.cpp | 40 +++++++++++++------ src/graphic/Renderer.cpp | 20 ---------- src/graphic/backend/opengl/Device.cpp | 4 ++ src/graphic/backend/opengl/Device.hpp | 1 + test/graphic/test_window.cpp | 14 ++++--- 9 files changed, 67 insertions(+), 58 deletions(-) diff --git a/include/gkit/graphic/render/RenderCommand.hpp b/include/gkit/graphic/render/RenderCommand.hpp index 0922228..bcbd932 100644 --- a/include/gkit/graphic/render/RenderCommand.hpp +++ b/include/gkit/graphic/render/RenderCommand.hpp @@ -13,6 +13,20 @@ namespace gkit::graphic { + /** + * @brief Viewport rectangle for a render command + * + * Each command carries its own viewport so FBO-targeted commands use the + * FBO size while screen commands use the window size (GL viewport is global + * state, so it must be set per command). + */ + struct Viewport { + int x = 0; // Left coordinate + int y = 0; // Bottom coordinate + int width = 0; // Viewport width + int height = 0; // Viewport height + }; + /** * @brief Engine-declared texture slot limit (fixed conservative value) * @@ -33,6 +47,7 @@ namespace gkit::graphic { const VertexArray* vertex_array = nullptr; const IndexBuffer* index_buffer = nullptr; Shader* shader = nullptr; // Non-const: uniforms are mutated during execution + Viewport viewport; // Viewport to set before drawing (per-target size) RenderState state; // State snapshot (sorting key) UniformData uniforms; // Simple-path per-name uniforms (see design §5.1) UboBlock ubo; // Batch-path UBO reference (see design §5.2) diff --git a/include/gkit/graphic/render/RenderDevice.hpp b/include/gkit/graphic/render/RenderDevice.hpp index cb7c69b..21cf09e 100644 --- a/include/gkit/graphic/render/RenderDevice.hpp +++ b/include/gkit/graphic/render/RenderDevice.hpp @@ -1,6 +1,7 @@ #pragma once #include "gkit/graphic/config.hpp" +#include "gkit/graphic/render/RenderCommand.hpp" #include "gkit/graphic/render/RenderState.hpp" #include "gkit/graphic/resource/FrameBuffer.hpp" #include "gkit/graphic/resource/IndexBuffer.hpp" @@ -71,6 +72,11 @@ namespace gkit::graphic { */ virtual auto apply_state(const RenderState& state) -> void = 0; + /** + * @brief Set the viewport (GL viewport is global state, set per command) + */ + virtual auto set_viewport(const Viewport& viewport) -> void = 0; + /** * @brief Draw indexed geometry */ diff --git a/include/gkit/graphic/render/RenderObject.hpp b/include/gkit/graphic/render/RenderObject.hpp index 560c33e..bf4b4bb 100644 --- a/include/gkit/graphic/render/RenderObject.hpp +++ b/include/gkit/graphic/render/RenderObject.hpp @@ -27,6 +27,7 @@ namespace gkit::graphic { const VertexArray* vertex_array = nullptr; const IndexBuffer* index_buffer = nullptr; Shader* shader = nullptr; // Non-const: uniforms mutated on execute + Viewport viewport; // Viewport to set before drawing (per-target size) RenderState state; // State snapshot UniformData uniforms; // Simple-path per-name uniforms UboBlock ubo; // Batch-path UBO reference @@ -46,6 +47,7 @@ namespace gkit::graphic { cmd.state = this->state; cmd.uniforms = this->uniforms; cmd.ubo = this->ubo; + cmd.viewport = this->viewport; cmd.textures = this->textures; cmd.texture_count = this->texture_count; cmd.instance_count = this->instance_count; diff --git a/include/gkit/graphic/render/Renderer.hpp b/include/gkit/graphic/render/Renderer.hpp index f5521f7..0ee54fc 100644 --- a/include/gkit/graphic/render/Renderer.hpp +++ b/include/gkit/graphic/render/Renderer.hpp @@ -6,7 +6,6 @@ #include "gkit/graphic/render/RenderObject.hpp" #include "gkit/graphic/render/RenderQueue.hpp" -#include #include /** @@ -39,32 +38,14 @@ namespace gkit::graphic { */ auto clear(ClearFlags flags = ClearFlags::All) -> void; - /** - * @brief Enqueue an indexed draw - * @param va Vertex array containing vertex data - * @param ib Index buffer containing indices - * @param shader Shader program to use for rendering - * @note Enqueued into the render queue; executed on flush(). Shader is - * non-const because uniforms are mutated during execution. - */ - auto draw(const VertexArray& va, const IndexBuffer& ib, Shader& shader) -> void; - /** * @brief Enqueue a draw from a reusable render object * @param obj Render object (geometry + material + state) + * @note Enqueued into the render queue; executed on flush(). Shader is + * non-const because uniforms are mutated during execution. */ auto draw(const RenderObject& obj) -> void; - /** - * @brief Enqueue an instanced indexed draw - * @param va Vertex array containing vertex data - * @param ib Index buffer containing indices - * @param shader Shader program to use for rendering - * @param instance_count Number of instances to draw - */ - auto draw_instance(const VertexArray& va, const IndexBuffer& ib, Shader& shader, uint32_t instance_count) - -> void; - /** * @brief Execute the queued render commands (sort + apply state + draw) */ diff --git a/src/graphic/RenderQueue.cpp b/src/graphic/RenderQueue.cpp index f95a90d..855670f 100644 --- a/src/graphic/RenderQueue.cpp +++ b/src/graphic/RenderQueue.cpp @@ -49,15 +49,15 @@ namespace gkit::graphic { // TODO(Step 6+/UBO): upload cmd.ubo via a UniformBuffer backend once implemented. } - /// @brief Sort comparator: group by render target, then by state/transparency + /// @brief Sort comparator: framebuffer commands first, then by state/transparency auto sort_key(const RenderCommand& cmd) -> uint64_t { - // Render target first: commands targeting the same FBO/screen must stay - // together (target switching is costly and order-sensitive). - // Then group by state (reduce state switches), then transparency class. - const uint64_t target_hash = - (cmd.target != nullptr) ? (reinterpret_cast(cmd.target) & 0xFFFF) : 0; - return (target_hash << 48) | (static_cast(cmd.state.blend.enabled) << 40) | - (static_cast(cmd.transparent) << 32); + // Render targets (FBO) must be drawn before screen commands, otherwise + // post-processing cannot sample the FBO attachment. So target=null (screen) + // sorts after any non-null target. Then group by state (reduce switches). + // Within the same target, opaque front-to-back / transparent back-to-front. + const uint64_t target_rank = (cmd.target != nullptr) ? 0 : 1; // FBO before screen + return (target_rank << 56) | (static_cast(cmd.state.blend.enabled) << 48) | + (static_cast(cmd.transparent) << 40); } } // namespace @@ -75,13 +75,24 @@ namespace gkit::graphic { return key_a < key_b; }); + const FrameBuffer* last_target = nullptr; for (const auto& cmd : this->commands) { - if (cmd.target != nullptr) { - cmd.target->bind(); - } else { - // Default framebuffer (screen). FBO unbind reverts to screen. + // Switch render target: unbind the previous FBO (reverting to screen) + // before binding a different target. target=null means screen, reached + // by unbinding the previous FBO. + if (cmd.target != last_target) { + if (last_target != nullptr) { + last_target->unbind(); + } + last_target = cmd.target; + if (cmd.target != nullptr) { + cmd.target->bind(); + } } + // GL viewport is global state; each command sets its own per-target viewport. + device.set_viewport(cmd.viewport); + device.apply_state(cmd.state); if (cmd.shader != nullptr) { @@ -98,6 +109,11 @@ namespace gkit::graphic { } } } + + // End of frame: leave the default framebuffer bound (screen). + if (last_target != nullptr) { + last_target->unbind(); + } this->commands.clear(); } diff --git a/src/graphic/Renderer.cpp b/src/graphic/Renderer.cpp index 722d462..1e7593b 100644 --- a/src/graphic/Renderer.cpp +++ b/src/graphic/Renderer.cpp @@ -12,35 +12,15 @@ namespace gkit::graphic { this->get_device().clear(flags); } - auto Renderer::draw(const VertexArray& va, const IndexBuffer& ib, Shader& shader) -> void { - RenderCommand cmd; - cmd.vertex_array = &va; - cmd.index_buffer = &ib; - cmd.shader = &shader; - this->queue.submit(cmd); - } - auto Renderer::draw(const RenderObject& obj) -> void { this->queue.submit(obj.to_command()); } - auto Renderer::draw_instance(const VertexArray& va, const IndexBuffer& ib, Shader& shader, uint32_t instance_count) - -> void { - RenderCommand cmd; - cmd.vertex_array = &va; - cmd.index_buffer = &ib; - cmd.shader = &shader; - cmd.instance_count = instance_count; - this->queue.submit(cmd); - } - auto Renderer::flush() -> void { this->queue.flush(this->get_device()); } auto Renderer::get_device() -> RenderDevice& { - // Lazily create the default device so callers don't have to ensure - // init() was called before get_device(). if (this->device == nullptr) { this->device = create_device(Backend::OpenGL); } diff --git a/src/graphic/backend/opengl/Device.cpp b/src/graphic/backend/opengl/Device.cpp index 4d7f740..b31c2ae 100644 --- a/src/graphic/backend/opengl/Device.cpp +++ b/src/graphic/backend/opengl/Device.cpp @@ -53,6 +53,10 @@ namespace gkit::graphic::opengl { this->state_manager.apply(state); } + auto Device::set_viewport(const graphic::Viewport& viewport) -> void { + glViewport(viewport.x, viewport.y, viewport.width, viewport.height); + } + auto Device::draw(const graphic::VertexArray& va, const graphic::IndexBuffer& ib, const graphic::Shader& shader) -> void { shader.bind(); diff --git a/src/graphic/backend/opengl/Device.hpp b/src/graphic/backend/opengl/Device.hpp index 58d64c3..ae7102e 100644 --- a/src/graphic/backend/opengl/Device.hpp +++ b/src/graphic/backend/opengl/Device.hpp @@ -31,6 +31,7 @@ namespace gkit::graphic::opengl { auto clear(ClearFlags flags) -> void override; auto apply_state(const graphic::RenderState& state) -> void override; + auto set_viewport(const graphic::Viewport& viewport) -> void override; auto draw(const graphic::VertexArray& va, const graphic::IndexBuffer& ib, const graphic::Shader& shader) -> void override; auto draw_instance(const graphic::VertexArray& va, diff --git a/test/graphic/test_window.cpp b/test/graphic/test_window.cpp index 6815a55..0a105ba 100644 --- a/test/graphic/test_window.cpp +++ b/test/graphic/test_window.cpp @@ -1,7 +1,6 @@ #include "gkit/graphic/VertexBufferLayout.hpp" #include "gkit/graphic/render/Renderer.hpp" #include "graphic/backend/opengl/Texture.hpp" -#include "graphic/backend/opengl/config.hpp" #include #include @@ -113,9 +112,12 @@ int main(int argc, char* argv[]) { #pragma endregion #pragma region framebuffer - auto fbo = device.create_frame_buffer(gkit::graphic::SCR_WIDTH, gkit::graphic::SCR_HEIGHT); + // FBO is half the window size. + const int fbo_width = screen_width; + const int fbo_height = screen_height; + auto fbo = device.create_frame_buffer(fbo_width, fbo_height); gkit::graphic::opengl::Texture fbo_texture(" ", gkit::graphic::TextureType::TextureFramebuffer); - auto rbo = device.create_render_buffer(gkit::graphic::SCR_WIDTH, gkit::graphic::SCR_HEIGHT); + auto rbo = device.create_render_buffer(fbo_width, fbo_height); fbo->attach_color_texture(fbo_texture, 0); fbo->attach_depth_stencil(*rbo); fbo->check(); @@ -129,6 +131,7 @@ int main(int argc, char* argv[]) { triangle_to_fbo.vertex_array = tri_vao.get(); triangle_to_fbo.index_buffer = tri_ibo.get(); triangle_to_fbo.shader = tri_shader.get(); + triangle_to_fbo.viewport = {0, 0, fbo_width, fbo_height}; // FBO size gkit::graphic::RenderObject post_quad; post_quad.target = nullptr; // screen @@ -138,12 +141,14 @@ int main(int argc, char* argv[]) { post_quad.textures[0] = &fbo_texture; post_quad.texture_count = 1; post_quad.uniforms.values.push_back({"screenTexture", 0}); + post_quad.viewport = {0, 0, screen_width, screen_height}; // window size gkit::graphic::RenderObject overlay_triangle; overlay_triangle.target = nullptr; // screen overlay_triangle.vertex_array = tri_vao.get(); overlay_triangle.index_buffer = tri_ibo.get(); overlay_triangle.shader = tri_shader.get(); + overlay_triangle.viewport = {0, 0, screen_width, screen_height}; // window size #pragma endregion // Main loop @@ -161,13 +166,12 @@ int main(int argc, char* argv[]) { } } - fbo->set_viewport(0, 0, screen_width, screen_height); renderer.clear(gkit::graphic::ClearFlags::All); // Submit reusable render objects; Renderer enqueues them and flush() executes. renderer.draw(triangle_to_fbo); // 1. Triangle to framebuffer renderer.draw(post_quad); // 2. Post-processing quad to screen (samples fbo) - renderer.draw(overlay_triangle); // 3. Small triangle overlay + //renderer.draw(overlay_triangle); // 3. Small triangle overlay renderer.flush(); From fea1e14fd56823d42eb5963e73b3ef69ee5cec3c Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Tue, 4 Aug 2026 19:45:05 +0800 Subject: [PATCH 10/17] feat(graphic): per-command clear of the render target - RenderCommand/RenderObject gain clear + clear_flags: a command can request the target (FBO or screen) be cleared before drawing - RenderQueue::flush clears the bound target when cmd.clear is set - test_window: triangle_to_fbo clears the FBO color/depth attachment before drawing, so the post-processing quad samples a clean framebuffer --- include/gkit/graphic/render/RenderCommand.hpp | 3 +++ include/gkit/graphic/render/RenderObject.hpp | 4 ++++ src/graphic/RenderQueue.cpp | 5 +++++ test/graphic/test_window.cpp | 7 ++++--- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/include/gkit/graphic/render/RenderCommand.hpp b/include/gkit/graphic/render/RenderCommand.hpp index bcbd932..9bd7ddc 100644 --- a/include/gkit/graphic/render/RenderCommand.hpp +++ b/include/gkit/graphic/render/RenderCommand.hpp @@ -1,5 +1,6 @@ #pragma once +#include "gkit/graphic/config.hpp" #include "gkit/graphic/render/RenderState.hpp" #include "gkit/graphic/resource/FrameBuffer.hpp" #include "gkit/graphic/resource/IndexBuffer.hpp" @@ -56,6 +57,8 @@ namespace gkit::graphic { uint32_t instance_count = 1; // 1 = non-instanced bool transparent = false; // Sort front-to-back (opaque) or back-to-front (transparent) float depth_key = 0.0f; // Depth sort key (filled by upper layer) + bool clear = false; // Whether to clear the target before drawing + ClearFlags clear_flags = ClearFlags::All; // What to clear when clear is set }; } // namespace gkit::graphic diff --git a/include/gkit/graphic/render/RenderObject.hpp b/include/gkit/graphic/render/RenderObject.hpp index bf4b4bb..d6538d0 100644 --- a/include/gkit/graphic/render/RenderObject.hpp +++ b/include/gkit/graphic/render/RenderObject.hpp @@ -36,6 +36,8 @@ namespace gkit::graphic { uint32_t instance_count = 1; // 1 = non-instanced bool transparent = false; // Sorting class float depth_key = 0.0f; // Depth sort key + bool clear = false; // Whether to clear the target before drawing + ClearFlags clear_flags = ClearFlags::All; // What to clear when clear is set /// @brief Build a draw command from this object auto to_command() const -> RenderCommand { @@ -53,6 +55,8 @@ namespace gkit::graphic { cmd.instance_count = this->instance_count; cmd.transparent = this->transparent; cmd.depth_key = this->depth_key; + cmd.clear = this->clear; + cmd.clear_flags = this->clear_flags; return cmd; } }; diff --git a/src/graphic/RenderQueue.cpp b/src/graphic/RenderQueue.cpp index 855670f..2a0f02f 100644 --- a/src/graphic/RenderQueue.cpp +++ b/src/graphic/RenderQueue.cpp @@ -93,6 +93,11 @@ namespace gkit::graphic { // GL viewport is global state; each command sets its own per-target viewport. device.set_viewport(cmd.viewport); + // Clear the currently bound target (FBO or screen) if the command asks for it. + if (cmd.clear) { + device.clear(cmd.clear_flags); + } + device.apply_state(cmd.state); if (cmd.shader != nullptr) { diff --git a/test/graphic/test_window.cpp b/test/graphic/test_window.cpp index 0a105ba..4e39b3f 100644 --- a/test/graphic/test_window.cpp +++ b/test/graphic/test_window.cpp @@ -132,6 +132,7 @@ int main(int argc, char* argv[]) { triangle_to_fbo.index_buffer = tri_ibo.get(); triangle_to_fbo.shader = tri_shader.get(); triangle_to_fbo.viewport = {0, 0, fbo_width, fbo_height}; // FBO size + triangle_to_fbo.clear = true; // clear FBO color/depth before drawing gkit::graphic::RenderObject post_quad; post_quad.target = nullptr; // screen @@ -148,7 +149,7 @@ int main(int argc, char* argv[]) { overlay_triangle.vertex_array = tri_vao.get(); overlay_triangle.index_buffer = tri_ibo.get(); overlay_triangle.shader = tri_shader.get(); - overlay_triangle.viewport = {0, 0, screen_width, screen_height}; // window size + overlay_triangle.viewport = {0, 0, screen_width/2, screen_height/2}; // window size #pragma endregion // Main loop @@ -166,12 +167,12 @@ int main(int argc, char* argv[]) { } } - renderer.clear(gkit::graphic::ClearFlags::All); + //renderer.clear(gkit::graphic::ClearFlags::All); // Submit reusable render objects; Renderer enqueues them and flush() executes. renderer.draw(triangle_to_fbo); // 1. Triangle to framebuffer renderer.draw(post_quad); // 2. Post-processing quad to screen (samples fbo) - //renderer.draw(overlay_triangle); // 3. Small triangle overlay + renderer.draw(overlay_triangle); // 3. Small triangle overlay renderer.flush(); From ee6940a50171ca4406199c10da04bf8d0710f45c Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Tue, 4 Aug 2026 22:38:14 +0800 Subject: [PATCH 11/17] feat(graphic): add Material struct; move MAX_TEXTURE_SLOTS to config - New render/Material.hpp: shader + texture slots + UniformData + UboBlock (resources held by pointer reference, not owned, shareable across objects) - Move MAX_TEXTURE_SLOTS from RenderCommand to graphic/config.hpp so both Material and RenderCommand use it without a dependency between them - Remove the duplicate definition from RenderCommand RenderObject refactor Step 1 --- include/gkit/graphic/config.hpp | 8 ++++++ include/gkit/graphic/render/Material.hpp | 28 +++++++++++++++++++ include/gkit/graphic/render/RenderCommand.hpp | 15 ++-------- 3 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 include/gkit/graphic/render/Material.hpp diff --git a/include/gkit/graphic/config.hpp b/include/gkit/graphic/config.hpp index 6e5859e..23e6ac0 100644 --- a/include/gkit/graphic/config.hpp +++ b/include/gkit/graphic/config.hpp @@ -9,6 +9,14 @@ namespace gkit::graphic { */ const unsigned int SCR_WIDTH = 500; const unsigned int SCR_HEIGHT = 500; + /** + * @brief Engine-declared texture slot limit (fixed conservative value) + * + * Most shaders fit within 8 slots; not chasing hardware limits + * (GL_MAX_TEXTURE_IMAGE_UNITS varies by GPU, usually >= 32). + * Start up may assert the hardware supports at least this many. + */ + static constexpr uint32_t MAX_TEXTURE_SLOTS = 8; /** * @brief Texture pattern diff --git a/include/gkit/graphic/render/Material.hpp b/include/gkit/graphic/render/Material.hpp new file mode 100644 index 0000000..30cb686 --- /dev/null +++ b/include/gkit/graphic/render/Material.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "gkit/graphic/config.hpp" +#include "gkit/graphic/resource/Shader.hpp" +#include "gkit/graphic/resource/Texture.hpp" +#include "gkit/graphic/resource/UniformBuffer.hpp" + +#include +#include + +namespace gkit::graphic { + + /** + * @brief A reusable material (shader + texture slots + uniforms) + * + * Shader and textures are held by pointer (referenced, not owned); their + * lifetime is managed by the resource system. A Material can be shared by + * multiple RenderObjects. + */ + struct Material { + Shader* shader = nullptr; // Shader (pointer reference, not owned) + std::array textures = {}; // Texture slots (pointer reference) + uint32_t texture_count = 0; // Number of slots actually used + UniformData uniforms; // Simple-path per-name uniforms + UboBlock ubo; // Batch-path UBO reference + }; + +} // namespace gkit::graphic diff --git a/include/gkit/graphic/render/RenderCommand.hpp b/include/gkit/graphic/render/RenderCommand.hpp index 9bd7ddc..145bee5 100644 --- a/include/gkit/graphic/render/RenderCommand.hpp +++ b/include/gkit/graphic/render/RenderCommand.hpp @@ -28,15 +28,6 @@ namespace gkit::graphic { int height = 0; // Viewport height }; - /** - * @brief Engine-declared texture slot limit (fixed conservative value) - * - * Most shaders fit within 8 slots; not chasing hardware limits - * (GL_MAX_TEXTURE_IMAGE_UNITS varies by GPU, usually >= 32). - * Start up may assert the hardware supports at least this many. - */ - static constexpr uint32_t MAX_TEXTURE_SLOTS = 8; - /** * @brief A single draw command carrying its complete render state * @@ -55,9 +46,9 @@ namespace gkit::graphic { std::array textures = {}; // Texture slots (slot ↔ shader sampler unit) uint32_t texture_count = 0; // Number of slots actually used uint32_t instance_count = 1; // 1 = non-instanced - bool transparent = false; // Sort front-to-back (opaque) or back-to-front (transparent) - float depth_key = 0.0f; // Depth sort key (filled by upper layer) - bool clear = false; // Whether to clear the target before drawing + bool transparent = false; // Sort front-to-back (opaque) or back-to-front (transparent) + float depth_key = 0.0f; // Depth sort key (filled by upper layer) + bool clear = false; // Whether to clear the target before drawing ClearFlags clear_flags = ClearFlags::All; // What to clear when clear is set }; From e3d83dd8d27cc299cc12818ab1dbebd2f4566cb9 Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Tue, 4 Aug 2026 22:48:29 +0800 Subject: [PATCH 12/17] refactor(graphic): RenderObject takes data + Material, hides VAO/VBO/IBO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RenderObject now owns CPU vertex/index data and a Material; GPU resources (VBO/IBO/VAO) are lazily created and cached on first draw via ensure_uploaded - RenderCommand holds only RenderObject* + per-draw controls (target, viewport, clear, sorting metadata); geometry/material/state read from the object - Renderer::draw(RenderObject&, target, viewport) enqueues the command - RenderQueue executes: switch target, set viewport, clear, apply state, lazily upload geometry, bind material shader/textures, apply uniforms, draw - Material moved to graphic/resource/ (it references shader/texture resources) - MAX_TEXTURE_SLOTS moved to config.hpp (shared by Material and RenderCommand) - test_window: builds objects from vertex/index arrays + Material, no manual VAO/VBO/IBO/shader RenderObject refactor (design doc RenderObject重构方案) --- include/gkit/graphic/render/RenderCommand.hpp | 37 +++--- include/gkit/graphic/render/RenderObject.hpp | 108 ++++++++++-------- include/gkit/graphic/render/Renderer.hpp | 8 +- .../graphic/{render => resource}/Material.hpp | 0 src/graphic/CMakeLists.txt | 1 + src/graphic/RenderObject.cpp | 30 +++++ src/graphic/RenderQueue.cpp | 56 +++++---- src/graphic/Renderer.cpp | 13 ++- test/graphic/test_window.cpp | 100 ++++++++-------- 9 files changed, 200 insertions(+), 153 deletions(-) rename include/gkit/graphic/{render => resource}/Material.hpp (100%) create mode 100644 src/graphic/RenderObject.cpp diff --git a/include/gkit/graphic/render/RenderCommand.hpp b/include/gkit/graphic/render/RenderCommand.hpp index 145bee5..6635781 100644 --- a/include/gkit/graphic/render/RenderCommand.hpp +++ b/include/gkit/graphic/render/RenderCommand.hpp @@ -1,15 +1,8 @@ #pragma once #include "gkit/graphic/config.hpp" -#include "gkit/graphic/render/RenderState.hpp" #include "gkit/graphic/resource/FrameBuffer.hpp" -#include "gkit/graphic/resource/IndexBuffer.hpp" -#include "gkit/graphic/resource/Shader.hpp" -#include "gkit/graphic/resource/Texture.hpp" -#include "gkit/graphic/resource/UniformBuffer.hpp" -#include "gkit/graphic/resource/VertexArray.hpp" -#include #include namespace gkit::graphic { @@ -28,28 +21,24 @@ namespace gkit::graphic { int height = 0; // Viewport height }; + class RenderObject; + /** - * @brief A single draw command carrying its complete render state + * @brief A single draw command referencing a render object * - * Value type; references (not owns) resources. Sorting keys and state are - * self-contained so the queue can reorder without global mutable state. + * Value type; references (not owns) the RenderObject and its target. + * Geometry/material/state are read from the RenderObject; the command only + * carries per-draw controls (target, viewport, clear, sorting metadata). */ struct RenderCommand { - const FrameBuffer* target = nullptr; // Render target (nullptr = screen) - const VertexArray* vertex_array = nullptr; - const IndexBuffer* index_buffer = nullptr; - Shader* shader = nullptr; // Non-const: uniforms are mutated during execution + const FrameBuffer* target = nullptr; // Render target (nullptr = screen) + RenderObject* object = nullptr; // Geometry + material + state source (lazily uploaded on execute) Viewport viewport; // Viewport to set before drawing (per-target size) - RenderState state; // State snapshot (sorting key) - UniformData uniforms; // Simple-path per-name uniforms (see design §5.1) - UboBlock ubo; // Batch-path UBO reference (see design §5.2) - std::array textures = {}; // Texture slots (slot ↔ shader sampler unit) - uint32_t texture_count = 0; // Number of slots actually used - uint32_t instance_count = 1; // 1 = non-instanced - bool transparent = false; // Sort front-to-back (opaque) or back-to-front (transparent) - float depth_key = 0.0f; // Depth sort key (filled by upper layer) - bool clear = false; // Whether to clear the target before drawing - ClearFlags clear_flags = ClearFlags::All; // What to clear when clear is set + uint32_t instance_count = 1; // 1 = non-instanced + bool transparent = false; // Sort front-to-back (opaque) or back-to-front (transparent) + float depth_key = 0.0f; // Depth sort key (filled by upper layer) + bool clear = false; // Whether to clear the target before drawing + ClearFlags clear_flags = ClearFlags::All; // What to clear when clear is set }; } // namespace gkit::graphic diff --git a/include/gkit/graphic/render/RenderObject.hpp b/include/gkit/graphic/render/RenderObject.hpp index d6538d0..4417c9a 100644 --- a/include/gkit/graphic/render/RenderObject.hpp +++ b/include/gkit/graphic/render/RenderObject.hpp @@ -1,64 +1,78 @@ #pragma once -#include "gkit/graphic/render/RenderCommand.hpp" +#include "gkit/graphic/VertexBufferLayout.hpp" +#include "gkit/graphic/config.hpp" +#include "gkit/graphic/render/RenderDevice.hpp" #include "gkit/graphic/render/RenderState.hpp" -#include "gkit/graphic/resource/FrameBuffer.hpp" #include "gkit/graphic/resource/IndexBuffer.hpp" -#include "gkit/graphic/resource/Shader.hpp" -#include "gkit/graphic/resource/Texture.hpp" -#include "gkit/graphic/resource/UniformBuffer.hpp" +#include "gkit/graphic/resource/Material.hpp" #include "gkit/graphic/resource/VertexArray.hpp" +#include "gkit/graphic/resource/VertexBuffer.hpp" -#include #include +#include +#include namespace gkit::graphic { /** - * @brief A reusable draw unit (geometry + material + state) + * @brief A draw unit defined by CPU data (vertices/indices + material + state) * - * Encapsulates everything needed to draw one object. A RenderCommand is - * built from it per frame. Fields mirror RenderCommand so the conversion - * is a straightforward copy. Shader is non-const (uniforms are mutated - * during execution), matching RenderCommand. + * Users provide vertex/index arrays, a vertex layout, a material, and state. + * The VAO/VBO/IBO creation and binding are hidden: GPU resources are lazily + * created and cached on first draw. Only a RenderCommand built from this + * object is enqueued. */ - struct RenderObject { - const FrameBuffer* target = nullptr; // Render target (nullptr = screen) - const VertexArray* vertex_array = nullptr; - const IndexBuffer* index_buffer = nullptr; - Shader* shader = nullptr; // Non-const: uniforms mutated on execute - Viewport viewport; // Viewport to set before drawing (per-target size) - RenderState state; // State snapshot - UniformData uniforms; // Simple-path per-name uniforms - UboBlock ubo; // Batch-path UBO reference - std::array textures = {}; // Texture slots - uint32_t texture_count = 0; // Number of slots actually used - uint32_t instance_count = 1; // 1 = non-instanced - bool transparent = false; // Sorting class - float depth_key = 0.0f; // Depth sort key - bool clear = false; // Whether to clear the target before drawing - ClearFlags clear_flags = ClearFlags::All; // What to clear when clear is set + class RenderObject { + public: + /** + * @brief Construct from CPU geometry data, layout, and material + * @param vertices interleaved vertex data + * @param indices index data + * @param layout vertex attribute layout (position/color/uv...) + * @param material material (shader + textures + uniforms) + * @note vertices/indices are copied into the object (owned CPU data). + */ + RenderObject(const std::vector& vertices, + const std::vector& indices, + const VertexBufferLayout& layout, + const Material& material); - /// @brief Build a draw command from this object - auto to_command() const -> RenderCommand { - RenderCommand cmd; - cmd.target = this->target; - cmd.vertex_array = this->vertex_array; - cmd.index_buffer = this->index_buffer; - cmd.shader = this->shader; - cmd.state = this->state; - cmd.uniforms = this->uniforms; - cmd.ubo = this->ubo; - cmd.viewport = this->viewport; - cmd.textures = this->textures; - cmd.texture_count = this->texture_count; - cmd.instance_count = this->instance_count; - cmd.transparent = this->transparent; - cmd.depth_key = this->depth_key; - cmd.clear = this->clear; - cmd.clear_flags = this->clear_flags; - return cmd; - } + // ---- Material (reusable, replaceable) ---- + Material material; + + // ---- Render state / target ---- + RenderState state; + uint32_t instance_count = 1; // 1 = non-instanced + bool transparent = false; // Sorting class + float depth_key = 0.0f; // Depth sort key + bool clear = false; // Whether to clear the target before drawing + ClearFlags clear_flags = ClearFlags::All; // What to clear when clear is set + + /** + * @brief Lazily upload vertices/indices to GPU and return the vertex array + * @param device backend device used to create GPU buffers + * @return reference to the cached vertex array + */ + auto ensure_uploaded(RenderDevice& device) -> const VertexArray&; + + /// @brief Access the cached index buffer (valid after ensure_uploaded) + [[nodiscard]] auto index_buffer() -> const IndexBuffer&; + + /// @brief Whether GPU resources have been created + [[nodiscard]] auto is_uploaded() const -> bool { return this->uploaded; } + + private: + // CPU geometry data (provided by the user) + std::vector vertices; + std::vector indices; + VertexBufferLayout layout; + + // Lazily created GPU resources (hidden from the user) + std::unique_ptr vbo; + std::unique_ptr vao; + std::unique_ptr ibo; + bool uploaded = false; }; } // namespace gkit::graphic diff --git a/include/gkit/graphic/render/Renderer.hpp b/include/gkit/graphic/render/Renderer.hpp index 0ee54fc..2c80828 100644 --- a/include/gkit/graphic/render/Renderer.hpp +++ b/include/gkit/graphic/render/Renderer.hpp @@ -41,10 +41,12 @@ namespace gkit::graphic { /** * @brief Enqueue a draw from a reusable render object * @param obj Render object (geometry + material + state) - * @note Enqueued into the render queue; executed on flush(). Shader is - * non-const because uniforms are mutated during execution. + * @param target render target (nullptr = screen) + * @param viewport viewport to use for this draw (per-target size) + * @note Enqueued into the render queue; executed on flush(). The object is + * non-const because its GPU resources are lazily uploaded on execute. */ - auto draw(const RenderObject& obj) -> void; + auto draw(RenderObject& obj, const FrameBuffer* target, const Viewport& viewport) -> void; /** * @brief Execute the queued render commands (sort + apply state + draw) diff --git a/include/gkit/graphic/render/Material.hpp b/include/gkit/graphic/resource/Material.hpp similarity index 100% rename from include/gkit/graphic/render/Material.hpp rename to include/gkit/graphic/resource/Material.hpp diff --git a/src/graphic/CMakeLists.txt b/src/graphic/CMakeLists.txt index 0cc1c7c..2753584 100644 --- a/src/graphic/CMakeLists.txt +++ b/src/graphic/CMakeLists.txt @@ -2,6 +2,7 @@ set (GKIT_GRAPHIC "gkit_graphic") set (GRAPHIC_SRC "./Renderer.cpp" + "./RenderObject.cpp" "./RenderQueue.cpp" "./create_device.cpp" diff --git a/src/graphic/RenderObject.cpp b/src/graphic/RenderObject.cpp new file mode 100644 index 0000000..4621b0f --- /dev/null +++ b/src/graphic/RenderObject.cpp @@ -0,0 +1,30 @@ +#include "gkit/graphic/render/RenderObject.hpp" + +namespace gkit::graphic { + + RenderObject::RenderObject(const std::vector& vertices, + const std::vector& indices, + const VertexBufferLayout& layout, + const Material& material) : + material(material), vertices(vertices), indices(indices), layout(layout) {} + + auto RenderObject::ensure_uploaded(RenderDevice& device) -> const VertexArray& { + if (this->uploaded) { + return *this->vao; + } + + // Create and cache GPU resources (hidden from the user). + this->vbo = device.create_vertex_buffer(this->vertices.data(), this->vertices.size() * sizeof(float), false); + this->ibo = device.create_index_buffer(this->indices.data(), static_cast(this->indices.size())); + this->vao = device.create_vertex_array(); + this->vao->add_buffer(*this->vbo, this->layout); + + this->uploaded = true; + return *this->vao; + } + + auto RenderObject::index_buffer() -> const IndexBuffer& { + return *this->ibo; + } + +} // namespace gkit::graphic diff --git a/src/graphic/RenderQueue.cpp b/src/graphic/RenderQueue.cpp index 2a0f02f..131f8ef 100644 --- a/src/graphic/RenderQueue.cpp +++ b/src/graphic/RenderQueue.cpp @@ -1,5 +1,7 @@ #include "gkit/graphic/render/RenderQueue.hpp" +#include "gkit/graphic/render/RenderObject.hpp" + #include #include @@ -8,10 +10,10 @@ namespace gkit::graphic { namespace { /// @brief Bind a texture slot to the shader sampler unit - auto bind_textures(const RenderCommand& cmd) -> void { - for (uint32_t i = 0; i < cmd.texture_count && i < MAX_TEXTURE_SLOTS; ++i) { - if (cmd.textures[i] != nullptr) { - cmd.textures[i]->bind(i); + auto bind_textures(const Material& material) -> void { + for (uint32_t i = 0; i < material.texture_count && i < MAX_TEXTURE_SLOTS; ++i) { + if (material.textures[i] != nullptr) { + material.textures[i]->bind(i); } } } @@ -38,15 +40,15 @@ namespace gkit::graphic { value); } - /// @brief Apply simple-path uniforms (per-name list) - auto apply_uniforms(const RenderCommand& cmd) -> void { - if (cmd.shader == nullptr) { + /// @brief Apply material uniforms (simple path) + auto apply_uniforms(const Material& material) -> void { + if (material.shader == nullptr) { return; } - for (const auto& [name, value] : cmd.uniforms.values) { - apply_uniform_value(*cmd.shader, name, value); + for (const auto& [name, value] : material.uniforms.values) { + apply_uniform_value(*material.shader, name, value); } - // TODO(Step 6+/UBO): upload cmd.ubo via a UniformBuffer backend once implemented. + // TODO(graphic): upload material.ubo via a UniformBuffer backend once implemented. } /// @brief Sort comparator: framebuffer commands first, then by state/transparency @@ -54,9 +56,9 @@ namespace gkit::graphic { // Render targets (FBO) must be drawn before screen commands, otherwise // post-processing cannot sample the FBO attachment. So target=null (screen) // sorts after any non-null target. Then group by state (reduce switches). - // Within the same target, opaque front-to-back / transparent back-to-front. + const bool blend_enabled = (cmd.object != nullptr) && cmd.object->state.blend.enabled; const uint64_t target_rank = (cmd.target != nullptr) ? 0 : 1; // FBO before screen - return (target_rank << 56) | (static_cast(cmd.state.blend.enabled) << 48) | + return (target_rank << 56) | (static_cast(blend_enabled) << 48) | (static_cast(cmd.transparent) << 40); } @@ -77,6 +79,11 @@ namespace gkit::graphic { const FrameBuffer* last_target = nullptr; for (const auto& cmd : this->commands) { + if (cmd.object == nullptr) { + continue; + } + const Material& material = cmd.object->material; + // Switch render target: unbind the previous FBO (reverting to screen) // before binding a different target. target=null means screen, reached // by unbinding the previous FBO. @@ -98,20 +105,21 @@ namespace gkit::graphic { device.clear(cmd.clear_flags); } - device.apply_state(cmd.state); + device.apply_state(cmd.object->state); - if (cmd.shader != nullptr) { - cmd.shader->bind(); + // Lazily upload geometry and bind shader/textures/uniforms. + const auto& vao = cmd.object->ensure_uploaded(device); + const auto& ibo = cmd.object->index_buffer(); + if (material.shader != nullptr) { + material.shader->bind(); } - bind_textures(cmd); - apply_uniforms(cmd); - - if (cmd.vertex_array != nullptr && cmd.index_buffer != nullptr && cmd.shader != nullptr) { - if (cmd.instance_count > 1) { - device.draw_instance(*cmd.vertex_array, *cmd.index_buffer, *cmd.shader, cmd.instance_count); - } else { - device.draw(*cmd.vertex_array, *cmd.index_buffer, *cmd.shader); - } + bind_textures(material); + apply_uniforms(material); + + if (cmd.instance_count > 1) { + device.draw_instance(vao, ibo, *material.shader, cmd.instance_count); + } else { + device.draw(vao, ibo, *material.shader); } } diff --git a/src/graphic/Renderer.cpp b/src/graphic/Renderer.cpp index 1e7593b..1f35c3a 100644 --- a/src/graphic/Renderer.cpp +++ b/src/graphic/Renderer.cpp @@ -12,8 +12,17 @@ namespace gkit::graphic { this->get_device().clear(flags); } - auto Renderer::draw(const RenderObject& obj) -> void { - this->queue.submit(obj.to_command()); + auto Renderer::draw(RenderObject& obj, const FrameBuffer* target, const Viewport& viewport) -> void { + RenderCommand cmd; + cmd.object = &obj; // lazily uploaded on execute + cmd.target = target; + cmd.viewport = viewport; + cmd.instance_count = obj.instance_count; + cmd.transparent = obj.transparent; + cmd.depth_key = obj.depth_key; + cmd.clear = obj.clear; + cmd.clear_flags = obj.clear_flags; + this->queue.submit(cmd); } auto Renderer::flush() -> void { diff --git a/test/graphic/test_window.cpp b/test/graphic/test_window.cpp index 4e39b3f..b8654e0 100644 --- a/test/graphic/test_window.cpp +++ b/test/graphic/test_window.cpp @@ -71,41 +71,45 @@ int main(int argc, char* argv[]) { #pragma region triangle // Colored triangle vertex data (position + color) - float tri_vertices[] = {// positions // colors - 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // top: red - -0.4f, -0.25f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom-left: green - 0.4f, -0.25f, 0.0f, 0.0f, 0.0f, 1.0f}; // bottom-right: blue - - // index data - unsigned int tri_indices[] = {0, 1, 2}; - - auto tri_vao = device.create_vertex_array(); - auto tri_vbo = device.create_vertex_buffer(tri_vertices, sizeof(tri_vertices), false); - auto tri_ibo = device.create_index_buffer(tri_indices, 3); + std::vector tri_vertices = {// positions // colors + 0.0f, + 0.5f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, // top: red + -0.4f, + -0.25f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, // bottom-left: green + 0.4f, + -0.25f, + 0.0f, + 0.0f, + 0.0f, + 1.0f}; // bottom-right: blue + + std::vector tri_indices = {0, 1, 2}; gkit::graphic::VertexBufferLayout tri_layout; tri_layout.push(3); // position tri_layout.push(3); // color - tri_vao->add_buffer(*tri_vbo, tri_layout); // load shader source auto tri_shader = device.create_shader((resource_base / "graphic" / "color_triangle.shader").string()); // Full-screen quad vertex data (post-processing) - float quad_vertices[] = {// 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}; + std::vector quad_vertices = {// 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 quad_indices[] = {0, 1, 2, 2, 3, 0}; - - auto quad_vao = device.create_vertex_array(); - auto quad_vb = device.create_vertex_buffer(quad_vertices, sizeof(quad_vertices), false); - auto quad_ib = device.create_index_buffer(quad_indices, 6); + std::vector quad_indices = {0, 1, 2, 2, 3, 0}; gkit::graphic::VertexBufferLayout quad_layout; quad_layout.push(3); quad_layout.push(2); - quad_vao->add_buffer(*quad_vb, quad_layout); // load post-processing shader auto post_shader = device.create_shader((resource_base / "graphic" / "post_process.shader").string()); @@ -115,7 +119,7 @@ int main(int argc, char* argv[]) { // FBO is half the window size. const int fbo_width = screen_width; const int fbo_height = screen_height; - auto fbo = device.create_frame_buffer(fbo_width, fbo_height); + auto fbo = device.create_frame_buffer(fbo_width, fbo_height); gkit::graphic::opengl::Texture fbo_texture(" ", gkit::graphic::TextureType::TextureFramebuffer); auto rbo = device.create_render_buffer(fbo_width, fbo_height); fbo->attach_color_texture(fbo_texture, 0); @@ -124,32 +128,23 @@ int main(int argc, char* argv[]) { #pragma endregion #pragma region render_objects - // Reusable draw units (geometry + material + state), built once and - // submitted each frame via Renderer::draw(RenderObject). - gkit::graphic::RenderObject triangle_to_fbo; - triangle_to_fbo.target = fbo.get(); - triangle_to_fbo.vertex_array = tri_vao.get(); - triangle_to_fbo.index_buffer = tri_ibo.get(); - triangle_to_fbo.shader = tri_shader.get(); - triangle_to_fbo.viewport = {0, 0, fbo_width, fbo_height}; // FBO size - triangle_to_fbo.clear = true; // clear FBO color/depth before drawing - - gkit::graphic::RenderObject post_quad; - post_quad.target = nullptr; // screen - post_quad.vertex_array = quad_vao.get(); - post_quad.index_buffer = quad_ib.get(); - post_quad.shader = post_shader.get(); - post_quad.textures[0] = &fbo_texture; - post_quad.texture_count = 1; - post_quad.uniforms.values.push_back({"screenTexture", 0}); - post_quad.viewport = {0, 0, screen_width, screen_height}; // window size - - gkit::graphic::RenderObject overlay_triangle; - overlay_triangle.target = nullptr; // screen - overlay_triangle.vertex_array = tri_vao.get(); - overlay_triangle.index_buffer = tri_ibo.get(); - overlay_triangle.shader = tri_shader.get(); - overlay_triangle.viewport = {0, 0, screen_width/2, screen_height/2}; // window size + // Reusable draw units: user provides data arrays + material; VAO/VBO/IBO hidden. + + // Triangle material + gkit::graphic::Material tri_material; + tri_material.shader = tri_shader.get(); + + // Post-processing quad material (samples the FBO texture) + gkit::graphic::Material post_material; + post_material.shader = post_shader.get(); + post_material.textures[0] = &fbo_texture; + post_material.texture_count = 1; + post_material.uniforms.values.push_back({"screenTexture", 0}); + + gkit::graphic::RenderObject triangle_obj(tri_vertices, tri_indices, tri_layout, tri_material); + triangle_obj.clear = true; // clear the FBO color/depth before drawing + + gkit::graphic::RenderObject quad_obj(quad_vertices, quad_indices, quad_layout, post_material); #pragma endregion // Main loop @@ -167,12 +162,11 @@ int main(int argc, char* argv[]) { } } - //renderer.clear(gkit::graphic::ClearFlags::All); - // Submit reusable render objects; Renderer enqueues them and flush() executes. - renderer.draw(triangle_to_fbo); // 1. Triangle to framebuffer - renderer.draw(post_quad); // 2. Post-processing quad to screen (samples fbo) - renderer.draw(overlay_triangle); // 3. Small triangle overlay + // Draw 1: triangle to the FBO (target = fbo) + renderer.draw(triangle_obj, fbo.get(), {0, 0, fbo_width, fbo_height}); + // Draw 2: post-processing quad to screen (samples fbo texture) + renderer.draw(quad_obj, nullptr, {0, 0, screen_width, screen_height}); renderer.flush(); From 2e4ba19992129c35dc82857cf83cbeee3511c3a2 Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Tue, 4 Aug 2026 22:57:17 +0800 Subject: [PATCH 13/17] feat(graphic): default target/viewport args for Renderer::draw - draw(RenderObject&, target = nullptr, viewport = full window) - allows draw(obj) to render to the screen at full window by default --- include/gkit/graphic/render/Renderer.hpp | 9 +++++--- test/graphic/test_window.cpp | 27 ++++++------------------ 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/include/gkit/graphic/render/Renderer.hpp b/include/gkit/graphic/render/Renderer.hpp index 2c80828..be1ae08 100644 --- a/include/gkit/graphic/render/Renderer.hpp +++ b/include/gkit/graphic/render/Renderer.hpp @@ -41,12 +41,15 @@ namespace gkit::graphic { /** * @brief Enqueue a draw from a reusable render object * @param obj Render object (geometry + material + state) - * @param target render target (nullptr = screen) - * @param viewport viewport to use for this draw (per-target size) + * @param target render target (default nullptr = screen) + * @param viewport viewport to use for this draw (default full window) * @note Enqueued into the render queue; executed on flush(). The object is * non-const because its GPU resources are lazily uploaded on execute. */ - auto draw(RenderObject& obj, const FrameBuffer* target, const Viewport& viewport) -> void; + auto draw(RenderObject& obj, + const FrameBuffer* target = nullptr, + const Viewport& viewport = Viewport{0, 0, static_cast(SCR_WIDTH), static_cast(SCR_HEIGHT)}) + -> void; /** * @brief Execute the queued render commands (sort + apply state + draw) diff --git a/test/graphic/test_window.cpp b/test/graphic/test_window.cpp index b8654e0..12cb191 100644 --- a/test/graphic/test_window.cpp +++ b/test/graphic/test_window.cpp @@ -72,24 +72,9 @@ int main(int argc, char* argv[]) { #pragma region triangle // Colored triangle vertex data (position + color) std::vector tri_vertices = {// positions // colors - 0.0f, - 0.5f, - 0.0f, - 1.0f, - 0.0f, - 0.0f, // top: red - -0.4f, - -0.25f, - 0.0f, - 0.0f, - 1.0f, - 0.0f, // bottom-left: green - 0.4f, - -0.25f, - 0.0f, - 0.0f, - 0.0f, - 1.0f}; // bottom-right: blue + 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // top: red + -0.4f, -0.25f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom-left: green + 0.4f, -0.25f, 0.0f, 0.0f, 0.0f, 1.0f}; // bottom-right: blue std::vector tri_indices = {0, 1, 2}; @@ -102,8 +87,10 @@ int main(int argc, char* argv[]) { // Full-screen quad vertex data (post-processing) std::vector quad_vertices = {// 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}; + -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}; std::vector quad_indices = {0, 1, 2, 2, 3, 0}; From a03cba845ebb616dd8a0653a130e5fbc3495c2e2 Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Tue, 4 Aug 2026 23:15:41 +0800 Subject: [PATCH 14/17] test(graphic): rename test_window to test_render, use default draw args - Rename test/graphic/test_window.cpp to test_render.cpp (GLOB picks it up, produces test_render.exe) - Use new RenderObject API with default target/viewport: renderer.draw(obj) for screen full-window, explicit target for FBO draws --- test/graphic/{test_window.cpp => test_render.cpp} | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) rename test/graphic/{test_window.cpp => test_render.cpp} (93%) diff --git a/test/graphic/test_window.cpp b/test/graphic/test_render.cpp similarity index 93% rename from test/graphic/test_window.cpp rename to test/graphic/test_render.cpp index 12cb191..2c68026 100644 --- a/test/graphic/test_window.cpp +++ b/test/graphic/test_render.cpp @@ -129,7 +129,6 @@ int main(int argc, char* argv[]) { post_material.uniforms.values.push_back({"screenTexture", 0}); gkit::graphic::RenderObject triangle_obj(tri_vertices, tri_indices, tri_layout, tri_material); - triangle_obj.clear = true; // clear the FBO color/depth before drawing gkit::graphic::RenderObject quad_obj(quad_vertices, quad_indices, quad_layout, post_material); #pragma endregion @@ -151,9 +150,13 @@ int main(int argc, char* argv[]) { // Submit reusable render objects; Renderer enqueues them and flush() executes. // Draw 1: triangle to the FBO (target = fbo) - renderer.draw(triangle_obj, fbo.get(), {0, 0, fbo_width, fbo_height}); + triangle_obj.clear = true; // clear the FBO color/depth before drawing + renderer.draw(triangle_obj, fbo.get()); // Draw 2: post-processing quad to screen (samples fbo texture) - renderer.draw(quad_obj, nullptr, {0, 0, screen_width, screen_height}); + renderer.draw(quad_obj); + // Draw 3:triangle to screen (no post-processing, just for comparison) + triangle_obj.clear = false; + renderer.draw(triangle_obj, nullptr, gkit::graphic::Viewport{.x=0, .y=0, .width=screen_width / 2, .height=screen_height / 2}); renderer.flush(); From 248b03cbfb7e08aae240cf03d5641885ed38aafb Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Wed, 5 Aug 2026 01:29:51 +0800 Subject: [PATCH 15/17] fix(graphic): harden render queue pipeline - Size FBO color textures from the FBO, not the global SCR_WIDTH/SCR_HEIGHT - Default per-command viewport to the render target size (optional viewport) - Snapshot RenderState into RenderCommand so per-draw state is self-contained - Force GL state sync on Device construction (initial shadow-state divergence) - Reject draw commands with missing or invalid shaders at enqueue time - Clear the screen once per frame in the render test --- include/gkit/graphic/render/RenderCommand.hpp | 12 ++++++---- include/gkit/graphic/resource/FrameBuffer.hpp | 16 ++++++++++++- include/gkit/graphic/resource/Shader.hpp | 8 +++++++ src/graphic/RenderQueue.cpp | 18 +++++++++------ src/graphic/Renderer.cpp | 23 +++++++++++++++++++ src/graphic/backend/opengl/Device.cpp | 4 ++++ src/graphic/backend/opengl/Device.hpp | 2 +- src/graphic/backend/opengl/FrameBuffer.cpp | 9 ++++++-- src/graphic/backend/opengl/FrameBuffer.hpp | 5 +++- src/graphic/backend/opengl/Shader.cpp | 5 ++++ src/graphic/backend/opengl/Shader.hpp | 2 ++ src/graphic/backend/opengl/Texture.cpp | 16 +++++++++++++ src/graphic/backend/opengl/Texture.hpp | 11 +++++++++ test/graphic/test_render.cpp | 6 +++++ 14 files changed, 121 insertions(+), 16 deletions(-) diff --git a/include/gkit/graphic/render/RenderCommand.hpp b/include/gkit/graphic/render/RenderCommand.hpp index 6635781..5f87d98 100644 --- a/include/gkit/graphic/render/RenderCommand.hpp +++ b/include/gkit/graphic/render/RenderCommand.hpp @@ -1,9 +1,11 @@ #pragma once #include "gkit/graphic/config.hpp" +#include "gkit/graphic/render/RenderState.hpp" #include "gkit/graphic/resource/FrameBuffer.hpp" #include +#include namespace gkit::graphic { @@ -27,13 +29,15 @@ namespace gkit::graphic { * @brief A single draw command referencing a render object * * Value type; references (not owns) the RenderObject and its target. - * Geometry/material/state are read from the RenderObject; the command only - * carries per-draw controls (target, viewport, clear, sorting metadata). + * The command carries a snapshot of the geometry's render state (so two + * commands can share one RenderObject with different states) plus per-draw + * controls (target, viewport, clear, sorting metadata). */ struct RenderCommand { const FrameBuffer* target = nullptr; // Render target (nullptr = screen) - RenderObject* object = nullptr; // Geometry + material + state source (lazily uploaded on execute) - Viewport viewport; // Viewport to set before drawing (per-target size) + RenderObject* object = nullptr; // Geometry + material source (lazily uploaded on execute) + std::optional viewport; // Viewport to set before drawing; empty = target size + RenderState state; // Render state snapshot (applied before drawing) uint32_t instance_count = 1; // 1 = non-instanced bool transparent = false; // Sort front-to-back (opaque) or back-to-front (transparent) float depth_key = 0.0f; // Depth sort key (filled by upper layer) diff --git a/include/gkit/graphic/resource/FrameBuffer.hpp b/include/gkit/graphic/resource/FrameBuffer.hpp index 105d736..3226911 100644 --- a/include/gkit/graphic/resource/FrameBuffer.hpp +++ b/include/gkit/graphic/resource/FrameBuffer.hpp @@ -25,8 +25,12 @@ namespace gkit::graphic { /** * @brief Attach a color texture to the given slot + * + * @param texture color attachment (non-const: a framebuffer texture may be + * resized to match this FBO's dimensions on attach) + * @param slot color attachment slot */ - virtual auto attach_color_texture(const Texture& texture, int slot) -> void = 0; + virtual auto attach_color_texture(Texture& texture, int slot) -> void = 0; /** * @brief Detach the color texture from the given slot @@ -68,6 +72,16 @@ namespace gkit::graphic { */ virtual auto bind() const -> void = 0; + /** + * @brief Framebuffer width in pixels + */ + [[nodiscard]] virtual auto width() const -> int = 0; + + /** + * @brief Framebuffer height in pixels + */ + [[nodiscard]] virtual auto height() const -> int = 0; + /** * @brief Unbind, reverting to the default framebuffer (screen) */ diff --git a/include/gkit/graphic/resource/Shader.hpp b/include/gkit/graphic/resource/Shader.hpp index 0d542b0..4bd00a8 100644 --- a/include/gkit/graphic/resource/Shader.hpp +++ b/include/gkit/graphic/resource/Shader.hpp @@ -44,6 +44,14 @@ namespace gkit::graphic { */ virtual auto unbind() const -> void = 0; + /** + * @brief Whether the shader program compiled and linked successfully + * + * A shader created from a file whose source failed to compile/link is + * invalid; rendering with it is undefined, so the queue rejects it. + */ + [[nodiscard]] virtual auto is_valid() const -> bool = 0; + // Uniform setters (implemented by backends, mapped to the concrete API) virtual auto set_uniform_1i(const std::string& name, int value) -> void = 0; diff --git a/src/graphic/RenderQueue.cpp b/src/graphic/RenderQueue.cpp index 131f8ef..751d46e 100644 --- a/src/graphic/RenderQueue.cpp +++ b/src/graphic/RenderQueue.cpp @@ -56,7 +56,7 @@ namespace gkit::graphic { // Render targets (FBO) must be drawn before screen commands, otherwise // post-processing cannot sample the FBO attachment. So target=null (screen) // sorts after any non-null target. Then group by state (reduce switches). - const bool blend_enabled = (cmd.object != nullptr) && cmd.object->state.blend.enabled; + const bool blend_enabled = cmd.state.blend.enabled; const uint64_t target_rank = (cmd.target != nullptr) ? 0 : 1; // FBO before screen return (target_rank << 56) | (static_cast(blend_enabled) << 48) | (static_cast(cmd.transparent) << 40); @@ -97,22 +97,26 @@ namespace gkit::graphic { } } - // GL viewport is global state; each command sets its own per-target viewport. - device.set_viewport(cmd.viewport); + // GL viewport is global state; each command sets its own viewport, + // defaulting to the render target size when none was specified. + const Viewport vp = cmd.viewport.value_or( + cmd.target != nullptr ? Viewport{.x=0, .y=0, .width=cmd.target->width(), .height=cmd.target->height()} + : Viewport{.x=0, .y=0, .width=static_cast(SCR_WIDTH), .height=static_cast(SCR_HEIGHT)}); + device.set_viewport(vp); // Clear the currently bound target (FBO or screen) if the command asks for it. if (cmd.clear) { device.clear(cmd.clear_flags); } - device.apply_state(cmd.object->state); + device.apply_state(cmd.state); // Lazily upload geometry and bind shader/textures/uniforms. + // Shader validity is enforced at enqueue time (Renderer::draw), so the + // program is guaranteed non-null and valid here. const auto& vao = cmd.object->ensure_uploaded(device); const auto& ibo = cmd.object->index_buffer(); - if (material.shader != nullptr) { - material.shader->bind(); - } + material.shader->bind(); bind_textures(material); apply_uniforms(material); diff --git a/src/graphic/Renderer.cpp b/src/graphic/Renderer.cpp index 1f35c3a..99881c0 100644 --- a/src/graphic/Renderer.cpp +++ b/src/graphic/Renderer.cpp @@ -1,5 +1,6 @@ #include "gkit/graphic/render/Renderer.hpp" +#include "gkit/core/log.hpp" #include "gkit/graphic/render/RenderDevice.hpp" namespace gkit::graphic { @@ -13,10 +14,32 @@ namespace gkit::graphic { } auto Renderer::draw(RenderObject& obj, const FrameBuffer* target, const Viewport& viewport) -> void { + // Reject shaderless materials: a null shader cannot be bound, and one that + // failed to compile/link is undefined to render with. Drawing with either + // would dereference a null/invalid program, so refuse to enqueue instead. + const Shader* shader = obj.material.shader; + if (shader == nullptr) { + core::Log::Message msg; + msg.level = core::Log::LogLevel::Error; + msg.functions = static_cast(core::Log::LogFunction::Both); + msg.message = "Renderer::draw: object has no shader; command rejected"; + core::Log::instance().log(std::move(msg)); + return; + } + if (!shader->is_valid()) { + core::Log::Message msg; + msg.level = core::Log::LogLevel::Error; + msg.functions = static_cast(core::Log::LogFunction::Both); + msg.message = "Renderer::draw: object shader is invalid (failed to compile/link); command rejected"; + core::Log::instance().log(std::move(msg)); + return; + } + RenderCommand cmd; cmd.object = &obj; // lazily uploaded on execute cmd.target = target; cmd.viewport = viewport; + cmd.state = obj.state; // snapshot: each command carries its own state cmd.instance_count = obj.instance_count; cmd.transparent = obj.transparent; cmd.depth_key = obj.depth_key; diff --git a/src/graphic/backend/opengl/Device.cpp b/src/graphic/backend/opengl/Device.cpp index b31c2ae..53899b2 100644 --- a/src/graphic/backend/opengl/Device.cpp +++ b/src/graphic/backend/opengl/Device.cpp @@ -12,6 +12,10 @@ namespace gkit::graphic::opengl { + Device::Device() { + this->state_manager.force_apply_all(); + } + auto Device::create_vertex_buffer(const void* data, uint32_t size, bool dynamic) -> std::unique_ptr { // Direct new (not make_unique) so Device's friendship grants access to the diff --git a/src/graphic/backend/opengl/Device.hpp b/src/graphic/backend/opengl/Device.hpp index ae7102e..45ed2ff 100644 --- a/src/graphic/backend/opengl/Device.hpp +++ b/src/graphic/backend/opengl/Device.hpp @@ -16,7 +16,7 @@ namespace gkit::graphic::opengl { class Device final : public graphic::RenderDevice { public: - Device() = default; + Device(); ~Device() override = default; auto create_vertex_buffer(const void* data, uint32_t size, bool dynamic) diff --git a/src/graphic/backend/opengl/FrameBuffer.cpp b/src/graphic/backend/opengl/FrameBuffer.cpp index 013b9ee..637b956 100644 --- a/src/graphic/backend/opengl/FrameBuffer.cpp +++ b/src/graphic/backend/opengl/FrameBuffer.cpp @@ -43,9 +43,10 @@ namespace gkit::graphic::opengl { } } - auto FrameBuffer::attach_color_texture(const graphic::Texture& texture, int slot) -> void { + auto FrameBuffer::attach_color_texture(graphic::Texture& texture, int slot) -> void { bind(); - const auto& gl_texture = static_cast(texture); + auto& gl_texture = static_cast(texture); + gl_texture.set_size(static_cast(this->fb_width), static_cast(this->fb_height)); glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + slot, GL_TEXTURE_2D, gl_texture.get_renderer_id(), 0); } @@ -101,4 +102,8 @@ namespace gkit::graphic::opengl { glBindFramebuffer(GL_FRAMEBUFFER, 0); } + auto FrameBuffer::width() const -> int { return static_cast(this->fb_width); } + + auto FrameBuffer::height() const -> int { return static_cast(this->fb_height); } + } // namespace gkit::graphic::opengl diff --git a/src/graphic/backend/opengl/FrameBuffer.hpp b/src/graphic/backend/opengl/FrameBuffer.hpp index 9d02f52..745e955 100644 --- a/src/graphic/backend/opengl/FrameBuffer.hpp +++ b/src/graphic/backend/opengl/FrameBuffer.hpp @@ -23,7 +23,7 @@ namespace gkit::graphic::opengl { ~FrameBuffer() override; - auto attach_color_texture(const graphic::Texture& texture, int slot) -> void override; + auto attach_color_texture(graphic::Texture& texture, int slot) -> void override; auto detach_color_texture(int slot) -> void override; auto attach_depth_stencil(const graphic::RenderBuffer& rbo) -> void override; auto detach_depth_stencil() -> void override; @@ -34,6 +34,9 @@ namespace gkit::graphic::opengl { auto bind() const -> void override; auto unbind() const -> void override; + [[nodiscard]] auto width() const -> int override; + [[nodiscard]] auto height() const -> int override; + private: explicit FrameBuffer(int width, int height); diff --git a/src/graphic/backend/opengl/Shader.cpp b/src/graphic/backend/opengl/Shader.cpp index 9808c70..3eee402 100644 --- a/src/graphic/backend/opengl/Shader.cpp +++ b/src/graphic/backend/opengl/Shader.cpp @@ -171,6 +171,11 @@ namespace gkit::graphic::opengl { glUseProgram(0); } + auto Shader::is_valid() const -> bool { + // renderer_id is 0 when compilation/linking failed or the program was moved. + return this->renderer_id != 0; + } + auto Shader::set_uniform_1i(const std::string& name, int value) -> void { glUniform1i(get_uniform_location(name), value); } diff --git a/src/graphic/backend/opengl/Shader.hpp b/src/graphic/backend/opengl/Shader.hpp index 441e71d..f828d88 100644 --- a/src/graphic/backend/opengl/Shader.hpp +++ b/src/graphic/backend/opengl/Shader.hpp @@ -29,6 +29,8 @@ namespace gkit::graphic::opengl { auto bind() const -> void override; auto unbind() const -> void override; + [[nodiscard]] auto is_valid() const -> bool override; + auto set_uniform_1i(const std::string& name, int value) -> void override; auto set_uniform_1f(const std::string& name, float value) -> void override; auto set_uniform_4f(const std::string& name, float v0, float v1, float v2, float v3) -> void override; diff --git a/src/graphic/backend/opengl/Texture.cpp b/src/graphic/backend/opengl/Texture.cpp index 438940b..4c226a4 100644 --- a/src/graphic/backend/opengl/Texture.cpp +++ b/src/graphic/backend/opengl/Texture.cpp @@ -24,6 +24,22 @@ gkit::graphic::opengl::Texture::Texture(const std::string& path, gkit::graphic:: } } +auto gkit::graphic::opengl::Texture::set_size(int width, int height) -> void { + // Only framebuffer textures are (re)allocated on attach; loaded images must + // keep their pixel data. + if (this->type != gkit::graphic::TextureType::TextureFramebuffer) { + return; + } + if (this->width == width && this->height == height) { + return; + } + this->width = width; + this->height = height; + glBindTexture(GL_TEXTURE_2D, this->renderer_id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, this->width, this->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glBindTexture(GL_TEXTURE_2D, 0); +} + gkit::graphic::opengl::Texture::~Texture() { delete[] this->local_buffer; if (this->renderer_id != 0) { diff --git a/src/graphic/backend/opengl/Texture.hpp b/src/graphic/backend/opengl/Texture.hpp index 10842aa..4fc0bd2 100644 --- a/src/graphic/backend/opengl/Texture.hpp +++ b/src/graphic/backend/opengl/Texture.hpp @@ -48,6 +48,17 @@ namespace gkit::graphic::opengl { */ [[nodiscard]] inline auto get_renderer_id() const -> uint32_t { return this->renderer_id; } + /** + * @brief Resize the texture storage (framebuffer textures only) + * + * A framebuffer texture is allocated empty at construction; attaching it + * to a FrameBuffer reallocates storage to the FBO size so the texture is + * never bound to the global SCR_WIDTH/SCR_HEIGHT constants. + * @param width new width in pixels + * @param height new height in pixels + */ + auto set_size(int width, int height) -> void; + private: inline static const std::vector FACES = { "right.jpg", "left.jpg", "top.jpg", "bottom.jpg", "front.jpg", "back.jpg"}; diff --git a/test/graphic/test_render.cpp b/test/graphic/test_render.cpp index 2c68026..554549e 100644 --- a/test/graphic/test_render.cpp +++ b/test/graphic/test_render.cpp @@ -1,4 +1,5 @@ #include "gkit/graphic/VertexBufferLayout.hpp" +#include "gkit/graphic/config.hpp" #include "gkit/graphic/render/Renderer.hpp" #include "graphic/backend/opengl/Texture.hpp" @@ -148,6 +149,11 @@ int main(int argc, char* argv[]) { } } + // Clear the default framebuffer (screen) every frame. The default + // framebuffer content is undefined at startup; clearing once per frame + // keeps regions outside the post-process quad deterministic. + renderer.clear(gkit::graphic::ClearFlags::Color); + // Submit reusable render objects; Renderer enqueues them and flush() executes. // Draw 1: triangle to the FBO (target = fbo) triangle_obj.clear = true; // clear the FBO color/depth before drawing From f1fdd952a95c69f47b63d77675ac715c1c64b1db Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Wed, 5 Aug 2026 02:23:35 +0800 Subject: [PATCH 16/17] test(graphic): cover blending, depth, and stencil masking in test_render - Add a translucent triangle (alpha shader + SrcAlpha blend, 1:1 blend at u_alpha=0.8), offset 50px to the bottom-left, depth-tested between the quad and the comparison triangle - Enable depth testing on the opaque objects so screen depth is real - Add a stencil-mask pass in the FBO: a stencil triangle offset 50px up-right writes stencil=1 (Always/Replace), the FBO color is then cleared (keeping stencil), and the next triangle draws with NotEqual(1) so the masked region is left empty --- test/graphic/alpha_triangle.shader | 27 +++++++ test/graphic/test_render.cpp | 116 +++++++++++++++++++++++++---- 2 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 test/graphic/alpha_triangle.shader diff --git a/test/graphic/alpha_triangle.shader b/test/graphic/alpha_triangle.shader new file mode 100644 index 0000000..be732d8 --- /dev/null +++ b/test/graphic/alpha_triangle.shader @@ -0,0 +1,27 @@ +#shader vertex +#version 450 core +layout(location = 0) in vec3 aPos; +layout(location = 1) in vec3 aColor; + +out vec3 v_Color; + +void main() +{ + gl_Position = vec4(aPos, 1.0); + v_Color = aColor; +} + +#shader fragment +#version 450 core +out vec4 FragColor; + +in vec3 v_Color; + +// Alpha factor for the blend test; defaults to opaque so an unset uniform +// does not silently make the object invisible. +uniform float u_alpha = 1.0; + +void main() +{ + FragColor = vec4(v_Color, u_alpha); +} diff --git a/test/graphic/test_render.cpp b/test/graphic/test_render.cpp index 554549e..faa672f 100644 --- a/test/graphic/test_render.cpp +++ b/test/graphic/test_render.cpp @@ -86,12 +86,14 @@ int main(int argc, char* argv[]) { // load shader source auto tri_shader = device.create_shader((resource_base / "graphic" / "color_triangle.shader").string()); - // Full-screen quad vertex data (post-processing) + // Full-screen quad vertex data (post-processing). + // z = 0.2 (farthest): it depth-tests first and writes the deepest value, + // so the later triangles (z = 0.1 and z = 0) depth-test against it. std::vector quad_vertices = {// 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}; + -1.0f, -1.0f, 0.2f, 0.0f, 0.0f, + 1.0f, -1.0f, 0.2f, 1.0f, 0.0f, + 1.0f, 1.0f, 0.2f, 1.0f, 1.0f, + -1.0f, 1.0f, 0.2f, 0.0f, 1.0f}; std::vector quad_indices = {0, 1, 2, 2, 3, 0}; @@ -101,6 +103,9 @@ int main(int argc, char* argv[]) { // load post-processing shader auto post_shader = device.create_shader((resource_base / "graphic" / "post_process.shader").string()); + + // load alpha-blended triangle shader (u_alpha uniform controls opacity) + auto alpha_shader = device.create_shader((resource_base / "graphic" / "alpha_triangle.shader").string()); #pragma endregion #pragma region framebuffer @@ -129,9 +134,81 @@ int main(int argc, char* argv[]) { post_material.texture_count = 1; post_material.uniforms.values.push_back({"screenTexture", 0}); + // Opaque objects enable depth testing so they write their depth and the + // translucent triangle is really depth-tested against them (a disabled + // depth test does not write the depth buffer, which would leave it empty). gkit::graphic::RenderObject triangle_obj(tri_vertices, tri_indices, tri_layout, tri_material); + triangle_obj.state.depth.enabled = true; gkit::graphic::RenderObject quad_obj(quad_vertices, quad_indices, quad_layout, post_material); + quad_obj.state.depth.enabled = true; + + // Transparent triangle: reuses the triangle's shape (positions + colors), + // offset 50 px toward the bottom-left (NDC: 500 px window → 1 px = 0.004, + // 50 px = 0.2). z = 0.1 sits between the quad (z = 0.2, farthest) and the + // comparison triangle (z = 0, nearest), so depth testing decides the layering + // among the three. It blends with what is already on screen + // (SrcAlpha / OneMinusSrcAlpha, u_alpha = 0.8 → 0.8·src + 0.2·dst). + const float px_to_ndc = 2.0f / static_cast(screen_width); // 1 px in NDC + const float offset_x = -50.0f * px_to_ndc; + const float offset_y = -50.0f * px_to_ndc; + std::vector alpha_vertices; + for (std::size_t v = 0; v < tri_vertices.size(); v += 6) { + alpha_vertices.push_back(tri_vertices[v] + offset_x); // position x + alpha_vertices.push_back(tri_vertices[v + 1] + offset_y); // position y + alpha_vertices.push_back(0.1f); // position z: between quad (0.2) and comparison triangle (0) + // reuse the original colors (indices 3..5) + alpha_vertices.insert(alpha_vertices.end(), tri_vertices.begin() + v + 3, tri_vertices.begin() + v + 6); + } + + gkit::graphic::Material alpha_material; + alpha_material.shader = alpha_shader.get(); + alpha_material.uniforms.values.push_back({"u_alpha", 0.8f}); + + gkit::graphic::RenderObject alpha_triangle_obj(alpha_vertices, tri_indices, tri_layout, alpha_material); + alpha_triangle_obj.state.depth.enabled = true; // depth-tests against quad + comparison triangle + alpha_triangle_obj.state.blend.enabled = true; + alpha_triangle_obj.state.blend.src_rgb = gkit::graphic::BlendFunc::SrcAlpha; + alpha_triangle_obj.state.blend.dst_rgb = gkit::graphic::BlendFunc::OneMinusSrcAlpha; + alpha_triangle_obj.state.blend.src_alpha = gkit::graphic::BlendFunc::SrcAlpha; + alpha_triangle_obj.state.blend.dst_alpha = gkit::graphic::BlendFunc::OneMinusSrcAlpha; + alpha_triangle_obj.transparent = true; // sorted after opaque, back-to-front + alpha_triangle_obj.depth_key = 0.0f; // nearest transparent → drawn last + + // Stencil-mask triangle: offset 50 px right and 50 px up, written into the + // FBO as a stencil=1 region (color is irrelevant — the FBO color is cleared + // right after). Depth test stays disabled so it writes no depth and can't + // reject the later masked triangle. + const float right_offset = 50.0f * px_to_ndc; + const float up_offset = 35.0f * px_to_ndc; + std::vector stencil_vertices; + for (std::size_t v = 0; v < tri_vertices.size(); v += 6) { + stencil_vertices.push_back(tri_vertices[v] + right_offset); // position x + 50 px + stencil_vertices.push_back(tri_vertices[v + 1] + up_offset); // position y + 50 px + stencil_vertices.push_back(tri_vertices[v + 2]); // position z unchanged + // reuse the original colors (indices 3..5) + stencil_vertices.insert(stencil_vertices.end(), tri_vertices.begin() + v + 3, tri_vertices.begin() + v + 6); + } + + gkit::graphic::RenderObject stencil_triangle_obj(stencil_vertices, tri_indices, tri_layout, tri_material); + stencil_triangle_obj.state.stencil.enabled = true; + stencil_triangle_obj.state.stencil.compare_func = gkit::graphic::CompareFunc::Always; // always pass, just write + stencil_triangle_obj.state.stencil.ref = 1; + stencil_triangle_obj.state.stencil.write_mask = 0xFF; + stencil_triangle_obj.state.stencil.fail = gkit::graphic::StencilOp::Keep; + stencil_triangle_obj.state.stencil.z_fail = gkit::graphic::StencilOp::Keep; + stencil_triangle_obj.state.stencil.z_pass = gkit::graphic::StencilOp::Replace; // write stencil=ref=1 + + // Triangle drawn after the stencil mask: NotEqual(1) rejects fragments + // inside the mask region, leaving a hole there and drawing elsewhere. + gkit::graphic::RenderObject masked_triangle_obj(tri_vertices, tri_indices, tri_layout, tri_material); + masked_triangle_obj.state.depth.enabled = true; + masked_triangle_obj.state.stencil.enabled = true; + masked_triangle_obj.state.stencil.compare_func = gkit::graphic::CompareFunc::Notequal; // stencil==1 fails + masked_triangle_obj.state.stencil.ref = 1; + masked_triangle_obj.state.stencil.fail = gkit::graphic::StencilOp::Keep; + masked_triangle_obj.state.stencil.z_fail = gkit::graphic::StencilOp::Keep; + masked_triangle_obj.state.stencil.z_pass = gkit::graphic::StencilOp::Keep; #pragma endregion // Main loop @@ -149,20 +226,31 @@ int main(int argc, char* argv[]) { } } - // Clear the default framebuffer (screen) every frame. The default - // framebuffer content is undefined at startup; clearing once per frame - // keeps regions outside the post-process quad deterministic. - renderer.clear(gkit::graphic::ClearFlags::Color); + // Clear the default framebuffer (screen) every frame. Depth is cleared + // too: the translucent triangle depth-tests, so a stale depth buffer + // would reject it on later frames. + renderer.clear(gkit::graphic::ClearFlags::ColorDepth); // Submit reusable render objects; Renderer enqueues them and flush() executes. - // Draw 1: triangle to the FBO (target = fbo) - triangle_obj.clear = true; // clear the FBO color/depth before drawing - renderer.draw(triangle_obj, fbo.get()); - // Draw 2: post-processing quad to screen (samples fbo texture) + // FBO stencil-mask pass: + // Draw 1: clear the FBO (color+depth+stencil) then write stencil=1 in the + // mask triangle's region (offset 50 px up; color is irrelevant). + stencil_triangle_obj.clear = true; // clears color+depth+stencil (All) + renderer.draw(stencil_triangle_obj, fbo.get()); + // Draw 2: clear only the FBO color (keeps the stencil marks), then draw the + // triangle with stencil NotEqual(1): fragments inside the stencil + // region are rejected, leaving a hole. + masked_triangle_obj.clear = true; + masked_triangle_obj.clear_flags = gkit::graphic::ClearFlags::Color; + renderer.draw(masked_triangle_obj, fbo.get()); + // Draw 3: post-processing quad to screen (samples fbo texture) renderer.draw(quad_obj); - // Draw 3:triangle to screen (no post-processing, just for comparison) + // Draw 4:triangle to screen (no post-processing, just for comparison) triangle_obj.clear = false; renderer.draw(triangle_obj, nullptr, gkit::graphic::Viewport{.x=0, .y=0, .width=screen_width / 2, .height=screen_height / 2}); + // Draw 5: translucent triangle blended over the screen (depth-tested on + // top of the FBO quad; transparent flag sorts it last, so it's the top layer). + renderer.draw(alpha_triangle_obj); renderer.flush(); From 8215f4099f617b09ae317ebec37b3e041f9b75bb Mon Sep 17 00:00:00 2001 From: YuanSang <3152937201@qq.com> Date: Wed, 5 Aug 2026 02:51:22 +0800 Subject: [PATCH 17/17] docs(graphic): translate comments to English --- include/gkit/graphic/render/RenderState.hpp | 7 ++++--- include/gkit/graphic/resource/UniformBuffer.hpp | 13 ++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/include/gkit/graphic/render/RenderState.hpp b/include/gkit/graphic/render/RenderState.hpp index 0dd1c80..99e477d 100644 --- a/include/gkit/graphic/render/RenderState.hpp +++ b/include/gkit/graphic/render/RenderState.hpp @@ -51,10 +51,11 @@ namespace gkit::graphic { }; /** - * @brief Composite render state snapshot (排序键, 命令自携带) + * @brief Composite render state snapshot (sort key, carried by command) * - * 把深度/混合/剔除/模板四个状态打包成一个快照, - * 供 RenderCommand 携带、排序去重、以及 StateManager 增量应用。 + * Packs the depth/blend/cull/stencil states into one snapshot that is + * carried by RenderCommand, used for sorting/deduplication, and applied + * incrementally by StateManager. */ struct RenderState { DepthState depth; // Depth test state diff --git a/include/gkit/graphic/resource/UniformBuffer.hpp b/include/gkit/graphic/resource/UniformBuffer.hpp index aea0df6..b3e9656 100644 --- a/include/gkit/graphic/resource/UniformBuffer.hpp +++ b/include/gkit/graphic/resource/UniformBuffer.hpp @@ -21,19 +21,22 @@ namespace gkit::graphic { using UniformValue = std::variant; /** - * @brief Simple uniform set (逐条赋值) + * @brief Simple uniform set (value-by-value assignment) * - * 简单路径: 命令携带 name→value 列表, 执行器逐个 set_uniform_*。 + * Simple path: the command carries a name→value list, and the executor + * calls set_uniform_* for each entry. */ struct UniformData { std::vector> values; }; /** - * @brief UBO block reference (批量上传) + * @brief UBO block reference (bulk upload) * - * 批量路径: 命令携带用户参数结构体的引用, 执行器一次上传整个 block。 - * 持引用不拥有 —— 用户结构体须存活到 flush 结束(生命周期契约)。 + * Batch path: the command carries a reference to the user's parameter + * struct, and the executor uploads the whole block at once. + * Holds a reference, does not own — the user struct must stay alive until + * flush finishes (lifetime contract). */ struct UboBlock { const void* data = nullptr; // Pointer to the user struct (e.g. SceneParams)