refactor(opengl): 重构渲染器资源管理并引入资源抽象层

- 引入资源抽象层接口(Buffer、Pipeline、Framebuffer等)
- 将OpenGL资源管理重构为GLBuffer、GLPipeline、GLFramebuffer等实现类
- 使用GLBuffer替代手动管理的VBO/IBO,提供更安全的资源生命周期管理
- 新增GLPipeline管理OpenGL管线状态,减少冗余状态切换
- 新增GLFramebuffer封装帧缓冲对象功能
- 更新GLRenderer使用新的资源管理方式
- 添加详细文档说明资源抽象层设计
- 修复相关内存泄漏问题

docs: 添加资源抽象层文档说明

- 新增docs/README.md详细说明资源抽象层设计
- 文档包含各接口功能说明和实现原则
This commit is contained in:
ChestnutYueyue 2026-02-17 22:36:02 +08:00
parent c8a6ea19e3
commit 2748e80dea
21 changed files with 2488 additions and 137 deletions

View File

@ -0,0 +1,83 @@
#pragma once
#include <extra2d/graphics/resources/buffer.h>
#include <glad/glad.h>
#include <cstddef>
#include <cstdint>
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<uintptr_t>(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

View File

@ -0,0 +1,139 @@
#pragma once
#include <glad/glad.h>
#include <cstdint>
#include <string>
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

View File

@ -0,0 +1,105 @@
#pragma once
#include <extra2d/graphics/resources/framebuffer.h>
#include <glad/glad.h>
#include <array>
#include <cstdint>
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> texture, int attachment = 0) override;
void attachDepthTexture(Ptr<Texture> texture) override;
void attachDepthStencilTexture(Ptr<Texture> texture) override;
bool isComplete() override;
Ptr<Texture> getColorTexture(int attachment = 0) const override;
Ptr<Texture> getDepthTexture() const override;
int getWidth() const override { return width_; }
int getHeight() const override { return height_; }
Size getSize() const override { return Size(static_cast<float>(width_), static_cast<float>(height_)); }
bool isValid() const override { return fboID_ != 0; }
uintptr_t getNativeHandle() const override { return static_cast<uintptr_t>(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<uint8_t>& 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<Ptr<Texture>, MAX_COLOR_ATTACHMENTS> colorTextures_;
Ptr<Texture> depthTexture_;
Ptr<Texture> depthStencilTexture_;
// 是否为内置纹理(需要自动清理)
bool hasInternalTextures_ = false;
/**
* @brief
*/
bool checkStatus();
/**
* @brief OpenGL
*/
static GLenum getColorAttachment(int index);
};
} // namespace extra2d

View File

@ -0,0 +1,131 @@
#pragma once
#include <extra2d/graphics/resources/pipeline.h>
#include <glad/glad.h>
#include <cstdint>
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

View File

@ -1,5 +1,8 @@
#pragma once #pragma once
#include <extra2d/graphics/backends/opengl/gl_buffer.h>
#include <extra2d/graphics/backends/opengl/gl_framebuffer.h>
#include <extra2d/graphics/backends/opengl/gl_pipeline.h>
#include <extra2d/graphics/backends/opengl/gl_sprite_batch.h> #include <extra2d/graphics/backends/opengl/gl_sprite_batch.h>
#include <extra2d/graphics/core/render_backend.h> #include <extra2d/graphics/core/render_backend.h>
#include <extra2d/graphics/shader/shader_interface.h> #include <extra2d/graphics/shader/shader_interface.h>
@ -10,7 +13,10 @@
namespace extra2d { namespace extra2d {
// 前向声明
class IWindow; class IWindow;
class GLContext;
class GLFramebuffer;
// ============================================================================ // ============================================================================
// OpenGL 渲染器实现 // OpenGL 渲染器实现
@ -77,6 +83,42 @@ public:
Stats getStats() const override { return stats_; } Stats getStats() const override { return stats_; }
void resetStats() override; void resetStats() override;
// GLFramebuffer 相关方法
/**
* @brief
* @param desc
* @return
*/
Ptr<GLFramebuffer> createFramebuffer(const FramebufferDesc& desc);
/**
* @brief
* @param framebuffer nullptr
*/
void bindFramebuffer(GLFramebuffer* framebuffer);
/**
* @brief
*/
void unbindFramebuffer();
/**
* @brief
* @return
*/
Ptr<GLFramebuffer> 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: private:
// 形状批处理常量 // 形状批处理常量
static constexpr size_t MAX_CIRCLE_SEGMENTS = 128; static constexpr size_t MAX_CIRCLE_SEGMENTS = 128;
@ -93,10 +135,10 @@ private:
GLSpriteBatch spriteBatch_; GLSpriteBatch spriteBatch_;
Ptr<IShader> shapeShader_; Ptr<IShader> shapeShader_;
GLuint shapeVao_; GLuint shapeVao_; // 形状 VAO手动管理用于顶点属性配置
GLuint shapeVbo_; GLBuffer shapeBuffer_; // 形状 VBO使用 GLBuffer 管理)
GLuint lineVao_; // 线条专用 VAO GLuint lineVao_; // 线条 VAO(手动管理,用于顶点属性配置)
GLuint lineVbo_; // 线条专用 VBO GLBuffer lineBuffer_; // 线条 VBO使用 GLBuffer 管理)
glm::mat4 viewProjection_; glm::mat4 viewProjection_;
std::vector<glm::mat4> transformStack_; std::vector<glm::mat4> transformStack_;
@ -113,13 +155,8 @@ private:
size_t lineVertexCount_ = 0; size_t lineVertexCount_ = 0;
float currentLineWidth_ = 1.0f; float currentLineWidth_ = 1.0f;
// OpenGL 状态缓存 // OpenGL 管线状态管理
BlendMode cachedBlendMode_ = BlendMode::None; GLPipeline pipeline_;
bool blendEnabled_ = false;
int cachedViewportX_ = 0;
int cachedViewportY_ = 0;
int cachedViewportWidth_ = 0;
int cachedViewportHeight_ = 0;
// 自动批处理状态 // 自动批处理状态
bool batchActive_ = false; // 批处理是否激活 bool batchActive_ = false; // 批处理是否激活
@ -128,6 +165,10 @@ private:
std::vector<SpriteData> pendingSprites_; // 待提交的精灵 std::vector<SpriteData> pendingSprites_; // 待提交的精灵
static constexpr size_t MAX_BATCH_SPRITES = 1000; // 最大批处理精灵数 static constexpr size_t MAX_BATCH_SPRITES = 1000; // 最大批处理精灵数
// 帧缓冲管理
mutable Ptr<GLFramebuffer> defaultFramebuffer_; // 默认帧缓冲(延迟创建)
GLFramebuffer* currentFramebuffer_ = nullptr; // 当前绑定的帧缓冲
void initShapeRendering(); void initShapeRendering();
void ensureBatchActive(); // 确保批处理已激活 void ensureBatchActive(); // 确保批处理已激活
void submitPendingSprites(); // 提交待处理的精灵 void submitPendingSprites(); // 提交待处理的精灵

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <extra2d/graphics/backends/opengl/gl_buffer.h>
#include <extra2d/graphics/backends/opengl/gl_texture.h> #include <extra2d/graphics/backends/opengl/gl_texture.h>
#include <extra2d/graphics/batch/sprite_batch.h> #include <extra2d/graphics/batch/sprite_batch.h>
#include <extra2d/graphics/shader/shader_interface.h> #include <extra2d/graphics/shader/shader_interface.h>
@ -39,8 +40,8 @@ public:
private: private:
// OpenGL 对象 // OpenGL 对象
GLuint vao_; GLuint vao_;
GLuint vbo_; GLBuffer vbo_; // 顶点缓冲区(动态)
GLuint ebo_; GLBuffer ebo_; // 索引缓冲区(静态)
// 后端无关的批处理层 // 后端无关的批处理层
SpriteBatch batch_; SpriteBatch batch_;

View File

@ -0,0 +1,157 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/types.h>
#include <glm/mat4x4.hpp>
#include <cstdint>
#include <vector>
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<Vec2>& points,
const Color& color, float width = 1.0f) = 0;
/**
* @brief
* @param points
* @param color
*/
virtual void fillPolygon(const std::vector<Vec2>& 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

View File

@ -3,6 +3,7 @@
#include <extra2d/core/color.h> #include <extra2d/core/color.h>
#include <extra2d/core/math_types.h> #include <extra2d/core/math_types.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/graphics/resources/pipeline.h>
#include <glm/mat4x4.hpp> #include <glm/mat4x4.hpp>
namespace extra2d { namespace extra2d {
@ -24,15 +25,7 @@ enum class BackendType {
// D3D12 // D3D12
}; };
// ============================================================================ // BlendMode 定义在 pipeline.h 中
// 混合模式
// ============================================================================
enum class BlendMode {
None, // 不混合
Alpha, // 标准 Alpha 混合
Additive, // 加法混合
Multiply // 乘法混合
};
// ============================================================================ // ============================================================================
// 渲染后端抽象接口 // 渲染后端抽象接口

View File

@ -0,0 +1,111 @@
#pragma once
#include <extra2d/core/types.h>
#include <cstddef>
#include <cstdint>
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

View File

@ -0,0 +1,131 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/types.h>
#include <extra2d/graphics/texture/texture.h>
#include <string>
#include <unordered_map>
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<Texture> 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

View File

@ -0,0 +1,140 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/types.h>
#include <extra2d/graphics/texture/texture.h>
#include <cstdint>
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> texture, int attachment = 0) = 0;
/**
* @brief
* @param texture
*/
virtual void attachDepthTexture(Ptr<Texture> texture) = 0;
/**
* @brief
* @param texture
*/
virtual void attachDepthStencilTexture(Ptr<Texture> texture) = 0;
/**
* @brief
* @return true
*/
virtual bool isComplete() = 0;
/**
* @brief
* @param attachment
* @return
*/
virtual Ptr<Texture> getColorTexture(int attachment = 0) const = 0;
/**
* @brief
* @return
*/
virtual Ptr<Texture> 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<uint8_t>& outData) = 0;
};
} // namespace extra2d

View File

@ -0,0 +1,162 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/types.h>
#include <cstdint>
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<VertexAttribute> 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

View File

@ -0,0 +1,134 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/types.h>
#include <glm/mat4x4.hpp>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#include <string>
#include <vector>
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<uint8_t> 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

View File

@ -0,0 +1,171 @@
#include <extra2d/graphics/backends/opengl/gl_buffer.h>
#include <extra2d/graphics/memory/vram_manager.h>
#include <extra2d/utils/logger.h>
#include <cstring>
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<GLsizeiptr>(size_), desc.initialData, glUsage_);
glBindBuffer(target_, 0);
// 追踪显存使用
VRAMMgr::get().allocBuffer(size_);
E2D_LOG_DEBUG("GLBuffer created: ID={}, Size={}, Type={}, Usage={}",
bufferID_, size_, static_cast<int>(type_), static_cast<int>(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<GLsizeiptr>(size), data);
} else {
// 大小不同,重新分配
size_ = size;
glBufferData(target_, static_cast<GLsizeiptr>(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<GLintptr>(offset), static_cast<GLsizeiptr>(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<GLsizeiptr>(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

View File

@ -0,0 +1,167 @@
#include <extra2d/graphics/backends/opengl/gl_context.h>
#include <extra2d/graphics/memory/gpu_context.h>
#include <extra2d/utils/logger.h>
#include <cstring>
#include <sstream>
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<const char*>(glGetString(GL_VERSION));
return version ? version : "Unknown";
}
std::string GLContext::getVendor() const {
const char* vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
return vendor ? vendor : "Unknown";
}
std::string GLContext::getRenderer() const {
const char* renderer = reinterpret_cast<const char*>(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<const char*>(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<const char*>(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

View File

@ -0,0 +1,268 @@
#include <extra2d/graphics/backends/opengl/gl_framebuffer.h>
#include <extra2d/graphics/backends/opengl/gl_texture.h>
#include <extra2d/utils/logger.h>
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> texture, int attachment) {
if (fboID_ == 0 || !texture || attachment < 0 || attachment >= MAX_COLOR_ATTACHMENTS) {
return;
}
bind();
// 获取 OpenGL 纹理 ID
GLuint texID = static_cast<GLuint>(reinterpret_cast<uintptr_t>(texture->getNativeHandle()));
glFramebufferTexture2D(GL_FRAMEBUFFER, getColorAttachment(attachment),
GL_TEXTURE_2D, texID, 0);
colorTextures_[attachment] = texture;
unbind();
}
void GLFramebuffer::attachDepthTexture(Ptr<Texture> texture) {
if (fboID_ == 0 || !texture) {
return;
}
bind();
// 获取 OpenGL 纹理 ID
GLuint texID = static_cast<GLuint>(reinterpret_cast<uintptr_t>(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> texture) {
if (fboID_ == 0 || !texture) {
return;
}
bind();
// 获取 OpenGL 纹理 ID
GLuint texID = static_cast<GLuint>(reinterpret_cast<uintptr_t>(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<Texture> GLFramebuffer::getColorTexture(int attachment) const {
if (attachment >= 0 && attachment < MAX_COLOR_ATTACHMENTS) {
return colorTextures_[attachment];
}
return nullptr;
}
Ptr<Texture> 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<uint8_t>& 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

View File

@ -0,0 +1,222 @@
#include <extra2d/graphics/backends/opengl/gl_pipeline.h>
#include <extra2d/utils/logger.h>
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<int>(blendMode_), depthTest_, static_cast<int>(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

View File

@ -1,7 +1,9 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <extra2d/graphics/backends/opengl/gl_context.h>
#include <extra2d/graphics/backends/opengl/gl_font_atlas.h> #include <extra2d/graphics/backends/opengl/gl_font_atlas.h>
#include <extra2d/graphics/backends/opengl/gl_framebuffer.h>
#include <extra2d/graphics/backends/opengl/gl_renderer.h> #include <extra2d/graphics/backends/opengl/gl_renderer.h>
#include <extra2d/graphics/backends/opengl/gl_texture.h> #include <extra2d/graphics/backends/opengl/gl_texture.h>
#include <extra2d/graphics/batch/sprite_batch.h> #include <extra2d/graphics/batch/sprite_batch.h>
@ -17,30 +19,11 @@ namespace extra2d {
// VBO 初始大小(用于 VRAM 跟踪) // VBO 初始大小(用于 VRAM 跟踪)
static constexpr size_t SHAPE_VBO_SIZE = 1024 * sizeof(float); 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渲染器成员变量 * @brief OpenGL渲染器成员变量
*/ */
GLRenderer::GLRenderer() 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), vsync_(true), shapeVertexCount_(0), currentShapeMode_(GL_TRIANGLES),
lineVertexCount_(0), currentLineWidth_(1.0f) { lineVertexCount_(0), currentLineWidth_(1.0f) {
resetStats(); resetStats();
@ -65,7 +48,11 @@ GLRenderer::~GLRenderer() { shutdown(); }
bool GLRenderer::init(IWindow *window) { bool GLRenderer::init(IWindow *window) {
window_ = 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()) { if (!spriteBatch_.init()) {
@ -76,16 +63,24 @@ bool GLRenderer::init(IWindow *window) {
// 初始化形状渲染 // 初始化形状渲染
initShapeRendering(); initShapeRendering();
// 设置 OpenGL 状态 // 初始化管线状态管理
glEnable(GL_BLEND); PipelineDesc pipelineDesc;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 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 上下文为有效 // 标记 GPU 上下文为有效
GPUContext::get().markValid(); GPUContext::get().markValid();
E2D_LOG_INFO("OpenGL Renderer initialized"); E2D_LOG_INFO("OpenGL Renderer initialized");
E2D_LOG_INFO("OpenGL Version: {}", E2D_LOG_INFO("OpenGL Version: {}", GLContext::get().getVersionString());
reinterpret_cast<const char *>(glGetString(GL_VERSION)));
return true; return true;
} }
@ -100,24 +95,22 @@ void GLRenderer::shutdown() {
spriteBatch_.shutdown(); spriteBatch_.shutdown();
if (lineVbo_ != 0) { // 关闭 GLBuffer自动释放 VBO
glDeleteBuffers(1, &lineVbo_); lineBuffer_.shutdown();
VRAMMgr::get().freeBuffer(MAX_LINE_VERTICES * sizeof(ShapeVertex)); shapeBuffer_.shutdown();
lineVbo_ = 0;
} // 删除 VAOVAO 仍然手动管理)
if (lineVao_ != 0) { if (lineVao_ != 0) {
glDeleteVertexArrays(1, &lineVao_); glDeleteVertexArrays(1, &lineVao_);
lineVao_ = 0; lineVao_ = 0;
} }
if (shapeVbo_ != 0) {
glDeleteBuffers(1, &shapeVbo_);
VRAMMgr::get().freeBuffer(MAX_SHAPE_VERTICES * sizeof(ShapeVertex));
shapeVbo_ = 0;
}
if (shapeVao_ != 0) { if (shapeVao_ != 0) {
glDeleteVertexArrays(1, &shapeVao_); glDeleteVertexArrays(1, &shapeVao_);
shapeVao_ = 0; shapeVao_ = 0;
} }
// 关闭 OpenGL 上下文
GLContext::get().shutdown();
} }
/** /**
@ -125,6 +118,9 @@ void GLRenderer::shutdown() {
* @param clearColor * @param clearColor
*/ */
void GLRenderer::beginFrame(const Color &clearColor) { void GLRenderer::beginFrame(const Color &clearColor) {
// 应用管线状态
pipeline_.applyAllStates();
glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a); glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
resetStats(); resetStats();
@ -152,7 +148,8 @@ void GLRenderer::endFrame() {
* @param height * @param height
*/ */
void GLRenderer::setViewport(int x, int y, int width, int 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 * @param mode
*/ */
void GLRenderer::setBlendMode(BlendMode mode) { void GLRenderer::setBlendMode(BlendMode mode) {
// 状态缓存检查,避免冗余 GL 调用 // 使用 GLPipeline 管理混合状态
if (cachedBlendMode_ == mode) { pipeline_.setBlendMode(mode);
return;
}
cachedBlendMode_ = mode;
// 使用查找表替代 switch
size_t index = static_cast<size_t>(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;
}
}
} }
/** /**
@ -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_); glGenVertexArrays(1, &shapeVao_);
glGenBuffers(1, &shapeVbo_);
glBindVertexArray(shapeVao_); glBindVertexArray(shapeVao_);
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_); shapeBuffer_.bind();
glBufferData(GL_ARRAY_BUFFER, MAX_SHAPE_VERTICES * sizeof(ShapeVertex),
nullptr, GL_DYNAMIC_DRAW);
// 位置属性 (location = 0) // 位置属性 (location = 0)
glEnableVertexAttribArray(0); glEnableVertexAttribArray(0);
@ -808,14 +787,19 @@ void GLRenderer::initShapeRendering() {
glBindVertexArray(0); 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_); glGenVertexArrays(1, &lineVao_);
glGenBuffers(1, &lineVbo_);
glBindVertexArray(lineVao_); glBindVertexArray(lineVao_);
glBindBuffer(GL_ARRAY_BUFFER, lineVbo_); lineBuffer_.bind();
glBufferData(GL_ARRAY_BUFFER, MAX_LINE_VERTICES * sizeof(ShapeVertex),
nullptr, GL_DYNAMIC_DRAW);
// 位置属性 (location = 0) // 位置属性 (location = 0)
glEnableVertexAttribArray(0); glEnableVertexAttribArray(0);
@ -828,10 +812,6 @@ void GLRenderer::initShapeRendering() {
reinterpret_cast<void *>(offsetof(ShapeVertex, r))); reinterpret_cast<void *>(offsetof(ShapeVertex, r)));
glBindVertexArray(0); 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_); shapeShader_->setMat4("u_viewProjection", viewProjection_);
} }
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_); // 使用 GLBuffer::updateData() 更新缓冲区数据
glBufferSubData(GL_ARRAY_BUFFER, 0, shapeVertexCount_ * sizeof(ShapeVertex), shapeBuffer_.updateData(shapeVertexCache_.data(), 0,
shapeVertexCache_.data()); shapeVertexCount_ * sizeof(ShapeVertex));
glBindVertexArray(shapeVao_); glBindVertexArray(shapeVao_);
glDrawArrays(currentShapeMode_, 0, static_cast<GLsizei>(shapeVertexCount_)); glDrawArrays(currentShapeMode_, 0, static_cast<GLsizei>(shapeVertexCount_));
@ -940,9 +920,9 @@ void GLRenderer::flushLineBatch() {
shapeShader_->setMat4("u_viewProjection", viewProjection_); shapeShader_->setMat4("u_viewProjection", viewProjection_);
} }
glBindBuffer(GL_ARRAY_BUFFER, lineVbo_); // 使用 GLBuffer::updateData() 更新缓冲区数据
glBufferSubData(GL_ARRAY_BUFFER, 0, lineVertexCount_ * sizeof(ShapeVertex), lineBuffer_.updateData(lineVertexCache_.data(), 0,
lineVertexCache_.data()); lineVertexCount_ * sizeof(ShapeVertex));
glBindVertexArray(lineVao_); glBindVertexArray(lineVao_);
glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(lineVertexCount_)); glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(lineVertexCount_));
@ -952,4 +932,90 @@ void GLRenderer::flushLineBatch() {
lineVertexCount_ = 0; lineVertexCount_ = 0;
} }
/**
* @brief
* @param desc
* @return
*/
Ptr<GLFramebuffer> GLRenderer::createFramebuffer(const FramebufferDesc& desc) {
auto framebuffer = makePtr<GLFramebuffer>();
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<GLFramebuffer> GLRenderer::getDefaultFramebuffer() const {
if (!defaultFramebuffer_) {
// 延迟创建默认帧缓冲对象代表系统默认帧缓冲ID 为 0
defaultFramebuffer_ = makePtr<GLFramebuffer>();
// 注意:默认帧缓冲不需要显式初始化,它的 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 } // namespace extra2d

View File

@ -6,8 +6,7 @@
namespace extra2d { namespace extra2d {
GLSpriteBatch::GLSpriteBatch() GLSpriteBatch::GLSpriteBatch()
: vao_(0), vbo_(0), ebo_(0), currentTexture_(nullptr), : vao_(0), currentTexture_(nullptr), drawCallCount_(0) {}
drawCallCount_(0) {}
GLSpriteBatch::~GLSpriteBatch() { shutdown(); } GLSpriteBatch::~GLSpriteBatch() { shutdown(); }
@ -19,18 +18,21 @@ bool GLSpriteBatch::init() {
return false; return false;
} }
// 创建 VAO, VBO, EBO // 创建 VAO
glGenVertexArrays(1, &vao_); glGenVertexArrays(1, &vao_);
glGenBuffers(1, &vbo_);
glGenBuffers(1, &ebo_);
glBindVertexArray(vao_); glBindVertexArray(vao_);
// 设置 VBO // 初始化 VBO顶点缓冲区- 动态使用模式
glBindBuffer(GL_ARRAY_BUFFER, vbo_); BufferDesc vboDesc;
glBufferData(GL_ARRAY_BUFFER, vboDesc.type = BufferType::Vertex;
SpriteBatch::MAX_VERTICES * sizeof(SpriteVertex), nullptr, vboDesc.usage = BufferUsage::Dynamic;
GL_DYNAMIC_DRAW); 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); glEnableVertexAttribArray(0);
@ -45,11 +47,17 @@ bool GLSpriteBatch::init() {
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex), glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex),
reinterpret_cast<void *>(offsetof(SpriteVertex, color))); reinterpret_cast<void *>(offsetof(SpriteVertex, color)));
// 设置 EBO索引缓冲区- 使用 batch 层生成的静态索引 // 初始化 EBO索引缓冲区- 静态使用模式
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_); BufferDesc eboDesc;
glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboDesc.type = BufferType::Index;
batch_.getIndices().size() * sizeof(uint16_t), eboDesc.usage = BufferUsage::Static;
batch_.getIndices().data(), GL_STATIC_DRAW); 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); glBindVertexArray(0);
@ -57,14 +65,10 @@ bool GLSpriteBatch::init() {
} }
void GLSpriteBatch::shutdown() { void GLSpriteBatch::shutdown() {
if (ebo_ != 0) { // 使用 GLBuffer::shutdown() 释放缓冲区资源
glDeleteBuffers(1, &ebo_); vbo_.shutdown();
ebo_ = 0; ebo_.shutdown();
}
if (vbo_ != 0) {
glDeleteBuffers(1, &vbo_);
vbo_ = 0;
}
if (vao_ != 0) { if (vao_ != 0) {
glDeleteVertexArrays(1, &vao_); glDeleteVertexArrays(1, &vao_);
vao_ = 0; vao_ = 0;
@ -78,12 +82,22 @@ void GLSpriteBatch::begin(const glm::mat4 &viewProjection) {
drawCallCount_ = 0; drawCallCount_ = 0;
// 保存 viewProjection 矩阵供后续使用 // 保存 viewProjection 矩阵供后续使用
viewProjection_ = viewProjection; viewProjection_ = viewProjection;
// 绑定 VAO 和缓冲区
glBindVertexArray(vao_);
vbo_.bind();
ebo_.bind();
} }
void GLSpriteBatch::end() { void GLSpriteBatch::end() {
if (batch_.getSpriteCount() > 0) { if (batch_.getSpriteCount() > 0) {
flush(); flush();
} }
// 解绑缓冲区
vbo_.unbind();
ebo_.unbind();
glBindVertexArray(0);
} }
void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) { void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) {
@ -154,13 +168,12 @@ void GLSpriteBatch::submitBatch() {
shader_->setFloat("u_opacity", 1.0f); shader_->setFloat("u_opacity", 1.0f);
} }
// 上传顶点数据 // 上传顶点数据 - 使用 orphaning 策略优化动态缓冲区
glBindBuffer(GL_ARRAY_BUFFER, vbo_); // 通过传入 nullptr 进行 orphaning告诉驱动器可以丢弃旧缓冲区并分配新内存
glBufferSubData(GL_ARRAY_BUFFER, 0, // 这样可以避免 GPU 等待,提高性能
batch_.getVertices().size() * sizeof(SpriteVertex), size_t vertexDataSize = batch_.getVertices().size() * sizeof(SpriteVertex);
batch_.getVertices().data()); vbo_.setData(nullptr, vertexDataSize); // orphaning
vbo_.updateData(batch_.getVertices().data(), 0, vertexDataSize);
glBindVertexArray(vao_);
// 绘制 // 绘制
currentTexture_->bind(0); currentTexture_->bind(0);

View File

@ -0,0 +1,72 @@
#include <extra2d/graphics/batch/shape_batch.h>
namespace extra2d {
// ============================================================================
// ShapeBatch 基础实现(后端无关部分)
// ============================================================================
// 这里可以添加后端无关的工具函数
// 例如:计算圆形的顶点、三角化多边形等
// 计算圆形顶点
void calculateCircleVertices(std::vector<Vec2>& 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<float>(i) / static_cast<float>(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<float>(i) / static_cast<float>(segments);
outVertices.emplace_back(
center.x + radius * cosf(angle),
center.y + radius * sinf(angle)
);
}
}
}
// 计算矩形顶点
void calculateRectVertices(std::vector<Vec2>& 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<uint16_t>& 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

44
docs/README.md Normal file
View File

@ -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. **类型安全** - 使用强类型枚举和结构体