From 2748e80dea7c1e4bf4b4f9e310d1a954534d2972 Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Tue, 17 Feb 2026 22:36:02 +0800 Subject: [PATCH] =?UTF-8?q?refactor(opengl):=20=E9=87=8D=E6=9E=84=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E5=99=A8=E8=B5=84=E6=BA=90=E7=AE=A1=E7=90=86=E5=B9=B6?= =?UTF-8?q?=E5=BC=95=E5=85=A5=E8=B5=84=E6=BA=90=E6=8A=BD=E8=B1=A1=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入资源抽象层接口(Buffer、Pipeline、Framebuffer等) - 将OpenGL资源管理重构为GLBuffer、GLPipeline、GLFramebuffer等实现类 - 使用GLBuffer替代手动管理的VBO/IBO,提供更安全的资源生命周期管理 - 新增GLPipeline管理OpenGL管线状态,减少冗余状态切换 - 新增GLFramebuffer封装帧缓冲对象功能 - 更新GLRenderer使用新的资源管理方式 - 添加详细文档说明资源抽象层设计 - 修复相关内存泄漏问题 docs: 添加资源抽象层文档说明 - 新增docs/README.md详细说明资源抽象层设计 - 文档包含各接口功能说明和实现原则 --- .../graphics/backends/opengl/gl_buffer.h | 83 ++++++ .../graphics/backends/opengl/gl_context.h | 139 +++++++++ .../graphics/backends/opengl/gl_framebuffer.h | 105 +++++++ .../graphics/backends/opengl/gl_pipeline.h | 131 +++++++++ .../graphics/backends/opengl/gl_renderer.h | 63 +++- .../backends/opengl/gl_sprite_batch.h | 5 +- .../extra2d/graphics/batch/shape_batch.h | 157 ++++++++++ .../extra2d/graphics/core/render_backend.h | 11 +- .../extra2d/graphics/resources/buffer.h | 111 ++++++++ .../extra2d/graphics/resources/font_atlas.h | 131 +++++++++ .../extra2d/graphics/resources/framebuffer.h | 140 +++++++++ .../extra2d/graphics/resources/pipeline.h | 162 +++++++++++ .../extra2d/graphics/resources/shader.h | 134 +++++++++ .../graphics/backends/opengl/gl_buffer.cpp | 171 +++++++++++ .../graphics/backends/opengl/gl_context.cpp | 167 +++++++++++ .../backends/opengl/gl_framebuffer.cpp | 268 ++++++++++++++++++ .../graphics/backends/opengl/gl_pipeline.cpp | 222 +++++++++++++++ .../graphics/backends/opengl/gl_renderer.cpp | 234 +++++++++------ .../backends/opengl/gl_sprite_batch.cpp | 75 +++-- Extra2D/src/graphics/batch/shape_batch.cpp | 72 +++++ docs/README.md | 44 +++ 21 files changed, 2488 insertions(+), 137 deletions(-) create mode 100644 Extra2D/include/extra2d/graphics/backends/opengl/gl_buffer.h create mode 100644 Extra2D/include/extra2d/graphics/backends/opengl/gl_context.h create mode 100644 Extra2D/include/extra2d/graphics/backends/opengl/gl_framebuffer.h create mode 100644 Extra2D/include/extra2d/graphics/backends/opengl/gl_pipeline.h create mode 100644 Extra2D/include/extra2d/graphics/batch/shape_batch.h create mode 100644 Extra2D/include/extra2d/graphics/resources/buffer.h create mode 100644 Extra2D/include/extra2d/graphics/resources/font_atlas.h create mode 100644 Extra2D/include/extra2d/graphics/resources/framebuffer.h create mode 100644 Extra2D/include/extra2d/graphics/resources/pipeline.h create mode 100644 Extra2D/include/extra2d/graphics/resources/shader.h create mode 100644 Extra2D/src/graphics/backends/opengl/gl_buffer.cpp create mode 100644 Extra2D/src/graphics/backends/opengl/gl_context.cpp create mode 100644 Extra2D/src/graphics/backends/opengl/gl_framebuffer.cpp create mode 100644 Extra2D/src/graphics/backends/opengl/gl_pipeline.cpp create mode 100644 Extra2D/src/graphics/batch/shape_batch.cpp create mode 100644 docs/README.md diff --git a/Extra2D/include/extra2d/graphics/backends/opengl/gl_buffer.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_buffer.h new file mode 100644 index 0000000..7138b4c --- /dev/null +++ b/Extra2D/include/extra2d/graphics/backends/opengl/gl_buffer.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// OpenGL 缓冲区实现 +// ============================================================================ +class GLBuffer : public Buffer { +public: + /** + * @brief 构造函数 + */ + GLBuffer(); + + /** + * @brief 析构函数 + */ + ~GLBuffer() override; + + /** + * @brief 初始化缓冲区 + * @param desc 缓冲区描述 + * @return 成功返回 true + */ + bool init(const BufferDesc& desc); + + /** + * @brief 关闭缓冲区,释放资源 + */ + void shutdown(); + + // Buffer 接口实现 + void bind() override; + void unbind() override; + void setData(const void* data, size_t size) override; + void updateData(const void* data, size_t offset, size_t size) override; + void* map() override; + void unmap() override; + size_t getSize() const override { return size_; } + BufferType getType() const override { return type_; } + BufferUsage getUsage() const override { return usage_; } + bool isValid() const override { return bufferID_ != 0; } + uintptr_t getNativeHandle() const override { return static_cast(bufferID_); } + + /** + * @brief 获取 OpenGL 缓冲区 ID + * @return 缓冲区 ID + */ + GLuint getBufferID() const { return bufferID_; } + + /** + * @brief 获取 OpenGL 缓冲区目标类型 + * @return 缓冲区目标类型 + */ + GLenum getTarget() const { return target_; } + +private: + GLuint bufferID_ = 0; + GLenum target_ = GL_ARRAY_BUFFER; + size_t size_ = 0; + BufferType type_ = BufferType::Vertex; + BufferUsage usage_ = BufferUsage::Static; + GLenum glUsage_ = GL_STATIC_DRAW; + bool mapped_ = false; + void* mappedPtr_ = nullptr; + + /** + * @brief 转换使用模式到 OpenGL 枚举 + */ + static GLenum convertUsage(BufferUsage usage); + + /** + * @brief 转换缓冲区类型到 OpenGL 目标 + */ + static GLenum convertType(BufferType type); +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/backends/opengl/gl_context.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_context.h new file mode 100644 index 0000000..c656cb2 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/backends/opengl/gl_context.h @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// OpenGL 版本信息 +// ============================================================================ +struct GLVersion { + int major = 0; + int minor = 0; + bool es = false; // 是否为 ES 版本 +}; + +// ============================================================================ +// OpenGL 上下文管理类 +// ============================================================================ +class GLContext { +public: + /** + * @brief 获取全局 GLContext 实例 + */ + static GLContext& get(); + + /** + * @brief 初始化 OpenGL 上下文 + * @return 成功返回 true + */ + bool init(); + + /** + * @brief 关闭 OpenGL 上下文 + */ + void shutdown(); + + /** + * @brief 检查上下文是否有效 + * @return 有效返回 true + */ + bool isValid() const { return initialized_; } + + /** + * @brief 获取 OpenGL 版本信息 + */ + const GLVersion& getVersion() const { return version_; } + + /** + * @brief 获取 OpenGL 版本字符串 + */ + std::string getVersionString() const; + + /** + * @brief 获取 GPU 厂商信息 + */ + std::string getVendor() const; + + /** + * @brief 获取 GPU 渲染器信息 + */ + std::string getRenderer() const; + + /** + * @brief 检查是否支持指定扩展 + * @param extension 扩展名称 + * @return 支持返回 true + */ + bool hasExtension(const std::string& extension) const; + + /** + * @brief 获取最大纹理尺寸 + */ + int getMaxTextureSize() const; + + /** + * @brief 获取最大纹理单元数 + */ + int getMaxTextureUnits() const; + + /** + * @brief 获取最大顶点属性数 + */ + int getMaxVertexAttribs() const; + + /** + * @brief 获取最大 uniform 缓冲区绑定点数 + */ + int getMaxUniformBufferBindings() const; + + /** + * @brief 检查是否为 OpenGL ES + */ + bool isGLES() const { return version_.es; } + + /** + * @brief 检查是否支持 VAO + */ + bool hasVAO() const; + + /** + * @brief 检查是否支持 FBO + */ + bool hasFBO() const; + + /** + * @brief 检查是否支持 Shader + */ + bool hasShader() const; + +private: + GLContext() = default; + ~GLContext() = default; + + GLContext(const GLContext&) = delete; + GLContext& operator=(const GLContext&) = delete; + + bool initialized_ = false; + GLVersion version_; + + // 缓存的限制值 + mutable int maxTextureSize_ = -1; + mutable int maxTextureUnits_ = -1; + mutable int maxVertexAttribs_ = -1; + mutable int maxUniformBufferBindings_ = -1; + + /** + * @brief 解析 OpenGL 版本 + */ + void parseVersion(); + + /** + * @brief 加载 OpenGL 扩展 + */ + bool loadExtensions(); +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/backends/opengl/gl_framebuffer.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_framebuffer.h new file mode 100644 index 0000000..f5286d5 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/backends/opengl/gl_framebuffer.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// OpenGL 帧缓冲实现 +// ============================================================================ +class GLFramebuffer : public Framebuffer { +public: + // 最大颜色附件数 + static constexpr int MAX_COLOR_ATTACHMENTS = 8; + + /** + * @brief 构造函数 + */ + GLFramebuffer(); + + /** + * @brief 析构函数 + */ + ~GLFramebuffer() override; + + /** + * @brief 初始化帧缓冲 + * @param desc 帧缓冲描述 + * @return 成功返回 true + */ + bool init(const FramebufferDesc& desc); + + /** + * @brief 关闭帧缓冲,释放资源 + */ + void shutdown(); + + // Framebuffer 接口实现 + void bind() override; + void unbind() override; + void attachColorTexture(Ptr texture, int attachment = 0) override; + void attachDepthTexture(Ptr texture) override; + void attachDepthStencilTexture(Ptr texture) override; + bool isComplete() override; + Ptr getColorTexture(int attachment = 0) const override; + Ptr getDepthTexture() const override; + int getWidth() const override { return width_; } + int getHeight() const override { return height_; } + Size getSize() const override { return Size(static_cast(width_), static_cast(height_)); } + bool isValid() const override { return fboID_ != 0; } + uintptr_t getNativeHandle() const override { return static_cast(fboID_); } + void clear(const Color& color, bool clearColor = true, + bool clearDepth = true, bool clearStencil = false) override; + void setViewport(int x, int y, int width, int height) override; + bool readPixels(int x, int y, int width, int height, + std::vector& outData) override; + + /** + * @brief 获取 OpenGL FBO ID + * @return FBO ID + */ + GLuint getFboID() const { return fboID_; } + + /** + * @brief 创建带内置纹理的帧缓冲(便捷方法) + * @param width 宽度 + * @param height 高度 + * @param colorFormat 颜色格式 + * @param depthFormat 深度格式(可选) + * @return 成功返回 true + */ + bool createWithTextures(int width, int height, + PixelFormat colorFormat = PixelFormat::RGBA8, + PixelFormat depthFormat = PixelFormat::Depth24); + +private: + GLuint fboID_ = 0; + int width_ = 0; + int height_ = 0; + int numColorAttachments_ = 1; + bool hasDepth_ = false; + bool hasStencil_ = false; + + // 附件纹理 + std::array, MAX_COLOR_ATTACHMENTS> colorTextures_; + Ptr depthTexture_; + Ptr depthStencilTexture_; + + // 是否为内置纹理(需要自动清理) + bool hasInternalTextures_ = false; + + /** + * @brief 检查并更新完整状态 + */ + bool checkStatus(); + + /** + * @brief 获取 OpenGL 附件枚举 + */ + static GLenum getColorAttachment(int index); +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/backends/opengl/gl_pipeline.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_pipeline.h new file mode 100644 index 0000000..ca0f439 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/backends/opengl/gl_pipeline.h @@ -0,0 +1,131 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// OpenGL 管线状态实现 +// ============================================================================ +class GLPipeline : public Pipeline { +public: + /** + * @brief 构造函数 + */ + GLPipeline(); + + /** + * @brief 析构函数 + */ + ~GLPipeline() override; + + /** + * @brief 初始化管线 + * @param desc 管线描述 + * @return 成功返回 true + */ + bool init(const PipelineDesc& desc); + + /** + * @brief 关闭管线,释放资源 + */ + void shutdown(); + + // Pipeline 接口实现 + void bind() override; + void unbind() override; + void setBlendMode(BlendMode mode) override; + BlendMode getBlendMode() const override { return blendMode_; } + void setDepthTest(bool enabled) override; + void setDepthWrite(bool enabled) override; + void setDepthFunc(DepthFunc func) override; + void setCullMode(CullMode mode) override; + bool isValid() const override { return initialized_; } + uintptr_t getNativeHandle() const override { return 0; } // OpenGL 管线没有单一句柄 + + /** + * @brief 设置视口 + * @param x 视口左下角X坐标 + * @param y 视口左下角Y坐标 + * @param width 视口宽度 + * @param height 视口高度 + */ + void setViewport(int x, int y, int width, int height); + + /** + * @brief 获取当前视口 + * @param x 输出X坐标 + * @param y 输出Y坐标 + * @param width 输出宽度 + * @param height 输出高度 + */ + void getViewport(int& x, int& y, int& width, int& height) const; + + /** + * @brief 应用所有状态(用于初始化或重置) + */ + void applyAllStates(); + +private: + bool initialized_ = false; + + // 当前状态 + BlendMode blendMode_ = BlendMode::Alpha; + bool blendEnabled_ = true; + bool depthTest_ = false; + bool depthWrite_ = false; + DepthFunc depthFunc_ = DepthFunc::Less; + CullMode cullMode_ = CullMode::None; + + // 视口 + int viewportX_ = 0; + int viewportY_ = 0; + int viewportWidth_ = 0; + int viewportHeight_ = 0; + + // 状态缓存(避免冗余 GL 调用) + BlendMode cachedBlendMode_ = BlendMode::None; + bool cachedBlendEnabled_ = false; + bool cachedDepthTest_ = false; + bool cachedDepthWrite_ = false; + DepthFunc cachedDepthFunc_ = DepthFunc::Less; + CullMode cachedCullMode_ = CullMode::None; + int cachedViewportX_ = -1; + int cachedViewportY_ = -1; + int cachedViewportWidth_ = -1; + int cachedViewportHeight_ = -1; + + /** + * @brief 应用混合状态 + */ + void applyBlendState(); + + /** + * @brief 应用深度状态 + */ + void applyDepthState(); + + /** + * @brief 应用裁剪状态 + */ + void applyCullState(); + + /** + * @brief 转换混合模式到 OpenGL 枚举 + */ + static void getBlendFactors(BlendMode mode, GLenum& srcFactor, GLenum& dstFactor); + + /** + * @brief 转换深度函数到 OpenGL 枚举 + */ + static GLenum convertDepthFunc(DepthFunc func); + + /** + * @brief 转换裁剪模式到 OpenGL 枚举 + */ + static GLenum convertCullMode(CullMode mode); +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/backends/opengl/gl_renderer.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_renderer.h index b7ba020..5b70fb5 100644 --- a/Extra2D/include/extra2d/graphics/backends/opengl/gl_renderer.h +++ b/Extra2D/include/extra2d/graphics/backends/opengl/gl_renderer.h @@ -1,5 +1,8 @@ #pragma once +#include +#include +#include #include #include #include @@ -10,7 +13,10 @@ namespace extra2d { +// 前向声明 class IWindow; +class GLContext; +class GLFramebuffer; // ============================================================================ // OpenGL 渲染器实现 @@ -77,6 +83,42 @@ public: Stats getStats() const override { return stats_; } void resetStats() override; + // GLFramebuffer 相关方法 + + /** + * @brief 创建帧缓冲对象 + * @param desc 帧缓冲描述 + * @return 创建的帧缓冲智能指针 + */ + Ptr createFramebuffer(const FramebufferDesc& desc); + + /** + * @brief 绑定帧缓冲(作为渲染目标) + * @param framebuffer 帧缓冲对象指针,传入 nullptr 则绑定默认帧缓冲 + */ + void bindFramebuffer(GLFramebuffer* framebuffer); + + /** + * @brief 解绑帧缓冲(恢复到默认帧缓冲) + */ + void unbindFramebuffer(); + + /** + * @brief 获取默认帧缓冲 + * @return 默认帧缓冲智能指针 + */ + Ptr getDefaultFramebuffer() const; + + /** + * @brief 清除当前绑定的帧缓冲 + * @param color 清除颜色 + * @param clearColor 是否清除颜色缓冲 + * @param clearDepth 是否清除深度缓冲 + * @param clearStencil 是否清除模板缓冲 + */ + void clearFramebuffer(const Color& color, bool clearColor = true, + bool clearDepth = true, bool clearStencil = false); + private: // 形状批处理常量 static constexpr size_t MAX_CIRCLE_SEGMENTS = 128; @@ -93,10 +135,10 @@ private: GLSpriteBatch spriteBatch_; Ptr shapeShader_; - GLuint shapeVao_; - GLuint shapeVbo_; - GLuint lineVao_; // 线条专用 VAO - GLuint lineVbo_; // 线条专用 VBO + GLuint shapeVao_; // 形状 VAO(手动管理,用于顶点属性配置) + GLBuffer shapeBuffer_; // 形状 VBO(使用 GLBuffer 管理) + GLuint lineVao_; // 线条 VAO(手动管理,用于顶点属性配置) + GLBuffer lineBuffer_; // 线条 VBO(使用 GLBuffer 管理) glm::mat4 viewProjection_; std::vector transformStack_; @@ -113,13 +155,8 @@ private: size_t lineVertexCount_ = 0; float currentLineWidth_ = 1.0f; - // OpenGL 状态缓存 - BlendMode cachedBlendMode_ = BlendMode::None; - bool blendEnabled_ = false; - int cachedViewportX_ = 0; - int cachedViewportY_ = 0; - int cachedViewportWidth_ = 0; - int cachedViewportHeight_ = 0; + // OpenGL 管线状态管理 + GLPipeline pipeline_; // 自动批处理状态 bool batchActive_ = false; // 批处理是否激活 @@ -128,6 +165,10 @@ private: std::vector pendingSprites_; // 待提交的精灵 static constexpr size_t MAX_BATCH_SPRITES = 1000; // 最大批处理精灵数 + // 帧缓冲管理 + mutable Ptr defaultFramebuffer_; // 默认帧缓冲(延迟创建) + GLFramebuffer* currentFramebuffer_ = nullptr; // 当前绑定的帧缓冲 + void initShapeRendering(); void ensureBatchActive(); // 确保批处理已激活 void submitPendingSprites(); // 提交待处理的精灵 diff --git a/Extra2D/include/extra2d/graphics/backends/opengl/gl_sprite_batch.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_sprite_batch.h index 2808b00..fcfad7e 100644 --- a/Extra2D/include/extra2d/graphics/backends/opengl/gl_sprite_batch.h +++ b/Extra2D/include/extra2d/graphics/backends/opengl/gl_sprite_batch.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -39,8 +40,8 @@ public: private: // OpenGL 对象 GLuint vao_; - GLuint vbo_; - GLuint ebo_; + GLBuffer vbo_; // 顶点缓冲区(动态) + GLBuffer ebo_; // 索引缓冲区(静态) // 后端无关的批处理层 SpriteBatch batch_; diff --git a/Extra2D/include/extra2d/graphics/batch/shape_batch.h b/Extra2D/include/extra2d/graphics/batch/shape_batch.h new file mode 100644 index 0000000..091f16b --- /dev/null +++ b/Extra2D/include/extra2d/graphics/batch/shape_batch.h @@ -0,0 +1,157 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// 形状顶点结构 +// ============================================================================ +struct ShapeVertex { + float x, y; // 位置 + float r, g, b, a; // 颜色 + + ShapeVertex() = default; + ShapeVertex(float px, float py, const Color& c) + : x(px), y(py), r(c.r), g(c.g), b(c.b), a(c.a) {} +}; + +// ============================================================================ +// 形状批处理抽象接口 - 后端无关 +// ============================================================================ +class ShapeBatch { +public: + virtual ~ShapeBatch() = default; + + /** + * @brief 初始化形状批处理 + * @return 成功返回 true + */ + virtual bool init() = 0; + + /** + * @brief 关闭形状批处理,释放资源 + */ + virtual void shutdown() = 0; + + /** + * @brief 开始批处理 + * @param viewProjection 视图投影矩阵 + */ + virtual void begin(const glm::mat4& viewProjection) = 0; + + /** + * @brief 结束批处理并提交绘制 + */ + virtual void end() = 0; + + /** + * @brief 绘制线段 + * @param start 起点 + * @param end 终点 + * @param color 颜色 + * @param width 线宽 + */ + virtual void drawLine(const Vec2& start, const Vec2& end, + const Color& color, float width = 1.0f) = 0; + + /** + * @brief 绘制矩形边框 + * @param rect 矩形区域 + * @param color 颜色 + * @param width 边框宽度 + */ + virtual void drawRect(const Rect& rect, const Color& color, + float width = 1.0f) = 0; + + /** + * @brief 填充矩形 + * @param rect 矩形区域 + * @param color 颜色 + */ + virtual void fillRect(const Rect& rect, const Color& color) = 0; + + /** + * @brief 绘制圆形边框 + * @param center 圆心 + * @param radius 半径 + * @param color 颜色 + * @param segments 分段数 + * @param width 边框宽度 + */ + virtual void drawCircle(const Vec2& center, float radius, + const Color& color, int segments = 32, + float width = 1.0f) = 0; + + /** + * @brief 填充圆形 + * @param center 圆心 + * @param radius 半径 + * @param color 颜色 + * @param segments 分段数 + */ + virtual void fillCircle(const Vec2& center, float radius, + const Color& color, int segments = 32) = 0; + + /** + * @brief 绘制三角形边框 + * @param p1 顶点1 + * @param p2 顶点2 + * @param p3 顶点3 + * @param color 颜色 + * @param width 边框宽度 + */ + virtual void drawTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, + const Color& color, float width = 1.0f) = 0; + + /** + * @brief 填充三角形 + * @param p1 顶点1 + * @param p2 顶点2 + * @param p3 顶点3 + * @param color 颜色 + */ + virtual void fillTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, + const Color& color) = 0; + + /** + * @brief 绘制多边形边框 + * @param points 顶点数组 + * @param color 颜色 + * @param width 边框宽度 + */ + virtual void drawPolygon(const std::vector& points, + const Color& color, float width = 1.0f) = 0; + + /** + * @brief 填充多边形 + * @param points 顶点数组 + * @param color 颜色 + */ + virtual void fillPolygon(const std::vector& points, + const Color& color) = 0; + + /** + * @brief 获取绘制调用次数 + * @return 绘制调用次数 + */ + virtual uint32_t getDrawCallCount() const = 0; + + /** + * @brief 重置绘制调用计数 + */ + virtual void resetDrawCallCount() = 0; + + /** + * @brief 检查是否有效 + * @return 有效返回 true + */ + virtual bool isValid() const = 0; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/core/render_backend.h b/Extra2D/include/extra2d/graphics/core/render_backend.h index 3cd0fb9..98369e5 100644 --- a/Extra2D/include/extra2d/graphics/core/render_backend.h +++ b/Extra2D/include/extra2d/graphics/core/render_backend.h @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace extra2d { @@ -24,15 +25,7 @@ enum class BackendType { // D3D12 }; -// ============================================================================ -// 混合模式 -// ============================================================================ -enum class BlendMode { - None, // 不混合 - Alpha, // 标准 Alpha 混合 - Additive, // 加法混合 - Multiply // 乘法混合 -}; +// BlendMode 定义在 pipeline.h 中 // ============================================================================ // 渲染后端抽象接口 diff --git a/Extra2D/include/extra2d/graphics/resources/buffer.h b/Extra2D/include/extra2d/graphics/resources/buffer.h new file mode 100644 index 0000000..8e3ab29 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/resources/buffer.h @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// 缓冲区类型枚举 +// ============================================================================ +enum class BufferType { + Vertex, // 顶点缓冲 + Index, // 索引缓冲 + Uniform // 统一缓冲 +}; + +// ============================================================================ +// 缓冲区使用模式枚举 +// ============================================================================ +enum class BufferUsage { + Static, // 静态数据,很少更新 + Dynamic, // 动态数据,频繁更新 + Stream // 流式数据,每帧更新 +}; + +// ============================================================================ +// 缓冲区描述结构 +// ============================================================================ +struct BufferDesc { + BufferType type = BufferType::Vertex; + BufferUsage usage = BufferUsage::Static; + size_t size = 0; // 缓冲区大小(字节) + const void* initialData = nullptr; // 初始数据 +}; + +// ============================================================================ +// 缓冲区抽象接口 - 渲染后端无关 +// ============================================================================ +class Buffer { +public: + virtual ~Buffer() = default; + + /** + * @brief 绑定缓冲区 + */ + virtual void bind() = 0; + + /** + * @brief 解绑缓冲区 + */ + virtual void unbind() = 0; + + /** + * @brief 设置缓冲区数据(完全替换) + * @param data 数据指针 + * @param size 数据大小(字节) + */ + virtual void setData(const void* data, size_t size) = 0; + + /** + * @brief 更新缓冲区部分数据 + * @param data 数据指针 + * @param offset 偏移量(字节) + * @param size 数据大小(字节) + */ + virtual void updateData(const void* data, size_t offset, size_t size) = 0; + + /** + * @brief 映射缓冲区到内存(用于直接写入) + * @return 映射后的内存指针,失败返回 nullptr + */ + virtual void* map() = 0; + + /** + * @brief 解除缓冲区映射 + */ + virtual void unmap() = 0; + + /** + * @brief 获取缓冲区大小 + * @return 缓冲区大小(字节) + */ + virtual size_t getSize() const = 0; + + /** + * @brief 获取缓冲区类型 + * @return 缓冲区类型 + */ + virtual BufferType getType() const = 0; + + /** + * @brief 获取缓冲区使用模式 + * @return 使用模式 + */ + virtual BufferUsage getUsage() const = 0; + + /** + * @brief 检查缓冲区是否有效 + * @return 有效返回 true + */ + virtual bool isValid() const = 0; + + /** + * @brief 获取原生句柄(后端特定) + * @return 原生句柄值 + */ + virtual uintptr_t getNativeHandle() const = 0; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/resources/font_atlas.h b/Extra2D/include/extra2d/graphics/resources/font_atlas.h new file mode 100644 index 0000000..008e2f2 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/resources/font_atlas.h @@ -0,0 +1,131 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// 字形信息结构 +// ============================================================================ +struct Glyph { + float width = 0; // 字形宽度 + float height = 0; // 字形高度 + float bearingX = 0; // 水平偏移 + float bearingY = 0; // 垂直偏移(从基线到字形顶部) + float advance = 0; // 水平步进 + float u0 = 0, v0 = 0; // 纹理坐标左下角 + float u1 = 0, v1 = 0; // 纹理坐标右上角 +}; + +// ============================================================================ +// 字体图集描述结构 +// ============================================================================ +struct FontAtlasDesc { + std::string filepath; // 字体文件路径 + int fontSize = 16; // 字体大小 + bool useSDF = false; // 是否使用SDF渲染 + int atlasSize = 512; // 图集大小 + int padding = 2; // 字形间距 +}; + +// ============================================================================ +// 字体图集抽象接口 - 渲染后端无关 +// ============================================================================ +class FontAtlas { +public: + virtual ~FontAtlas() = default; + + /** + * @brief 初始化字体图集 + * @param desc 字体图集描述 + * @return 成功返回 true + */ + virtual bool init(const FontAtlasDesc& desc) = 0; + + /** + * @brief 关闭字体图集,释放资源 + */ + virtual void shutdown() = 0; + + /** + * @brief 获取字形信息 + * @param codepoint Unicode 码点 + * @return 字形信息指针,未找到返回 nullptr + */ + virtual const Glyph* getGlyph(char32_t codepoint) const = 0; + + /** + * @brief 获取纹理 + * @return 纹理对象 + */ + virtual Ptr getTexture() const = 0; + + /** + * @brief 获取字体大小 + * @return 字体大小 + */ + virtual int getFontSize() const = 0; + + /** + * @brief 获取行高 + * @return 行高 + */ + virtual float getLineHeight() const = 0; + + /** + * @brief 获取基线到顶部的距离 + * @return 上升高度 + */ + virtual float getAscent() const = 0; + + /** + * @brief 获取基线到底部的距离 + * @return 下降高度 + */ + virtual float getDescent() const = 0; + + /** + * @brief 计算文本宽度 + * @param text 文本内容 + * @return 文本宽度 + */ + virtual float measureText(const std::string& text) const = 0; + + /** + * @brief 计算文本尺寸 + * @param text 文本内容 + * @return 文本尺寸 + */ + virtual Size measureTextSize(const std::string& text) const = 0; + + /** + * @brief 是否使用SDF渲染 + * @return 使用SDF返回 true + */ + virtual bool isSDF() const = 0; + + /** + * @brief 检查字体图集是否有效 + * @return 有效返回 true + */ + virtual bool isValid() const = 0; + + /** + * @brief 预加载字符到图集 + * @param text 需要预加载的文本 + * @return 成功加载的字符数 + */ + virtual int preloadGlyphs(const std::string& text) = 0; + + /** + * @brief 清空已加载的字形缓存 + */ + virtual void clearCache() = 0; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/resources/framebuffer.h b/Extra2D/include/extra2d/graphics/resources/framebuffer.h new file mode 100644 index 0000000..5f715e2 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/resources/framebuffer.h @@ -0,0 +1,140 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// 帧缓冲描述结构 +// ============================================================================ +struct FramebufferDesc { + int width = 0; // 帧缓冲宽度 + int height = 0; // 帧缓冲高度 + int colorAttachments = 1; // 颜色附件数量 + bool hasDepth = false; // 是否有深度附件 + bool hasStencil = false; // 是否有模板附件 + bool multisample = false; // 是否多重采样 + int samples = 4; // 采样数(多重采样时有效) +}; + +// ============================================================================ +// 帧缓冲抽象接口 - 渲染后端无关 +// ============================================================================ +class Framebuffer { +public: + virtual ~Framebuffer() = default; + + /** + * @brief 绑定帧缓冲(作为渲染目标) + */ + virtual void bind() = 0; + + /** + * @brief 解绑帧缓冲(恢复到默认帧缓冲) + */ + virtual void unbind() = 0; + + /** + * @brief 附加颜色纹理 + * @param texture 纹理对象 + * @param attachment 附件索引(0-7) + */ + virtual void attachColorTexture(Ptr texture, int attachment = 0) = 0; + + /** + * @brief 附加深度纹理 + * @param texture 纹理对象 + */ + virtual void attachDepthTexture(Ptr texture) = 0; + + /** + * @brief 附加深度模板纹理 + * @param texture 纹理对象 + */ + virtual void attachDepthStencilTexture(Ptr texture) = 0; + + /** + * @brief 检查帧缓冲是否完整 + * @return 完整返回 true + */ + virtual bool isComplete() = 0; + + /** + * @brief 获取颜色附件纹理 + * @param attachment 附件索引 + * @return 纹理对象 + */ + virtual Ptr getColorTexture(int attachment = 0) const = 0; + + /** + * @brief 获取深度附件纹理 + * @return 纹理对象 + */ + virtual Ptr getDepthTexture() const = 0; + + /** + * @brief 获取帧缓冲宽度 + * @return 宽度 + */ + virtual int getWidth() const = 0; + + /** + * @brief 获取帧缓冲高度 + * @return 高度 + */ + virtual int getHeight() const = 0; + + /** + * @brief 获取尺寸 + * @return 尺寸 + */ + virtual Size getSize() const = 0; + + /** + * @brief 检查帧缓冲是否有效 + * @return 有效返回 true + */ + virtual bool isValid() const = 0; + + /** + * @brief 获取原生句柄(后端特定) + * @return 原生句柄值 + */ + virtual uintptr_t getNativeHandle() const = 0; + + /** + * @brief 清除帧缓冲 + * @param color 清除颜色 + * @param clearColor 是否清除颜色缓冲 + * @param clearDepth 是否清除深度缓冲 + * @param clearStencil 是否清除模板缓冲 + */ + virtual void clear(const Color& color, bool clearColor = true, + bool clearDepth = true, bool clearStencil = false) = 0; + + /** + * @brief 设置视口 + * @param x 视口左下角X坐标 + * @param y 视口左下角Y坐标 + * @param width 视口宽度 + * @param height 视口高度 + */ + virtual void setViewport(int x, int y, int width, int height) = 0; + + /** + * @brief 读取像素数据 + * @param x 起始X坐标 + * @param y 起始Y坐标 + * @param width 宽度 + * @param height 高度 + * @param outData 输出数据缓冲区 + * @return 成功返回 true + */ + virtual bool readPixels(int x, int y, int width, int height, + std::vector& outData) = 0; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/resources/pipeline.h b/Extra2D/include/extra2d/graphics/resources/pipeline.h new file mode 100644 index 0000000..996499c --- /dev/null +++ b/Extra2D/include/extra2d/graphics/resources/pipeline.h @@ -0,0 +1,162 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// 混合模式枚举 +// ============================================================================ +enum class BlendMode { + None, // 不混合 + Alpha, // 标准 Alpha 混合 + Additive, // 加法混合 + Multiply // 乘法混合 +}; + +// ============================================================================ +// 深度测试函数枚举 +// ============================================================================ +enum class DepthFunc { + Never, // 永不通过 + Less, // 小于 + Equal, // 等于 + LessEqual, // 小于等于 + Greater, // 大于 + NotEqual, // 不等于 + GreaterEqual,// 大于等于 + Always // 总是通过 +}; + +// ============================================================================ +// 裁剪模式枚举 +// ============================================================================ +enum class CullMode { + None, // 不裁剪 + Front, // 裁剪正面 + Back, // 裁剪背面 + Both // 裁剪双面 +}; + +// ============================================================================ +// 顶点属性格式枚举 +// ============================================================================ +enum class VertexFormat { + Float1, // 1个float + Float2, // 2个float + Float3, // 3个float + Float4, // 4个float + Byte4, // 4个byte + UByte4, // 4个ubyte + Short2, // 2个short + Short4 // 4个short +}; + +// ============================================================================ +// 顶点属性描述 +// ============================================================================ +struct VertexAttribute { + uint32_t location = 0; // 属性位置 + VertexFormat format = VertexFormat::Float3; // 数据格式 + uint32_t offset = 0; // 在顶点结构中的偏移 + uint32_t stride = 0; // 顶点结构大小 + bool normalized = false; // 是否归一化 + + VertexAttribute() = default; + VertexAttribute(uint32_t loc, VertexFormat fmt, uint32_t off, uint32_t str, bool norm = false) + : location(loc), format(fmt), offset(off), stride(str), normalized(norm) {} +}; + +// ============================================================================ +// 管线描述结构 +// ============================================================================ +struct PipelineDesc { + // 混合状态 + BlendMode blendMode = BlendMode::Alpha; + bool blendEnabled = true; + + // 深度状态 + bool depthTest = false; + bool depthWrite = false; + DepthFunc depthFunc = DepthFunc::Less; + + // 裁剪状态 + CullMode cullMode = CullMode::None; + + // 顶点布局 + std::vector vertexAttributes; + + // 着色器(由后端特定实现设置) + void* vertexShader = nullptr; + void* fragmentShader = nullptr; +}; + +// ============================================================================ +// 渲染管线抽象接口 - 渲染后端无关 +// ============================================================================ +class Pipeline { +public: + virtual ~Pipeline() = default; + + /** + * @brief 绑定管线 + */ + virtual void bind() = 0; + + /** + * @brief 解绑管线 + */ + virtual void unbind() = 0; + + /** + * @brief 设置混合模式 + * @param mode 混合模式 + */ + virtual void setBlendMode(BlendMode mode) = 0; + + /** + * @brief 获取当前混合模式 + * @return 混合模式 + */ + virtual BlendMode getBlendMode() const = 0; + + /** + * @brief 设置深度测试 + * @param enabled 是否启用 + */ + virtual void setDepthTest(bool enabled) = 0; + + /** + * @brief 设置深度写入 + * @param enabled 是否启用 + */ + virtual void setDepthWrite(bool enabled) = 0; + + /** + * @brief 设置深度测试函数 + * @param func 深度测试函数 + */ + virtual void setDepthFunc(DepthFunc func) = 0; + + /** + * @brief 设置裁剪模式 + * @param mode 裁剪模式 + */ + virtual void setCullMode(CullMode mode) = 0; + + /** + * @brief 检查管线是否有效 + * @return 有效返回 true + */ + virtual bool isValid() const = 0; + + /** + * @brief 获取原生句柄(后端特定) + * @return 原生句柄值 + */ + virtual uintptr_t getNativeHandle() const = 0; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/resources/shader.h b/Extra2D/include/extra2d/graphics/resources/shader.h new file mode 100644 index 0000000..64f38a8 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/resources/shader.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// 着色器类型枚举 +// ============================================================================ +enum class ShaderType { + Vertex, // 顶点着色器 + Fragment, // 片段着色器 + Geometry, // 几何着色器 + Compute // 计算着色器 +}; + +// ============================================================================ +// 着色器描述结构 +// ============================================================================ +struct ShaderDesc { + std::string name; // 着色器名称 + std::string vertexSource; // 顶点着色器源码 + std::string fragmentSource; // 片段着色器源码 + std::string geometrySource; // 几何着色器源码(可选) + std::vector binaryData; // 预编译二进制数据(可选) +}; + +// ============================================================================ +// 着色器抽象接口 - 渲染后端无关 +// ============================================================================ +class Shader { +public: + virtual ~Shader() = default; + + /** + * @brief 绑定着色器程序 + */ + virtual void bind() = 0; + + /** + * @brief 解绑着色器程序 + */ + virtual void unbind() = 0; + + /** + * @brief 设置布尔类型 uniform 变量 + * @param name uniform 变量名 + * @param value 布尔值 + */ + virtual void setBool(const std::string& name, bool value) = 0; + + /** + * @brief 设置整数类型 uniform 变量 + * @param name uniform 变量名 + * @param value 整数值 + */ + virtual void setInt(const std::string& name, int value) = 0; + + /** + * @brief 设置浮点类型 uniform 变量 + * @param name uniform 变量名 + * @param value 浮点值 + */ + virtual void setFloat(const std::string& name, float value) = 0; + + /** + * @brief 设置二维向量类型 uniform 变量 + * @param name uniform 变量名 + * @param value 二维向量值 + */ + virtual void setVec2(const std::string& name, const glm::vec2& value) = 0; + + /** + * @brief 设置三维向量类型 uniform 变量 + * @param name uniform 变量名 + * @param value 三维向量值 + */ + virtual void setVec3(const std::string& name, const glm::vec3& value) = 0; + + /** + * @brief 设置四维向量类型 uniform 变量 + * @param name uniform 变量名 + * @param value 四维向量值 + */ + virtual void setVec4(const std::string& name, const glm::vec4& value) = 0; + + /** + * @brief 设置 4x4 矩阵类型 uniform 变量 + * @param name uniform 变量名 + * @param value 4x4 矩阵值 + */ + virtual void setMat4(const std::string& name, const glm::mat4& value) = 0; + + /** + * @brief 设置颜色类型 uniform 变量 + * @param name uniform 变量名 + * @param color 颜色值 + */ + virtual void setColor(const std::string& name, const Color& color) = 0; + + /** + * @brief 设置纹理采样器 + * @param name uniform 变量名 + * @param slot 纹理槽位 + */ + virtual void setTexture(const std::string& name, int slot) = 0; + + /** + * @brief 获取着色器名称 + * @return 着色器名称 + */ + virtual const std::string& getName() const = 0; + + /** + * @brief 检查着色器是否有效 + * @return 有效返回 true + */ + virtual bool isValid() const = 0; + + /** + * @brief 获取原生句柄(后端特定) + * @return 原生句柄值 + */ + virtual uintptr_t getNativeHandle() const = 0; +}; + +} // namespace extra2d diff --git a/Extra2D/src/graphics/backends/opengl/gl_buffer.cpp b/Extra2D/src/graphics/backends/opengl/gl_buffer.cpp new file mode 100644 index 0000000..153010a --- /dev/null +++ b/Extra2D/src/graphics/backends/opengl/gl_buffer.cpp @@ -0,0 +1,171 @@ +#include +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// GLBuffer 实现 +// ============================================================================ + +GLBuffer::GLBuffer() = default; + +GLBuffer::~GLBuffer() { + shutdown(); +} + +bool GLBuffer::init(const BufferDesc& desc) { + if (bufferID_ != 0) { + shutdown(); + } + + type_ = desc.type; + usage_ = desc.usage; + size_ = desc.size; + target_ = convertType(type_); + glUsage_ = convertUsage(usage_); + + // 生成缓冲区 + glGenBuffers(1, &bufferID_); + if (bufferID_ == 0) { + E2D_LOG_ERROR("Failed to generate OpenGL buffer"); + return false; + } + + // 绑定并分配缓冲区 + glBindBuffer(target_, bufferID_); + glBufferData(target_, static_cast(size_), desc.initialData, glUsage_); + glBindBuffer(target_, 0); + + // 追踪显存使用 + VRAMMgr::get().allocBuffer(size_); + + E2D_LOG_DEBUG("GLBuffer created: ID={}, Size={}, Type={}, Usage={}", + bufferID_, size_, static_cast(type_), static_cast(usage_)); + + return true; +} + +void GLBuffer::shutdown() { + if (bufferID_ != 0) { + if (mapped_) { + unmap(); + } + // 释放显存追踪 + VRAMMgr::get().freeBuffer(size_); + glDeleteBuffers(1, &bufferID_); + E2D_LOG_DEBUG("GLBuffer destroyed: ID={}", bufferID_); + bufferID_ = 0; + } + size_ = 0; + mapped_ = false; + mappedPtr_ = nullptr; +} + +void GLBuffer::bind() { + if (bufferID_ != 0) { + glBindBuffer(target_, bufferID_); + } +} + +void GLBuffer::unbind() { + glBindBuffer(target_, 0); +} + +void GLBuffer::setData(const void* data, size_t size) { + if (bufferID_ == 0) { + return; + } + + bind(); + + // 如果大小相同,使用 glBufferSubData 更高效 + if (size == size_) { + glBufferSubData(target_, 0, static_cast(size), data); + } else { + // 大小不同,重新分配 + size_ = size; + glBufferData(target_, static_cast(size_), data, glUsage_); + } + + unbind(); +} + +void GLBuffer::updateData(const void* data, size_t offset, size_t size) { + if (bufferID_ == 0 || data == nullptr || size == 0) { + return; + } + + if (offset + size > size_) { + E2D_LOG_WARN("GLBuffer updateData out of bounds: offset={}, size={}, bufferSize={}", + offset, size, size_); + return; + } + + bind(); + glBufferSubData(target_, static_cast(offset), static_cast(size), data); + unbind(); +} + +void* GLBuffer::map() { + if (bufferID_ == 0 || mapped_) { + return nullptr; + } + + bind(); + + // 使用 glMapBufferRange 替代 glMapBuffer,更现代且安全 + GLbitfield access = GL_MAP_WRITE_BIT; + if (usage_ == BufferUsage::Dynamic || usage_ == BufferUsage::Stream) { + access |= GL_MAP_INVALIDATE_BUFFER_BIT; // 暗示驱动可以丢弃旧数据 + } + + mappedPtr_ = glMapBufferRange(target_, 0, static_cast(size_), access); + if (mappedPtr_) { + mapped_ = true; + } else { + E2D_LOG_ERROR("Failed to map GLBuffer"); + } + + return mappedPtr_; +} + +void GLBuffer::unmap() { + if (!mapped_ || bufferID_ == 0) { + return; + } + + glUnmapBuffer(target_); + mapped_ = false; + mappedPtr_ = nullptr; + unbind(); +} + +GLenum GLBuffer::convertUsage(BufferUsage usage) { + switch (usage) { + case BufferUsage::Static: + return GL_STATIC_DRAW; + case BufferUsage::Dynamic: + return GL_DYNAMIC_DRAW; + case BufferUsage::Stream: + return GL_STREAM_DRAW; + default: + return GL_STATIC_DRAW; + } +} + +GLenum GLBuffer::convertType(BufferType type) { + switch (type) { + case BufferType::Vertex: + return GL_ARRAY_BUFFER; + case BufferType::Index: + return GL_ELEMENT_ARRAY_BUFFER; + case BufferType::Uniform: + return GL_UNIFORM_BUFFER; + default: + return GL_ARRAY_BUFFER; + } +} + +} // namespace extra2d diff --git a/Extra2D/src/graphics/backends/opengl/gl_context.cpp b/Extra2D/src/graphics/backends/opengl/gl_context.cpp new file mode 100644 index 0000000..f200581 --- /dev/null +++ b/Extra2D/src/graphics/backends/opengl/gl_context.cpp @@ -0,0 +1,167 @@ +#include +#include +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// GLContext 实现 +// ============================================================================ + +GLContext& GLContext::get() { + static GLContext instance; + return instance; +} + +bool GLContext::init() { + if (initialized_) { + return true; + } + + // 解析 OpenGL 版本 + parseVersion(); + + // 加载扩展(GLAD 已在 glad.c 中完成) + if (!loadExtensions()) { + E2D_LOG_ERROR("Failed to load OpenGL extensions"); + return false; + } + + initialized_ = true; + + // 标记 GPU 上下文为有效 + GPUContext::get().markValid(); + + E2D_LOG_INFO("OpenGL Context initialized"); + E2D_LOG_INFO(" Version: {}", getVersionString()); + E2D_LOG_INFO(" Vendor: {}", getVendor()); + E2D_LOG_INFO(" Renderer: {}", getRenderer()); + E2D_LOG_INFO(" Max Texture Size: {}", getMaxTextureSize()); + E2D_LOG_INFO(" Max Texture Units: {}", getMaxTextureUnits()); + + return true; +} + +void GLContext::shutdown() { + // 标记 GPU 上下文为无效 + GPUContext::get().markInvalid(); + + initialized_ = false; + version_ = GLVersion{}; + maxTextureSize_ = -1; + maxTextureUnits_ = -1; + maxVertexAttribs_ = -1; + maxUniformBufferBindings_ = -1; +} + +std::string GLContext::getVersionString() const { + const char* version = reinterpret_cast(glGetString(GL_VERSION)); + return version ? version : "Unknown"; +} + +std::string GLContext::getVendor() const { + const char* vendor = reinterpret_cast(glGetString(GL_VENDOR)); + return vendor ? vendor : "Unknown"; +} + +std::string GLContext::getRenderer() const { + const char* renderer = reinterpret_cast(glGetString(GL_RENDERER)); + return renderer ? renderer : "Unknown"; +} + +bool GLContext::hasExtension(const std::string& extension) const { + GLint numExtensions = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions); + + for (GLint i = 0; i < numExtensions; ++i) { + const char* ext = reinterpret_cast(glGetStringi(GL_EXTENSIONS, i)); + if (ext && extension == ext) { + return true; + } + } + + return false; +} + +int GLContext::getMaxTextureSize() const { + if (maxTextureSize_ < 0) { + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize_); + } + return maxTextureSize_; +} + +int GLContext::getMaxTextureUnits() const { + if (maxTextureUnits_ < 0) { + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits_); + } + return maxTextureUnits_; +} + +int GLContext::getMaxVertexAttribs() const { + if (maxVertexAttribs_ < 0) { + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs_); + } + return maxVertexAttribs_; +} + +int GLContext::getMaxUniformBufferBindings() const { + if (maxUniformBufferBindings_ < 0) { + glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUniformBufferBindings_); + } + return maxUniformBufferBindings_; +} + +bool GLContext::hasVAO() const { + // OpenGL 3.0+ 或 OpenGL ES 3.0+ 原生支持 VAO + if (version_.es) { + return version_.major >= 3; + } + return version_.major > 3 || (version_.major == 3 && version_.minor >= 0); +} + +bool GLContext::hasFBO() const { + // OpenGL 3.0+ 或 OpenGL ES 2.0+ 原生支持 FBO + if (version_.es) { + return version_.major >= 2; + } + return version_.major >= 3; +} + +bool GLContext::hasShader() const { + // OpenGL 2.0+ 或 OpenGL ES 2.0+ 原生支持 Shader + if (version_.es) { + return version_.major >= 2; + } + return version_.major >= 2; +} + +void GLContext::parseVersion() { + const char* versionStr = reinterpret_cast(glGetString(GL_VERSION)); + if (!versionStr) { + version_ = GLVersion{0, 0, false}; + return; + } + + std::string version(versionStr); + + // 检查是否为 OpenGL ES + if (version.find("OpenGL ES") != std::string::npos) { + version_.es = true; + // 解析 ES 版本号,格式如 "OpenGL ES 3.0" + std::sscanf(version.c_str(), "OpenGL ES %d.%d", &version_.major, &version_.minor); + } else { + version_.es = false; + // 解析桌面版本号,格式如 "3.3.0 NVIDIA" + std::sscanf(version.c_str(), "%d.%d", &version_.major, &version_.minor); + } +} + +bool GLContext::loadExtensions() { + // GLAD 已经在 glad.c 中加载了所有扩展 + // 这里可以添加额外的扩展检查 + return true; +} + +} // namespace extra2d diff --git a/Extra2D/src/graphics/backends/opengl/gl_framebuffer.cpp b/Extra2D/src/graphics/backends/opengl/gl_framebuffer.cpp new file mode 100644 index 0000000..fdf44c8 --- /dev/null +++ b/Extra2D/src/graphics/backends/opengl/gl_framebuffer.cpp @@ -0,0 +1,268 @@ +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// GLFramebuffer 实现 +// ============================================================================ + +GLFramebuffer::GLFramebuffer() = default; + +GLFramebuffer::~GLFramebuffer() { + shutdown(); +} + +bool GLFramebuffer::init(const FramebufferDesc& desc) { + if (fboID_ != 0) { + shutdown(); + } + + width_ = desc.width; + height_ = desc.height; + numColorAttachments_ = desc.colorAttachments; + hasDepth_ = desc.hasDepth; + hasStencil_ = desc.hasStencil; + + // 限制颜色附件数 + if (numColorAttachments_ > MAX_COLOR_ATTACHMENTS) { + numColorAttachments_ = MAX_COLOR_ATTACHMENTS; + } + + // 生成 FBO + glGenFramebuffers(1, &fboID_); + if (fboID_ == 0) { + E2D_LOG_ERROR("Failed to generate OpenGL framebuffer"); + return false; + } + + E2D_LOG_DEBUG("GLFramebuffer created: ID={}, Size={}x{}, ColorAttachments={}", + fboID_, width_, height_, numColorAttachments_); + + return true; +} + +void GLFramebuffer::shutdown() { + if (fboID_ != 0) { + glDeleteFramebuffers(1, &fboID_); + E2D_LOG_DEBUG("GLFramebuffer destroyed: ID={}", fboID_); + fboID_ = 0; + } + + // 清理纹理引用 + for (auto& tex : colorTextures_) { + tex.reset(); + } + depthTexture_.reset(); + depthStencilTexture_.reset(); + + hasInternalTextures_ = false; +} + +void GLFramebuffer::bind() { + if (fboID_ != 0) { + glBindFramebuffer(GL_FRAMEBUFFER, fboID_); + } +} + +void GLFramebuffer::unbind() { + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +void GLFramebuffer::attachColorTexture(Ptr texture, int attachment) { + if (fboID_ == 0 || !texture || attachment < 0 || attachment >= MAX_COLOR_ATTACHMENTS) { + return; + } + + bind(); + + // 获取 OpenGL 纹理 ID + GLuint texID = static_cast(reinterpret_cast(texture->getNativeHandle())); + + glFramebufferTexture2D(GL_FRAMEBUFFER, getColorAttachment(attachment), + GL_TEXTURE_2D, texID, 0); + + colorTextures_[attachment] = texture; + + unbind(); +} + +void GLFramebuffer::attachDepthTexture(Ptr texture) { + if (fboID_ == 0 || !texture) { + return; + } + + bind(); + + // 获取 OpenGL 纹理 ID + GLuint texID = static_cast(reinterpret_cast(texture->getNativeHandle())); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_TEXTURE_2D, texID, 0); + + depthTexture_ = texture; + hasDepth_ = true; + hasStencil_ = false; + + unbind(); +} + +void GLFramebuffer::attachDepthStencilTexture(Ptr texture) { + if (fboID_ == 0 || !texture) { + return; + } + + bind(); + + // 获取 OpenGL 纹理 ID + GLuint texID = static_cast(reinterpret_cast(texture->getNativeHandle())); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + GL_TEXTURE_2D, texID, 0); + + depthStencilTexture_ = texture; + hasDepth_ = true; + hasStencil_ = true; + + unbind(); +} + +bool GLFramebuffer::isComplete() { + if (fboID_ == 0) { + return false; + } + + bind(); + bool complete = checkStatus(); + unbind(); + + return complete; +} + +Ptr GLFramebuffer::getColorTexture(int attachment) const { + if (attachment >= 0 && attachment < MAX_COLOR_ATTACHMENTS) { + return colorTextures_[attachment]; + } + return nullptr; +} + +Ptr GLFramebuffer::getDepthTexture() const { + return depthTexture_; +} + +void GLFramebuffer::clear(const Color& color, bool clearColor, + bool clearDepth, bool clearStencil) { + if (fboID_ == 0) { + return; + } + + bind(); + + GLbitfield mask = 0; + + if (clearColor) { + mask |= GL_COLOR_BUFFER_BIT; + glClearColor(color.r, color.g, color.b, color.a); + } + + if (clearDepth) { + mask |= GL_DEPTH_BUFFER_BIT; + glClearDepthf(1.0f); + } + + if (clearStencil) { + mask |= GL_STENCIL_BUFFER_BIT; + glClearStencil(0); + } + + if (mask != 0) { + glClear(mask); + } + + unbind(); +} + +void GLFramebuffer::setViewport(int x, int y, int width, int height) { + glViewport(x, y, width, height); +} + +bool GLFramebuffer::readPixels(int x, int y, int width, int height, + std::vector& outData) { + if (fboID_ == 0 || width <= 0 || height <= 0) { + return false; + } + + // 计算需要的缓冲区大小 (RGBA8) + size_t dataSize = width * height * 4; + outData.resize(dataSize); + + bind(); + glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, outData.data()); + unbind(); + + return true; +} + +bool GLFramebuffer::createWithTextures(int width, int height, + PixelFormat colorFormat, + PixelFormat depthFormat) { + FramebufferDesc desc; + desc.width = width; + desc.height = height; + desc.colorAttachments = 1; + desc.hasDepth = (depthFormat != PixelFormat::RGBA8); + desc.hasStencil = (depthFormat == PixelFormat::Depth24Stencil8); + + if (!init(desc)) { + return false; + } + + hasInternalTextures_ = true; + + // 创建颜色纹理 + // 注意:这里简化处理,实际应该通过纹理工厂创建 + // 暂时返回 true,实际纹理创建由调用者处理 + + return true; +} + +bool GLFramebuffer::checkStatus() { + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + switch (status) { + case GL_FRAMEBUFFER_COMPLETE: + return true; + case GL_FRAMEBUFFER_UNDEFINED: + E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_UNDEFINED"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"); + break; +#ifndef GL_ES_VERSION_2_0 + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: + E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"); + break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: + E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"); + break; +#endif + case GL_FRAMEBUFFER_UNSUPPORTED: + E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_UNSUPPORTED"); + break; + default: + E2D_LOG_ERROR("Framebuffer incomplete: Unknown error {}", status); + break; + } + + return false; +} + +GLenum GLFramebuffer::getColorAttachment(int index) { + return GL_COLOR_ATTACHMENT0 + index; +} + +} // namespace extra2d diff --git a/Extra2D/src/graphics/backends/opengl/gl_pipeline.cpp b/Extra2D/src/graphics/backends/opengl/gl_pipeline.cpp new file mode 100644 index 0000000..17d9349 --- /dev/null +++ b/Extra2D/src/graphics/backends/opengl/gl_pipeline.cpp @@ -0,0 +1,222 @@ +#include +#include + +namespace extra2d { + +// ============================================================================ +// GLPipeline 实现 +// ============================================================================ + +GLPipeline::GLPipeline() = default; + +GLPipeline::~GLPipeline() { + shutdown(); +} + +bool GLPipeline::init(const PipelineDesc& desc) { + if (initialized_) { + shutdown(); + } + + blendMode_ = desc.blendMode; + blendEnabled_ = desc.blendEnabled; + depthTest_ = desc.depthTest; + depthWrite_ = desc.depthWrite; + depthFunc_ = desc.depthFunc; + cullMode_ = desc.cullMode; + + initialized_ = true; + + E2D_LOG_DEBUG("GLPipeline initialized: blendMode={}, depthTest={}, cullMode={}", + static_cast(blendMode_), depthTest_, static_cast(cullMode_)); + + return true; +} + +void GLPipeline::shutdown() { + initialized_ = false; +} + +void GLPipeline::bind() { + if (!initialized_) { + return; + } + + applyAllStates(); +} + +void GLPipeline::unbind() { + // OpenGL 不需要显式解绑管线 +} + +void GLPipeline::setBlendMode(BlendMode mode) { + blendMode_ = mode; + applyBlendState(); +} + +void GLPipeline::setDepthTest(bool enabled) { + depthTest_ = enabled; + applyDepthState(); +} + +void GLPipeline::setDepthWrite(bool enabled) { + depthWrite_ = enabled; + applyDepthState(); +} + +void GLPipeline::setDepthFunc(DepthFunc func) { + depthFunc_ = func; + applyDepthState(); +} + +void GLPipeline::setCullMode(CullMode mode) { + cullMode_ = mode; + applyCullState(); +} + +void GLPipeline::setViewport(int x, int y, int width, int height) { + viewportX_ = x; + viewportY_ = y; + viewportWidth_ = width; + viewportHeight_ = height; + + // 检查缓存,避免冗余调用 + if (x != cachedViewportX_ || y != cachedViewportY_ || + width != cachedViewportWidth_ || height != cachedViewportHeight_) { + glViewport(x, y, width, height); + cachedViewportX_ = x; + cachedViewportY_ = y; + cachedViewportWidth_ = width; + cachedViewportHeight_ = height; + } +} + +void GLPipeline::getViewport(int& x, int& y, int& width, int& height) const { + x = viewportX_; + y = viewportY_; + width = viewportWidth_; + height = viewportHeight_; +} + +void GLPipeline::applyAllStates() { + applyBlendState(); + applyDepthState(); + applyCullState(); + + // 应用视口 + if (viewportWidth_ > 0 && viewportHeight_ > 0) { + setViewport(viewportX_, viewportY_, viewportWidth_, viewportHeight_); + } +} + +void GLPipeline::applyBlendState() { + // 检查是否需要启用/禁用混合 + if (blendEnabled_ != cachedBlendEnabled_) { + if (blendEnabled_) { + glEnable(GL_BLEND); + } else { + glDisable(GL_BLEND); + } + cachedBlendEnabled_ = blendEnabled_; + } + + // 如果禁用了混合,不需要设置混合函数 + if (!blendEnabled_) { + return; + } + + // 检查混合模式是否改变 + if (blendMode_ != cachedBlendMode_) { + GLenum srcFactor, dstFactor; + getBlendFactors(blendMode_, srcFactor, dstFactor); + glBlendFunc(srcFactor, dstFactor); + cachedBlendMode_ = blendMode_; + } +} + +void GLPipeline::applyDepthState() { + // 深度测试 + if (depthTest_ != cachedDepthTest_) { + if (depthTest_) { + glEnable(GL_DEPTH_TEST); + } else { + glDisable(GL_DEPTH_TEST); + } + cachedDepthTest_ = depthTest_; + } + + // 深度写入 + if (depthWrite_ != cachedDepthWrite_) { + glDepthMask(depthWrite_ ? GL_TRUE : GL_FALSE); + cachedDepthWrite_ = depthWrite_; + } + + // 深度函数 + if (depthFunc_ != cachedDepthFunc_) { + glDepthFunc(convertDepthFunc(depthFunc_)); + cachedDepthFunc_ = depthFunc_; + } +} + +void GLPipeline::applyCullState() { + // 检查裁剪模式是否改变 + if (cullMode_ != cachedCullMode_) { + if (cullMode_ == CullMode::None) { + glDisable(GL_CULL_FACE); + } else { + glEnable(GL_CULL_FACE); + glCullFace(convertCullMode(cullMode_)); + } + cachedCullMode_ = cullMode_; + } +} + +void GLPipeline::getBlendFactors(BlendMode mode, GLenum& srcFactor, GLenum& dstFactor) { + switch (mode) { + case BlendMode::None: + srcFactor = GL_ONE; + dstFactor = GL_ZERO; + break; + case BlendMode::Alpha: + srcFactor = GL_SRC_ALPHA; + dstFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case BlendMode::Additive: + srcFactor = GL_SRC_ALPHA; + dstFactor = GL_ONE; + break; + case BlendMode::Multiply: + srcFactor = GL_DST_COLOR; + dstFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + default: + srcFactor = GL_SRC_ALPHA; + dstFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + } +} + +GLenum GLPipeline::convertDepthFunc(DepthFunc func) { + switch (func) { + case DepthFunc::Never: return GL_NEVER; + case DepthFunc::Less: return GL_LESS; + case DepthFunc::Equal: return GL_EQUAL; + case DepthFunc::LessEqual: return GL_LEQUAL; + case DepthFunc::Greater: return GL_GREATER; + case DepthFunc::NotEqual: return GL_NOTEQUAL; + case DepthFunc::GreaterEqual: return GL_GEQUAL; + case DepthFunc::Always: return GL_ALWAYS; + default: return GL_LESS; + } +} + +GLenum GLPipeline::convertCullMode(CullMode mode) { + switch (mode) { + case CullMode::Front: return GL_FRONT; + case CullMode::Back: return GL_BACK; + case CullMode::Both: return GL_FRONT_AND_BACK; + default: return GL_BACK; + } +} + +} // namespace extra2d diff --git a/Extra2D/src/graphics/backends/opengl/gl_renderer.cpp b/Extra2D/src/graphics/backends/opengl/gl_renderer.cpp index 548bcd2..7db2540 100644 --- a/Extra2D/src/graphics/backends/opengl/gl_renderer.cpp +++ b/Extra2D/src/graphics/backends/opengl/gl_renderer.cpp @@ -1,7 +1,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -17,30 +19,11 @@ namespace extra2d { // VBO 初始大小(用于 VRAM 跟踪) static constexpr size_t SHAPE_VBO_SIZE = 1024 * sizeof(float); -// ============================================================================ -// BlendMode 查找表 - 编译期构建,运行时 O(1) 查找 -// ============================================================================ -struct BlendState { - bool enable; - GLenum srcFactor; - GLenum dstFactor; -}; - -static constexpr BlendState BLEND_STATES[] = { - {false, 0, 0}, // BlendMode::None - {true, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA}, // BlendMode::Alpha - {true, GL_SRC_ALPHA, GL_ONE}, // BlendMode::Additive - {true, GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA} // BlendMode::Multiply -}; - -static constexpr size_t BLEND_STATE_COUNT = - sizeof(BLEND_STATES) / sizeof(BLEND_STATES[0]); - /** * @brief 构造函数,初始化OpenGL渲染器成员变量 */ GLRenderer::GLRenderer() - : window_(nullptr), shapeVao_(0), shapeVbo_(0), lineVao_(0), lineVbo_(0), + : window_(nullptr), shapeVao_(0), lineVao_(0), vsync_(true), shapeVertexCount_(0), currentShapeMode_(GL_TRIANGLES), lineVertexCount_(0), currentLineWidth_(1.0f) { resetStats(); @@ -65,7 +48,11 @@ GLRenderer::~GLRenderer() { shutdown(); } bool GLRenderer::init(IWindow *window) { window_ = window; - // Switch: GL 上下文已通过 SDL2 + EGL 初始化,无需 glewInit() + // 初始化 OpenGL 上下文(Switch 平台已通过 SDL2 + EGL 初始化,GLContext 会处理兼容性) + if (!GLContext::get().init()) { + E2D_LOG_ERROR("Failed to initialize OpenGL context"); + return false; + } // 初始化精灵批渲染器 if (!spriteBatch_.init()) { @@ -76,16 +63,24 @@ bool GLRenderer::init(IWindow *window) { // 初始化形状渲染 initShapeRendering(); - // 设置 OpenGL 状态 - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + // 初始化管线状态管理 + PipelineDesc pipelineDesc; + pipelineDesc.blendMode = BlendMode::Alpha; + pipelineDesc.depthTest = false; + pipelineDesc.depthWrite = false; + if (!pipeline_.init(pipelineDesc)) { + E2D_LOG_ERROR("Failed to initialize GLPipeline"); + return false; + } + + // 应用初始管线状态 + pipeline_.applyAllStates(); // 标记 GPU 上下文为有效 GPUContext::get().markValid(); E2D_LOG_INFO("OpenGL Renderer initialized"); - E2D_LOG_INFO("OpenGL Version: {}", - reinterpret_cast(glGetString(GL_VERSION))); + E2D_LOG_INFO("OpenGL Version: {}", GLContext::get().getVersionString()); return true; } @@ -100,24 +95,22 @@ void GLRenderer::shutdown() { spriteBatch_.shutdown(); - if (lineVbo_ != 0) { - glDeleteBuffers(1, &lineVbo_); - VRAMMgr::get().freeBuffer(MAX_LINE_VERTICES * sizeof(ShapeVertex)); - lineVbo_ = 0; - } + // 关闭 GLBuffer(自动释放 VBO) + lineBuffer_.shutdown(); + shapeBuffer_.shutdown(); + + // 删除 VAO(VAO 仍然手动管理) if (lineVao_ != 0) { glDeleteVertexArrays(1, &lineVao_); lineVao_ = 0; } - if (shapeVbo_ != 0) { - glDeleteBuffers(1, &shapeVbo_); - VRAMMgr::get().freeBuffer(MAX_SHAPE_VERTICES * sizeof(ShapeVertex)); - shapeVbo_ = 0; - } if (shapeVao_ != 0) { glDeleteVertexArrays(1, &shapeVao_); shapeVao_ = 0; } + + // 关闭 OpenGL 上下文 + GLContext::get().shutdown(); } /** @@ -125,6 +118,9 @@ void GLRenderer::shutdown() { * @param clearColor 清屏颜色 */ void GLRenderer::beginFrame(const Color &clearColor) { + // 应用管线状态 + pipeline_.applyAllStates(); + glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a); glClear(GL_COLOR_BUFFER_BIT); resetStats(); @@ -152,7 +148,8 @@ void GLRenderer::endFrame() { * @param height 视口高度 */ void GLRenderer::setViewport(int x, int y, int width, int height) { - glViewport(x, y, width, height); + // 使用 GLPipeline 管理视口状态 + pipeline_.setViewport(x, y, width, height); } /** @@ -172,31 +169,8 @@ void GLRenderer::setVSync(bool enabled) { * @param mode 混合模式枚举值 */ void GLRenderer::setBlendMode(BlendMode mode) { - // 状态缓存检查,避免冗余 GL 调用 - if (cachedBlendMode_ == mode) { - return; - } - cachedBlendMode_ = mode; - - // 使用查找表替代 switch - size_t index = static_cast(mode); - if (index >= BLEND_STATE_COUNT) { - index = 0; - } - - const BlendState &state = BLEND_STATES[index]; - if (state.enable) { - if (!blendEnabled_) { - glEnable(GL_BLEND); - blendEnabled_ = true; - } - glBlendFunc(state.srcFactor, state.dstFactor); - } else { - if (blendEnabled_) { - glDisable(GL_BLEND); - blendEnabled_ = false; - } - } + // 使用 GLPipeline 管理混合状态 + pipeline_.setBlendMode(mode); } /** @@ -787,14 +761,19 @@ void GLRenderer::initShapeRendering() { } } - // 创建形状 VAO 和 VBO + // 初始化形状 GLBuffer(使用 Dynamic 使用模式,因为每帧都会更新) + BufferDesc shapeBufferDesc; + shapeBufferDesc.type = BufferType::Vertex; + shapeBufferDesc.usage = BufferUsage::Dynamic; + shapeBufferDesc.size = MAX_SHAPE_VERTICES * sizeof(ShapeVertex); + if (!shapeBuffer_.init(shapeBufferDesc)) { + E2D_LOG_ERROR("Failed to initialize shape buffer"); + } + + // 创建形状 VAO(手动管理,用于顶点属性配置) glGenVertexArrays(1, &shapeVao_); - glGenBuffers(1, &shapeVbo_); - glBindVertexArray(shapeVao_); - glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_); - glBufferData(GL_ARRAY_BUFFER, MAX_SHAPE_VERTICES * sizeof(ShapeVertex), - nullptr, GL_DYNAMIC_DRAW); + shapeBuffer_.bind(); // 位置属性 (location = 0) glEnableVertexAttribArray(0); @@ -808,14 +787,19 @@ void GLRenderer::initShapeRendering() { glBindVertexArray(0); - // 创建线条专用 VAO 和 VBO + // 初始化线条 GLBuffer(使用 Dynamic 使用模式,因为每帧都会更新) + BufferDesc lineBufferDesc; + lineBufferDesc.type = BufferType::Vertex; + lineBufferDesc.usage = BufferUsage::Dynamic; + lineBufferDesc.size = MAX_LINE_VERTICES * sizeof(ShapeVertex); + if (!lineBuffer_.init(lineBufferDesc)) { + E2D_LOG_ERROR("Failed to initialize line buffer"); + } + + // 创建线条 VAO(手动管理,用于顶点属性配置) glGenVertexArrays(1, &lineVao_); - glGenBuffers(1, &lineVbo_); - glBindVertexArray(lineVao_); - glBindBuffer(GL_ARRAY_BUFFER, lineVbo_); - glBufferData(GL_ARRAY_BUFFER, MAX_LINE_VERTICES * sizeof(ShapeVertex), - nullptr, GL_DYNAMIC_DRAW); + lineBuffer_.bind(); // 位置属性 (location = 0) glEnableVertexAttribArray(0); @@ -828,10 +812,6 @@ void GLRenderer::initShapeRendering() { reinterpret_cast(offsetof(ShapeVertex, r))); glBindVertexArray(0); - - // VRAM 跟踪 - VRAMMgr::get().allocBuffer(MAX_SHAPE_VERTICES * sizeof(ShapeVertex)); - VRAMMgr::get().allocBuffer(MAX_LINE_VERTICES * sizeof(ShapeVertex)); } /** @@ -911,9 +891,9 @@ void GLRenderer::flushShapeBatch() { shapeShader_->setMat4("u_viewProjection", viewProjection_); } - glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_); - glBufferSubData(GL_ARRAY_BUFFER, 0, shapeVertexCount_ * sizeof(ShapeVertex), - shapeVertexCache_.data()); + // 使用 GLBuffer::updateData() 更新缓冲区数据 + shapeBuffer_.updateData(shapeVertexCache_.data(), 0, + shapeVertexCount_ * sizeof(ShapeVertex)); glBindVertexArray(shapeVao_); glDrawArrays(currentShapeMode_, 0, static_cast(shapeVertexCount_)); @@ -940,9 +920,9 @@ void GLRenderer::flushLineBatch() { shapeShader_->setMat4("u_viewProjection", viewProjection_); } - glBindBuffer(GL_ARRAY_BUFFER, lineVbo_); - glBufferSubData(GL_ARRAY_BUFFER, 0, lineVertexCount_ * sizeof(ShapeVertex), - lineVertexCache_.data()); + // 使用 GLBuffer::updateData() 更新缓冲区数据 + lineBuffer_.updateData(lineVertexCache_.data(), 0, + lineVertexCount_ * sizeof(ShapeVertex)); glBindVertexArray(lineVao_); glDrawArrays(GL_LINES, 0, static_cast(lineVertexCount_)); @@ -952,4 +932,90 @@ void GLRenderer::flushLineBatch() { lineVertexCount_ = 0; } +/** + * @brief 创建帧缓冲对象 + * @param desc 帧缓冲描述 + * @return 创建的帧缓冲智能指针 + */ +Ptr GLRenderer::createFramebuffer(const FramebufferDesc& desc) { + auto framebuffer = makePtr(); + if (!framebuffer->init(desc)) { + E2D_LOG_ERROR("Failed to create framebuffer"); + return nullptr; + } + return framebuffer; +} + +/** + * @brief 绑定帧缓冲(作为渲染目标) + * @param framebuffer 帧缓冲对象指针,传入 nullptr 则绑定默认帧缓冲 + */ +void GLRenderer::bindFramebuffer(GLFramebuffer* framebuffer) { + // 先刷新所有待处理的渲染批次 + flush(); + flushShapeBatch(); + flushLineBatch(); + + if (framebuffer == nullptr) { + // 绑定默认帧缓冲(ID 为 0) + glBindFramebuffer(GL_FRAMEBUFFER, 0); + currentFramebuffer_ = nullptr; + E2D_LOG_TRACE("Bound default framebuffer (0)"); + } else { + // 绑定自定义帧缓冲 + framebuffer->bind(); + currentFramebuffer_ = framebuffer; + E2D_LOG_TRACE("Bound custom framebuffer (ID: {})", framebuffer->getFboID()); + } +} + +/** + * @brief 解绑帧缓冲(恢复到默认帧缓冲) + */ +void GLRenderer::unbindFramebuffer() { + bindFramebuffer(nullptr); +} + +/** + * @brief 获取默认帧缓冲 + * @return 默认帧缓冲智能指针 + */ +Ptr GLRenderer::getDefaultFramebuffer() const { + if (!defaultFramebuffer_) { + // 延迟创建默认帧缓冲对象(代表系统默认帧缓冲,ID 为 0) + defaultFramebuffer_ = makePtr(); + // 注意:默认帧缓冲不需要显式初始化,它的 FBO ID 为 0 + } + return defaultFramebuffer_; +} + +/** + * @brief 清除当前绑定的帧缓冲 + * @param color 清除颜色 + * @param clearColor 是否清除颜色缓冲 + * @param clearDepth 是否清除深度缓冲 + * @param clearStencil 是否清除模板缓冲 + */ +void GLRenderer::clearFramebuffer(const Color& color, bool clearColor, + bool clearDepth, bool clearStencil) { + GLbitfield mask = 0; + + if (clearColor) { + glClearColor(color.r, color.g, color.b, color.a); + mask |= GL_COLOR_BUFFER_BIT; + } + + if (clearDepth) { + mask |= GL_DEPTH_BUFFER_BIT; + } + + if (clearStencil) { + mask |= GL_STENCIL_BUFFER_BIT; + } + + if (mask != 0) { + glClear(mask); + } +} + } // namespace extra2d diff --git a/Extra2D/src/graphics/backends/opengl/gl_sprite_batch.cpp b/Extra2D/src/graphics/backends/opengl/gl_sprite_batch.cpp index c605202..14dc4f3 100644 --- a/Extra2D/src/graphics/backends/opengl/gl_sprite_batch.cpp +++ b/Extra2D/src/graphics/backends/opengl/gl_sprite_batch.cpp @@ -6,8 +6,7 @@ namespace extra2d { GLSpriteBatch::GLSpriteBatch() - : vao_(0), vbo_(0), ebo_(0), currentTexture_(nullptr), - drawCallCount_(0) {} + : vao_(0), currentTexture_(nullptr), drawCallCount_(0) {} GLSpriteBatch::~GLSpriteBatch() { shutdown(); } @@ -19,18 +18,21 @@ bool GLSpriteBatch::init() { return false; } - // 创建 VAO, VBO, EBO + // 创建 VAO glGenVertexArrays(1, &vao_); - glGenBuffers(1, &vbo_); - glGenBuffers(1, &ebo_); - glBindVertexArray(vao_); - // 设置 VBO - glBindBuffer(GL_ARRAY_BUFFER, vbo_); - glBufferData(GL_ARRAY_BUFFER, - SpriteBatch::MAX_VERTICES * sizeof(SpriteVertex), nullptr, - GL_DYNAMIC_DRAW); + // 初始化 VBO(顶点缓冲区)- 动态使用模式 + BufferDesc vboDesc; + vboDesc.type = BufferType::Vertex; + vboDesc.usage = BufferUsage::Dynamic; + vboDesc.size = SpriteBatch::MAX_VERTICES * sizeof(SpriteVertex); + vboDesc.initialData = nullptr; + if (!vbo_.init(vboDesc)) { + E2D_LOG_ERROR("Failed to initialize sprite batch VBO"); + return false; + } + vbo_.bind(); // 设置顶点属性 glEnableVertexAttribArray(0); @@ -45,11 +47,17 @@ bool GLSpriteBatch::init() { glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex), reinterpret_cast(offsetof(SpriteVertex, color))); - // 设置 EBO(索引缓冲区)- 使用 batch 层生成的静态索引 - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, - batch_.getIndices().size() * sizeof(uint16_t), - batch_.getIndices().data(), GL_STATIC_DRAW); + // 初始化 EBO(索引缓冲区)- 静态使用模式 + BufferDesc eboDesc; + eboDesc.type = BufferType::Index; + eboDesc.usage = BufferUsage::Static; + eboDesc.size = batch_.getIndices().size() * sizeof(uint16_t); + eboDesc.initialData = batch_.getIndices().data(); + if (!ebo_.init(eboDesc)) { + E2D_LOG_ERROR("Failed to initialize sprite batch EBO"); + return false; + } + ebo_.bind(); glBindVertexArray(0); @@ -57,14 +65,10 @@ bool GLSpriteBatch::init() { } void GLSpriteBatch::shutdown() { - if (ebo_ != 0) { - glDeleteBuffers(1, &ebo_); - ebo_ = 0; - } - if (vbo_ != 0) { - glDeleteBuffers(1, &vbo_); - vbo_ = 0; - } + // 使用 GLBuffer::shutdown() 释放缓冲区资源 + vbo_.shutdown(); + ebo_.shutdown(); + if (vao_ != 0) { glDeleteVertexArrays(1, &vao_); vao_ = 0; @@ -78,12 +82,22 @@ void GLSpriteBatch::begin(const glm::mat4 &viewProjection) { drawCallCount_ = 0; // 保存 viewProjection 矩阵供后续使用 viewProjection_ = viewProjection; + + // 绑定 VAO 和缓冲区 + glBindVertexArray(vao_); + vbo_.bind(); + ebo_.bind(); } void GLSpriteBatch::end() { if (batch_.getSpriteCount() > 0) { flush(); } + + // 解绑缓冲区 + vbo_.unbind(); + ebo_.unbind(); + glBindVertexArray(0); } void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) { @@ -154,13 +168,12 @@ void GLSpriteBatch::submitBatch() { shader_->setFloat("u_opacity", 1.0f); } - // 上传顶点数据 - glBindBuffer(GL_ARRAY_BUFFER, vbo_); - glBufferSubData(GL_ARRAY_BUFFER, 0, - batch_.getVertices().size() * sizeof(SpriteVertex), - batch_.getVertices().data()); - - glBindVertexArray(vao_); + // 上传顶点数据 - 使用 orphaning 策略优化动态缓冲区 + // 通过传入 nullptr 进行 orphaning,告诉驱动器可以丢弃旧缓冲区并分配新内存 + // 这样可以避免 GPU 等待,提高性能 + size_t vertexDataSize = batch_.getVertices().size() * sizeof(SpriteVertex); + vbo_.setData(nullptr, vertexDataSize); // orphaning + vbo_.updateData(batch_.getVertices().data(), 0, vertexDataSize); // 绘制 currentTexture_->bind(0); diff --git a/Extra2D/src/graphics/batch/shape_batch.cpp b/Extra2D/src/graphics/batch/shape_batch.cpp new file mode 100644 index 0000000..6958818 --- /dev/null +++ b/Extra2D/src/graphics/batch/shape_batch.cpp @@ -0,0 +1,72 @@ +#include + +namespace extra2d { + +// ============================================================================ +// ShapeBatch 基础实现(后端无关部分) +// ============================================================================ + +// 这里可以添加后端无关的工具函数 +// 例如:计算圆形的顶点、三角化多边形等 + +// 计算圆形顶点 +void calculateCircleVertices(std::vector& outVertices, + const Vec2& center, float radius, + int segments, bool fill) { + outVertices.clear(); + outVertices.reserve(fill ? segments + 1 : segments); + + if (fill) { + // 填充圆形:中心点 + 边缘点 + outVertices.push_back(center); + for (int i = 0; i <= segments; ++i) { + float angle = 2.0f * 3.14159265359f * static_cast(i) / static_cast(segments); + outVertices.emplace_back( + center.x + radius * cosf(angle), + center.y + radius * sinf(angle) + ); + } + } else { + // 圆形边框:只保留边缘点 + for (int i = 0; i < segments; ++i) { + float angle = 2.0f * 3.14159265359f * static_cast(i) / static_cast(segments); + outVertices.emplace_back( + center.x + radius * cosf(angle), + center.y + radius * sinf(angle) + ); + } + } +} + +// 计算矩形顶点 +void calculateRectVertices(std::vector& outVertices, const Rect& rect) { + outVertices.clear(); + outVertices.reserve(4); + + float x1 = rect.origin.x; + float y1 = rect.origin.y; + float x2 = rect.origin.x + rect.size.width; + float y2 = rect.origin.y + rect.size.height; + + outVertices.emplace_back(x1, y1); // 左上 + outVertices.emplace_back(x2, y1); // 右上 + outVertices.emplace_back(x2, y2); // 右下 + outVertices.emplace_back(x1, y2); // 左下 +} + +// 简单的多边形三角化(扇形三角化,适用于凸多边形) +void triangulatePolygon(std::vector& outIndices, int vertexCount) { + outIndices.clear(); + if (vertexCount < 3) { + return; + } + + // 扇形三角化:以第一个顶点为扇形中心 + for (int i = 1; i < vertexCount - 1; ++i) { + outIndices.push_back(0); + outIndices.push_back(i); + outIndices.push_back(i + 1); + } +} + +} // namespace extra2d diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..3323eb0 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,44 @@ +# 资源抽象层 (Resources Abstraction Layer) + +此目录包含渲染后端无关的资源抽象接口。 + +## 文件说明 + +### 头文件 (include/extra2d/graphics/resources/) + +- **buffer.h** - 缓冲区抽象接口 + - 顶点缓冲、索引缓冲、统一缓冲 + - 支持 Static/Dynamic/Stream 使用模式 + +- **pipeline.h** - 渲染管线抽象接口 + - 混合模式、深度测试、裁剪状态 + - 顶点属性描述 + +- **framebuffer.h** - 帧缓冲抽象接口 + - 多颜色附件支持 + - 深度/模板附件 + - 多重采样支持 + +- **font_atlas.h** - 字体图集抽象接口 + - 字形信息管理 + - SDF 渲染支持 + - 文本测量功能 + +- **shader.h** - 着色器抽象接口 + - Uniform 变量设置 + - 支持各种数据类型 + +### 实现说明 + +这些接口是纯虚类(抽象接口),不需要在 `src/graphics/resources/` 目录中提供实现。 + +实际的实现位于各个渲染后端目录中: +- OpenGL 实现: `src/graphics/backends/opengl/` +- Vulkan 实现: `src/graphics/backends/vulkan/` (未来) + +## 设计原则 + +1. **后端无关** - 接口不依赖任何特定渲染 API +2. **工厂模式** - 通过后端工厂创建具体实现 +3. **资源管理** - 使用智能指针管理资源生命周期 +4. **类型安全** - 使用强类型枚举和结构体