Skip to content

Feature/graphic/rhi - #67

Merged
CoraBlack merged 14 commits into
gkit-org:mainfrom
YuanSang0512:feature/graphic/RHI
Aug 3, 2026
Merged

CoraBlack merged 14 commits into
gkit-org:mainfrom
YuanSang0512:feature/graphic/RHI

Conversation

@YuanSang0512

@YuanSang0512 YuanSang0512 commented Jul 31, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Lays the foundation for a Render Hardware Interface (RHI) in the graphic module: a frontend abstraction layer (gkit::graphic) decoupled from any concrete graphics API, with OpenGL moved behind a backend device implementation. This makes adding a Vulkan (or other) backend a drop-in exercise rather than a rewrite, and prepares the renderer for a future render queue / draw-sorting pass.

What changed

1. Backend-agnostic enums (include/gkit/graphic/config.hpp)

  • Moved ClearFlags, CompareFunc, BlendFunc, BlendEquation, CullFaceMode, FrontFace, StencilOp, TextureType, and SCR_WIDTH/SCR_HEIGHT out of the OpenGL layer.
  • Enums are now decoupled from GL constant values; opengl/config.hpp became a thin mapping layer (to_gl_* converters) used only by the OpenGL backend.

2. Frontend abstract interfaces (include/gkit/graphic/)

  • New base/abstract classes: Buffer (with VertexBuffer/IndexBuffer derivations), VertexArray, Shader, Texture, FrameBuffer, RenderBuffer, VertexBufferLayout.
  • Placeholder headers for future StorageBuffer/UniformBuffer.
  • Texture is designed as a thin binding to the resource module (data ownership stays in resource); the current opengl::Texture remains a deprecated placeholder until the resource module lands.

3. Backend refactor (include/gkit/graphic/opengl/, src/graphic/opengl/)

  • OpenGL classes now inherit the frontend interfaces; GL handles stay private inside the backend.
  • Shader moved from gkit::graphic::Shader to opengl::Shader (the frontend Shader is now abstract).
  • Removed the opengl::buffer:: sub-namespace; VertexBufferLayout promoted to a frontend concept.

4. RenderDevice factory (RenderDevice.hpp, opengl/Device.hpp, create_device.cpp)

  • New RenderDevice abstract class: resource factory (create_vertex_buffer, create_index_buffer, create_shader, …) plus render entry points (clear, draw, draw_instance).
  • Backend enum + create_device(Backend) is the single backend-selection switch point.
  • opengl::Device implements the factory for the OpenGL backend.

5. Renderer integration (Renderer.hpp/cpp)

  • Renderer owns a std::unique_ptr<RenderDevice>, selected via init(Backend).
  • get_device() now lazily creates the default OpenGL device, so callers no longer risk a null dereference by calling get_device() before init().

6. Fixes & test updates

  • opengl::Texture: framebuffer textures now allocate a real GL texture with storage, fixing the black screenTexture output when rendering to an FBO.
  • test_window.cpp: replaced the textured square with a centered colored triangle (position + color attributes); new color_triangle.shader.

Commits

Commit Description
ac8cae4 refactor(graphic): move graphic enums to frontend config, decouple from GL
85db700 refactor(graphic): introduce frontend abstract interfaces, inherit backend classes
62e047e feat(graphic): add RenderDevice abstract factory and OpenGL Device backend
a3a549e refactor(graphic): wire Renderer to RenderDevice, normalize formatting
1001bfc docs(graphic): translate all comments to English
3344d6f fix(graphic): framebuffer texture, lazy device creation, colored triangle test

…om GL

- Add include/gkit/graphic/config.hpp with backend-agnostic enums
  (ClearFlags/CompareFunc/BlendFunc/BlendEquation/CullFaceMode/FrontFace/
  StencilOp/TextureType) + SCR_WIDTH/SCR_HEIGHT
- Rewrite opengl/config.hpp as GL mapping layer: to_gl_* converter funcs
- Update Renderer/StateManager/Texture/test_window to use frontend enums
- StateManager applies states via to_gl_* mapping instead of raw cast
…ckend classes

- Add frontend abstract classes in include/gkit/graphic/:
  Buffer, VertexBuffer, IndexBuffer, VertexArray, Shader, Texture,
  FrameBuffer, RenderBuffer, VertexBufferLayout, config
- opengl:: classes now inherit frontend interfaces; GL handles stay private
- Move Shader to opengl::Shader (gkit::graphic::Shader is now abstract)
- Move VertexBufferLayout to frontend; delete opengl::buffer:: namespace
- Renderer::draw takes frontend abstract types
- Update CMakeLists and test_window to new layout
…ckend

- Add frontend RenderDevice interface (resource factory + render entry)
- Add Backend enum and create_device() single switch point
- Add opengl::Device implementing RenderDevice
- create_texture left unimplemented pending resource module
- Wire new sources into CMakeLists
- Renderer holds unique_ptr<RenderDevice>, init(Backend) selects backend
- clear/draw/draw_instance delegate to device; get_device() accessor
- test_window creates resources via device factory, calls renderer.init()
- clang-tidy: rename Buffer::size_ -> size, drop default arg on virtual
  create_vertex_buffer (base class), update all references
- clang-format: normalize all graphic headers/sources to project style
- Replace Chinese comments in graphic frontend/backend headers, sources,
  and test with English equivalents
- Remove default arg on opengl::Device::create_vertex_buffer override
  (google-default-arguments)
…ngle test

- Texture: create a real GL texture with storage for TextureFramebuffer type
  so the FBO color attachment is valid (fixes black screenTexture output)
- Renderer: get_device() lazily creates the default OpenGL device, so callers
  no longer have to call init() first to avoid a null dereference; clear/draw
  delegate through it as well
- test_window: replace the textured square with a centered colored triangle
  (position + color), add color_triangle.shader; drop main_texture usage
- Convert /// @brief one-liners and '/** @brief X */' single-line blocks
  to three-line doxygen blocks across graphic headers
- Fix block indentation to match clang-format output (double-tab style),
  removing stray blank lines inside comment blocks
- StateManager.hpp: drop redundant opengl/config.hpp include; move it to
  StateManager.cpp where to_gl_* is used
- test_window.cpp: replace unused StateManager include with opengl/config.hpp
  for viewport; drop unused state_manager variable
@YuanSang0512

Copy link
Copy Markdown
Contributor Author

feature/graphic/RHI 分支注意事项

记录本分支(相对 main)RHI 重构后需要留意的事项:临时占位、使用指南、容易踩的坑。
配套设计文档见 [[RHI架构与接口设计]]。


1. 架构现状(一句话)

graphic 模块已拆成 前端抽象层(gkit::graphic)+ 后端实现层(gkit::graphic::opengl),
后端通过 RenderDevice 工厂选择。GL 细节(句柄、to_gl_* 映射)全部锁在 opengl:: 层。

gkit::graphic                前端抽象(Buffer/VertexBuffer/IndexBuffer/Shader/
  RenderDevice + Backend        VertexArray/Texture/FrameBuffer/RenderBuffer + config)
      ↑
gkit::graphic::opengl       后端实现,GL 句柄全私有
  Device / Shader / ...

2. 临时占位(重要,别当正式实现用)

位置 状态 说明 / 后续动作
opengl::Texture 构造函数 deprecated 占位 当前自行解码 + 持有 local_buffer。将来资源模块就绪后改为"只持有一个指向资源的指针"。只对 TextureFramebuffer 创建真实 GL 纹理,普通 2D 纹理加载(stb)是 stub,传真实图片路径也不会加载
RenderDevice::create_texture() 返回 nullptr 资源模块未就绪,工厂方法留空。现在不要通过 device 创建纹理;临时纹理直接构造 opengl::Texture
StorageBuffer / UniformBuffer 纯占位头,类定义被注释 未来计算着色器 / UBO 的挂载点,无实现、无工厂方法
Backend 枚举 只有 OpenGL Vulkan 在注释里标记 future,create_device 的 switch 无 Vulkan 分支
StateManager 保持旧 GL 直连 尚未改为前端 RenderState 增量应用器(渲染队列的前置改造,待做)

3. 使用指南

3.1 创建资源:走 device 工厂(新方式)

auto& renderer = gkit::graphic::Renderer::instance();
renderer.init();                       // 可省略,get_device() 会自动默认 OpenGL
auto& device   = renderer.get_device();

auto vao    = device.create_vertex_array();
auto vbo    = device.create_vertex_buffer(data, size, /*dynamic=*/false);
auto ibo    = device.create_index_buffer(indices, count);
auto shader = device.create_shader("test/graphic/xxx.shader");
auto fbo    = device.create_frame_buffer(w, h);
auto rbo    = device.create_render_buffer(w, h);

资源由 std::unique_ptr 持有,析构即释放;渲染参数传 *vao 解引用。

3.2 渲染

renderer.clear(gkit::graphic::ClearFlags::All);
shader->bind();
renderer.draw(*vao, *ibo, *shader);

3.3 纹理(临时路径)

// 临时: 帧缓冲纹理直接构造(注意是 opengl 层类型)
gkit::graphic::opengl::Texture fbo_texture(" ", gkit::graphic::TextureType::TextureFramebuffer);
fbo->attach_color_texture(fbo_texture, 0);

4. 关键 API 变化(从 main 迁移注意)

  • Shader 改名:gkit::graphic::Shader 现在是抽象基类,GL 实现移到 gkit::graphic::opengl::Shader。直接构造 Shader 的代码要改成 opengl::Shader 或走 device.create_shader()。
  • opengl::buffer:: 命名空间删除:VertexBuffer/IndexBuffer/FrameBuffer/RenderBuffer 从 opengl::buffer::X 变为 opengl::X;VertexBufferLayout 从 opengl::buffer::VertexBufferLayout 变为前端 gkit::graphic::VertexBufferLayout。
  • 枚举前移:opengl::ClearFlags 等 → gkit::graphic::ClearFlags(值已与 GL 解耦)。GL 常量转换集中到 opengl::config.hpp 的 to_gl_* 函数。
  • RenderDevice 接口:前端 create_vertex_buffer(..., bool dynamic) 无默认参数(google-default-arguments 规范),调用必须显式传 dynamic。

5. 容易踩的坑

5.1 get_device() 与 init() 的顺序

已修复:get_device() 内部懒创建默认 OpenGL device,不再要求先 init。但 init(Backend) 仍可显式选后端;先 get_device() 再 init() 会创建两次,init 会覆盖 —— 尽量先 init 或直接 get_device。

5.2 帧缓冲纹理是全黑的坑(已修)

TextureFramebuffer 构造现在会创建真实 GL 纹理 + 存储,fbo->attach_color_texture 才有效。若将来此逻辑被改动,注意 fbo 颜色附件必须是有效 GL 纹理,否则后处理采样到纹理 0 → 全黑。

5.3 include 的传递依赖已收紧

opengl/StateManager.hpp 不再 include opengl/config.hpp。用到 viewport::set_viewport / to_gl_* 的地方要显式 include gkit/graphic/opengl/config.hpp。
同理,别依赖"include 了 A 就能间接拿到 B"的传递关系。

5.4 dynamic 参数

create_vertex_buffer 的 dynamic 参数现在必须显式传(前端接口无默认值)。漏传会编译错。

5.5 后端句柄是私有

opengl::Texture::get_renderer_id() 等 escape hatch 仅后端内部用,前端代码不要依赖具体后端类型做 static_cast,除非清楚自己在 GL-only 上下文。


6. 未完成事项(future)

  • 渲染队列 / 绘制排序:StateManager → 前端 RenderState 增量应用器(apply(RenderState)),RenderCommand 自携带状态
  • StorageBuffer / UniformBuffer 后端实现 + Device 工厂方法
  • Vulkan 后端
  • Texture 接到资源模块(thin binding + 热重载失效机制)

7. 测试程序说明

test/graphic/test_window.cpp 当前演示:离屏 FBO 渲染居中彩色三角形 → 后处理 quad 采样 fbo 纹理显示。

  • 新增着色器 test/graphic/color_triangle.shader(顶点色直通)。
  • test/graphic/basic.shader(全黑)与 texture.shader(采样 u_mainTexture)仍在,未用于当前测试。

- Add move ctor/assign to opengl VertexBuffer/IndexBuffer/VertexArray/
  FrameBuffer/RenderBuffer (lost during refactor; user-defined dtors
  suppress implicit moves, making them non-movable)
- Call base-class move in Shader/Texture move ctor/assign
- Buffer + frontend VertexBuffer/IndexBuffer: explicit move with
  self-move-assignment guard (this != &other), so the whole chain from
  Buffer down to opengl backends is protected
… Device

- opengl VertexBuffer/IndexBuffer/VertexArray/FrameBuffer/RenderBuffer/Shader:
  constructors now private; opengl::Device is a friend, so resources can only
  be created through the device factory, preventing frontend code from
  bypassing the RHI abstraction
- opengl::Texture constructor stays public as a temporary exception while the
  resource module is not ready (see RHI design doc §4.4)
- Device factory uses direct new + unique_ptr instead of make_unique (friend
  does not propagate through make_unique's internal new)
- Move include/gkit/graphic/opengl/*.hpp to src/graphic/opengl/ so backend
  headers are co-located with their .cpp and no longer ship in the public
  include directory (only frontend abstractions remain in include/gkit/graphic)
- Update includes to 'graphic/opengl/X.hpp' matching the src-tree layout used
  by e.g. core/input/cache.hpp
- Move src/graphic/opengl/ to src/graphic/backend/opengl/ so multiple
  graphics API backends (opengl, future vulkan) live under one backend/
  directory instead of cluttering the graphic module root
- Update includes to graphic/backend/opengl/... and CMake source paths

@CoraBlack CoraBlack left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本次审查只审查了 公共头文件 部分,整体代码上补充以下问题:

  • 过量的虚函数设计,这完全是无意义的,在渲染这种高频调用的场景,虚函数开销是明显的,考虑使用静态分发 + 初始化初始方法表映射的方式来取代虚函数设计
  • 公共头文件中的类简单的作为后端实现的基类,无公开意义。
  • 考虑设计RAII容器管理C的数据生命周期,以及bind,unbind的调用

Comment thread src/graphic/backend/opengl/Shader.cpp
Comment thread include/gkit/graphic/Shader.hpp Outdated
Comment on lines +47 to +50
virtual auto set_uniform_vec_4f(const std::string& name, const float* vec4) -> void = 0;
virtual auto set_uniform_vec_3f(const std::string& name, const float* vec3) -> void = 0;
virtual auto set_uniform_mat_4f(const std::string& name, const float* mat4) -> void = 0;
virtual auto set_uniform_mat_3f(const std::string& name, const float* mat3) -> void = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

考虑使用 math 模块的数学类型而不是不安全的裸指针传参

Comment thread include/gkit/graphic/StorageBuffer.hpp
Comment thread include/gkit/graphic/UniformBuffer.hpp
…ointers

- Shader::set_uniform_vec_4f/vec_3f now take math::Vector4/Vector3
- Shader::set_uniform_mat_4f/mat_3f now take math::Matrix4/Matrix3
- Data extraction (mat.data(), &vec.x) happens inside the backend setter,
  so callers pass the math object directly without touching raw pointers
- Add Matrix3::data() (column-major, symmetric with Matrix4)
- Frontend Shader.hpp now includes gkit/math headers (graphic depends on math)
@YuanSang0512

YuanSang0512 commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor Author

本次审查只审查了 公共头文件 部分,整体代码上补充以下问题:

  • 过量的虚函数设计,这完全是无意义的,在渲染这种高频调用的场景,虚函数开销是明显的,考虑使用静态分发 + 初始化初始方法表映射的方式来取代虚函数设计
  • 公共头文件中的类简单的作为后端实现的基类,无公开意义。
  • 考虑设计RAII容器管理C的数据生命周期,以及bind,unbind的调用

这是仿照主流引擎的设计,渲染瓶颈在DrawCall上,虚函数的开销与DrawCall开销不是一个量级,没必要过度设计
bind和unbind本质一样,需要bind的时候会自动bind,无须手动bind和unbind(FBO和shader是特例,后续设计渲染队列时再处理)

@CoraBlack CoraBlack left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice code, nice commits

@CoraBlack
CoraBlack merged commit fd50388 into gkit-org:main Aug 3, 2026
5 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in libgkit-dev-record Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants