From d1a61ab23525cbd4cd9c89c569421f55f053cea6 Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Wed, 11 Feb 2026 22:30:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B8=B2=E6=9F=93=E7=B3=BB=E7=BB=9F):=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=B8=B2=E6=9F=93=E5=91=BD=E4=BB=A4=E6=89=B9?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=92=8C=E8=87=AA=E5=8A=A8=E6=8E=92=E5=BA=8F?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor(资源管理): 重构资源管理器支持异步加载和纹理压缩 perf(对象池): 新增对象池实现优化小对象分配性能 docs(文档): 更新资源管理文档说明异步加载和纹理压缩功能 style(代码): 统一渲染命令数据结构命名规范 --- .../extra2d/graphics/opengl/gl_sprite_batch.h | 24 +- .../include/extra2d/graphics/render_command.h | 225 +++++++++++---- .../include/extra2d/graphics/texture_atlas.h | 184 ++++++++++++ .../extra2d/resource/resource_manager.h | 80 +++++- Extra2D/include/extra2d/scene/node.h | 12 + Extra2D/include/extra2d/utils/object_pool.h | 264 ++++++++++++++++++ .../src/graphics/opengl/gl_sprite_batch.cpp | 133 ++++++--- Extra2D/src/graphics/render_command.cpp | 169 +++++++++++ Extra2D/src/graphics/texture_atlas.cpp | 264 ++++++++++++++++++ Extra2D/src/resource/resource_manager.cpp | 176 +++++++++++- Extra2D/src/scene/node.cpp | 25 ++ Extra2D/src/scene/shape_node.cpp | 24 +- Extra2D/src/scene/sprite.cpp | 4 +- Extra2D/src/utils/object_pool.cpp | 11 + docs/API_Tutorial/04_Resource_Management.md | 187 ++++++++++++- 15 files changed, 1658 insertions(+), 124 deletions(-) create mode 100644 Extra2D/include/extra2d/graphics/texture_atlas.h create mode 100644 Extra2D/include/extra2d/utils/object_pool.h create mode 100644 Extra2D/src/graphics/render_command.cpp create mode 100644 Extra2D/src/graphics/texture_atlas.cpp create mode 100644 Extra2D/src/utils/object_pool.cpp diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h b/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h index 8a8ed90..c925a09 100644 --- a/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h +++ b/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h @@ -7,19 +7,22 @@ #include #include #include +#include #include namespace extra2d { // ============================================================================ -// OpenGL 精灵批渲染器 +// OpenGL 精灵批渲染器 - 优化版本 // ============================================================================ class GLSpriteBatch { public: static constexpr size_t MAX_SPRITES = 10000; static constexpr size_t VERTICES_PER_SPRITE = 4; static constexpr size_t INDICES_PER_SPRITE = 6; + static constexpr size_t MAX_VERTICES = MAX_SPRITES * VERTICES_PER_SPRITE; + static constexpr size_t MAX_INDICES = MAX_SPRITES * INDICES_PER_SPRITE; struct Vertex { glm::vec2 position; @@ -48,9 +51,19 @@ public: void draw(const Texture &texture, const SpriteData &data); void end(); + // 批量绘制接口 - 用于自动批处理 + void drawBatch(const Texture& texture, const std::vector& sprites); + + // 立即绘制(不缓存) + void drawImmediate(const Texture& texture, const SpriteData& data); + // 统计 uint32_t getDrawCallCount() const { return drawCallCount_; } uint32_t getSpriteCount() const { return spriteCount_; } + uint32_t getBatchCount() const { return batchCount_; } + + // 检查是否需要刷新 + bool needsFlush(const Texture& texture, bool isSDF) const; private: GLuint vao_; @@ -58,8 +71,9 @@ private: GLuint ibo_; GLShader shader_; - std::vector vertices_; - std::vector indices_; + // 使用固定大小数组减少内存分配 + std::array vertexBuffer_; + size_t vertexCount_; const Texture *currentTexture_; bool currentIsSDF_; @@ -67,9 +81,13 @@ private: uint32_t drawCallCount_; uint32_t spriteCount_; + uint32_t batchCount_; void flush(); void setupShader(); + + // 添加顶点到缓冲区 + void addVertices(const SpriteData& data); }; } // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/render_command.h b/Extra2D/include/extra2d/graphics/render_command.h index 283fa60..3caeb04 100644 --- a/Extra2D/include/extra2d/graphics/render_command.h +++ b/Extra2D/include/extra2d/graphics/render_command.h @@ -2,10 +2,11 @@ #include #include -#include #include +#include +#include +#include #include -#include namespace extra2d { @@ -13,110 +14,210 @@ namespace extra2d { class Texture; class FontAtlas; -// ============================================================================ -// 渲染命令类型 -// ============================================================================ -enum class RenderCommandType { - Sprite, - Line, - Rect, - FilledRect, - Circle, - FilledCircle, - Triangle, - FilledTriangle, - Polygon, - FilledPolygon, - Text +/** + * @brief 渲染命令类型枚举 + */ +enum class RenderCommandType : uint8_t { + None = 0, + Sprite, // 精灵绘制 + Line, // 线条绘制 + Rect, // 矩形绘制 + FilledRect, // 填充矩形 + Circle, // 圆形绘制 + FilledCircle, // 填充圆形 + Triangle, // 三角形绘制 + FilledTriangle, // 填充三角形 + Polygon, // 多边形绘制 + FilledPolygon, // 填充多边形 + Text, // 文本绘制 + Custom // 自定义绘制 }; -// ============================================================================ -// 精灵数据 -// ============================================================================ -struct SpriteData { - Ptr texture; +/** + * @brief 精灵渲染命令数据 + */ +struct SpriteCommandData { + const Texture* texture; Rect destRect; Rect srcRect; Color tint; float rotation; Vec2 anchor; + uint32_t sortKey; // 用于自动排序的键值 + + SpriteCommandData() + : texture(nullptr), destRect(), srcRect(), tint(Colors::White), + rotation(0.0f), anchor(0.0f, 0.0f), sortKey(0) {} + SpriteCommandData(const Texture* tex, const Rect& dest, const Rect& src, + const Color& t, float rot, const Vec2& anc, uint32_t key) + : texture(tex), destRect(dest), srcRect(src), tint(t), + rotation(rot), anchor(anc), sortKey(key) {} }; -// ============================================================================ -// 直线数据 -// ============================================================================ -struct LineData { +/** + * @brief 线条渲染命令数据 + */ +struct LineCommandData { Vec2 start; Vec2 end; Color color; float width; + + LineCommandData() : start(), end(), color(Colors::White), width(1.0f) {} + LineCommandData(const Vec2& s, const Vec2& e, const Color& c, float w) + : start(s), end(e), color(c), width(w) {} }; -// ============================================================================ -// 矩形数据 -// ============================================================================ -struct RectData { +/** + * @brief 矩形渲染命令数据 + */ +struct RectCommandData { Rect rect; Color color; float width; + bool filled; + + RectCommandData() : rect(), color(Colors::White), width(1.0f), filled(false) {} + RectCommandData(const Rect& r, const Color& c, float w, bool f) + : rect(r), color(c), width(w), filled(f) {} }; -// ============================================================================ -// 圆形数据 -// ============================================================================ -struct CircleData { +/** + * @brief 圆形渲染命令数据 + */ +struct CircleCommandData { Vec2 center; float radius; Color color; int segments; float width; + bool filled; + + CircleCommandData() : center(), radius(0.0f), color(Colors::White), + segments(32), width(1.0f), filled(false) {} + CircleCommandData(const Vec2& c, float r, const Color& col, int seg, float w, bool f) + : center(c), radius(r), color(col), segments(seg), width(w), filled(f) {} }; -// ============================================================================ -// 三角形数据 -// ============================================================================ -struct TriangleData { - Vec2 p1; - Vec2 p2; - Vec2 p3; +/** + * @brief 三角形渲染命令数据 + */ +struct TriangleCommandData { + Vec2 p1, p2, p3; Color color; float width; + bool filled; + + TriangleCommandData() : p1(), p2(), p3(), color(Colors::White), + width(1.0f), filled(false) {} + TriangleCommandData(const Vec2& a, const Vec2& b, const Vec2& c, const Color& col, float w, bool f) + : p1(a), p2(b), p3(c), color(col), width(w), filled(f) {} }; -// ============================================================================ -// 多边形数据 -// ============================================================================ -struct PolygonData { +/** + * @brief 多边形渲染命令数据 + */ +struct PolygonCommandData { std::vector points; Color color; float width; + bool filled; + + PolygonCommandData() : color(Colors::White), width(1.0f), filled(false) {} + PolygonCommandData(std::vector pts, const Color& col, float w, bool f) + : points(std::move(pts)), color(col), width(w), filled(f) {} }; -// ============================================================================ -// 文字数据 -// ============================================================================ -struct TextData { - Ptr font; +/** + * @brief 文本渲染命令数据 + */ +struct TextCommandData { + const FontAtlas* font; std::string text; Vec2 position; Color color; + + TextCommandData() : font(nullptr), text(), position(), color(Colors::White) {} }; -// ============================================================================ -// 渲染命令 -// ============================================================================ +/** + * @brief 统一渲染命令结构 + * 使用 variant 存储不同类型的命令数据,减少内存分配 + */ struct RenderCommand { RenderCommandType type; - int zOrder; + uint32_t layer; // 渲染层级,用于排序 + uint32_t order; // 提交顺序,保证同层级内稳定排序 + glm::mat4 transform; // 变换矩阵 + + // 使用 variant 存储具体数据 + std::variant< + SpriteCommandData, + LineCommandData, + RectCommandData, + CircleCommandData, + TriangleCommandData, + PolygonCommandData, + TextCommandData + > data; + + RenderCommand() : type(RenderCommandType::None), layer(0), order(0), + transform(1.0f) {} + + // 便捷构造函数 + static RenderCommand makeSprite(const Texture* tex, const Rect& dest, + const Rect& src, const Color& tint, + float rot = 0.0f, const Vec2& anc = Vec2(0, 0), + uint32_t lyr = 0); + static RenderCommand makeLine(const Vec2& s, const Vec2& e, const Color& c, + float w = 1.0f, uint32_t lyr = 0); + static RenderCommand makeRect(const Rect& r, const Color& c, + float w = 1.0f, bool fill = false, uint32_t lyr = 0); +}; - std::variant - data; - - // 用于排序 - bool operator<(const RenderCommand &other) const { - return zOrder < other.zOrder; - } +/** + * @brief 渲染命令缓冲区 + * 用于收集和批量处理渲染命令 + */ +class RenderCommandBuffer { +public: + static constexpr size_t INITIAL_CAPACITY = 1024; + static constexpr size_t MAX_CAPACITY = 65536; + + RenderCommandBuffer(); + ~RenderCommandBuffer(); + + // 添加渲染命令 + void addCommand(const RenderCommand& cmd); + void addCommand(RenderCommand&& cmd); + + // 批量添加(预留空间后使用) + RenderCommand& emplaceCommand(); + + // 排序命令(按纹理、层级等) + void sortCommands(); + + // 清空缓冲区 + void clear(); + + // 获取命令列表 + const std::vector& getCommands() const { return commands_; } + std::vector& getCommands() { return commands_; } + + // 统计 + size_t size() const { return commands_.size(); } + bool empty() const { return commands_.empty(); } + size_t capacity() const { return commands_.capacity(); } + + // 预分配空间 + void reserve(size_t capacity); + +private: + std::vector commands_; + uint32_t nextOrder_; + + // 排序比较函数 + static bool compareCommands(const RenderCommand& a, const RenderCommand& b); }; } // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/texture_atlas.h b/Extra2D/include/extra2d/graphics/texture_atlas.h new file mode 100644 index 0000000..a912040 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/texture_atlas.h @@ -0,0 +1,184 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// 纹理图集 - 自动将小纹理合并到大图集以减少 DrawCall +// ============================================================================ + +/** + * @brief 图集中的单个纹理条目 + */ +struct AtlasEntry { + std::string name; // 原始纹理名称/路径 + Rect uvRect; // 在图集中的 UV 坐标范围 + Vec2 originalSize; // 原始纹理尺寸 + uint32_t padding; // 边距(用于避免纹理 bleeding) + + AtlasEntry() : uvRect(), originalSize(), padding(2) {} +}; + +/** + * @brief 纹理图集页面 + * 当单个图集放不下时,创建多个页面 + */ +class TextureAtlasPage { +public: + static constexpr int DEFAULT_SIZE = 2048; + static constexpr int MAX_SIZE = 4096; + static constexpr int MIN_TEXTURE_SIZE = 32; // 小于此大小的纹理才考虑合并 + static constexpr int PADDING = 2; // 纹理间边距 + + TextureAtlasPage(int width = DEFAULT_SIZE, int height = DEFAULT_SIZE); + ~TextureAtlasPage(); + + // 尝试添加纹理到图集 + // 返回是否成功,如果成功则输出 uvRect + bool tryAddTexture(const std::string& name, int texWidth, int texHeight, + const uint8_t* pixels, Rect& outUvRect); + + // 获取图集纹理 + Ptr getTexture() const { return texture_; } + + // 获取条目 + const AtlasEntry* getEntry(const std::string& name) const; + + // 获取使用率 + float getUsageRatio() const; + + // 获取尺寸 + int getWidth() const { return width_; } + int getHeight() const { return height_; } + + // 是否已满 + bool isFull() const { return isFull_; } + +private: + int width_, height_; + Ptr texture_; + std::unordered_map entries_; + + // 矩形打包数据 + struct PackNode { + int x, y, width, height; + bool used; + std::unique_ptr left; + std::unique_ptr right; + + PackNode(int x_, int y_, int w, int h) + : x(x_), y(y_), width(w), height(h), used(false) {} + }; + + std::unique_ptr root_; + bool isFull_; + int usedArea_; + + // 递归插入 + PackNode* insert(PackNode* node, int width, int height); + void writePixels(int x, int y, int w, int h, const uint8_t* pixels); +}; + +/** + * @brief 纹理图集管理器 + * 自动管理多个图集页面,提供统一的纹理查询接口 + */ +class TextureAtlas { +public: + TextureAtlas(); + ~TextureAtlas(); + + // 初始化 + void init(int pageSize = TextureAtlasPage::DEFAULT_SIZE); + + // 添加纹理到图集 + // 如果纹理太大,返回 false,应该作为独立纹理加载 + bool addTexture(const std::string& name, int width, int height, + const uint8_t* pixels); + + // 查询纹理是否在图集中 + bool contains(const std::string& name) const; + + // 获取纹理在图集中的信息 + // 返回图集纹理和 UV 坐标 + const Texture* getAtlasTexture(const std::string& name) const; + Rect getUVRect(const std::string& name) const; + + // 获取原始纹理尺寸 + Vec2 getOriginalSize(const std::string& name) const; + + // 获取所有图集页面 + const std::vector>& getPages() const { return pages_; } + + // 获取总使用率 + float getTotalUsageRatio() const; + + // 清空所有图集 + void clear(); + + // 设置是否启用自动图集 + void setEnabled(bool enabled) { enabled_ = enabled; } + bool isEnabled() const { return enabled_; } + + // 设置纹理大小阈值(小于此大小的纹理才进入图集) + void setSizeThreshold(int threshold) { sizeThreshold_ = threshold; } + int getSizeThreshold() const { return sizeThreshold_; } + +private: + std::vector> pages_; + std::unordered_map entryToPage_; + + int pageSize_; + int sizeThreshold_; + bool enabled_; + bool initialized_; +}; + +/** + * @brief 全局图集管理器(单例) + */ +class TextureAtlasManager { +public: + static TextureAtlasManager& getInstance(); + + // 获取主图集 + TextureAtlas& getAtlas() { return atlas_; } + + // 快捷方法 + bool addTexture(const std::string& name, int width, int height, + const uint8_t* pixels) { + return atlas_.addTexture(name, width, height, pixels); + } + + bool contains(const std::string& name) const { + return atlas_.contains(name); + } + + const Texture* getAtlasTexture(const std::string& name) const { + return atlas_.getAtlasTexture(name); + } + + Rect getUVRect(const std::string& name) const { + return atlas_.getUVRect(name); + } + +private: + TextureAtlasManager() = default; + ~TextureAtlasManager() = default; + + TextureAtlasManager(const TextureAtlasManager&) = delete; + TextureAtlasManager& operator=(const TextureAtlasManager&) = delete; + + TextureAtlas atlas_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/resource/resource_manager.h b/Extra2D/include/extra2d/resource/resource_manager.h index 11cf8f9..bbd7a02 100644 --- a/Extra2D/include/extra2d/resource/resource_manager.h +++ b/Extra2D/include/extra2d/resource/resource_manager.h @@ -5,15 +5,37 @@ #include #include #include +#include +#include #include #include #include +#include +#include +#include namespace extra2d { // ============================================================================ // 资源管理器 - 统一管理纹理、字体、音效等资源 +// 支持异步加载和纹理压缩 // ============================================================================ + +// 纹理格式枚举 +enum class TextureFormat { + Auto = 0, // 自动选择最佳格式 + RGBA8, // 32位 RGBA + RGB8, // 24位 RGB + DXT1, // BC1/DXT1 压缩(1 bit alpha) + DXT5, // BC3/DXT5 压缩(完整 alpha) + ETC2, // ETC2 压缩(移动平台) + ASTC4x4, // ASTC 4x4 压缩(高质量) + ASTC8x8, // ASTC 8x8 压缩(高压缩率) +}; + +// 异步加载回调类型 +using TextureLoadCallback = std::function)>; + class ResourceManager { public: // ------------------------------------------------------------------------ @@ -22,11 +44,23 @@ public: static ResourceManager &getInstance(); // ------------------------------------------------------------------------ - // 纹理资源 + // 纹理资源 - 同步加载 // ------------------------------------------------------------------------ /// 加载纹理(带缓存) Ptr loadTexture(const std::string &filepath); + + /// 加载纹理(指定是否异步) + Ptr loadTexture(const std::string &filepath, bool async); + + /// 加载纹理(完整参数:异步 + 压缩格式) + Ptr loadTexture(const std::string &filepath, bool async, TextureFormat format); + + /// 异步加载纹理(带回调) + void loadTextureAsync(const std::string &filepath, TextureLoadCallback callback); + + /// 异步加载纹理(指定格式 + 回调) + void loadTextureAsync(const std::string &filepath, TextureFormat format, TextureLoadCallback callback); /// 加载纹理并生成Alpha遮罩(用于不规则形状图片) Ptr loadTextureWithAlphaMask(const std::string &filepath); @@ -107,14 +141,41 @@ public: size_t getFontCacheSize() const; size_t getSoundCacheSize() const; + // ------------------------------------------------------------------------ + // 异步加载控制 + // ------------------------------------------------------------------------ + + /// 初始化异步加载系统(可选,自动在首次异步加载时初始化) + void initAsyncLoader(); + + /// 关闭异步加载系统 + void shutdownAsyncLoader(); + + /// 等待所有异步加载完成 + void waitForAsyncLoads(); + + /// 检查是否有正在进行的异步加载 + bool hasPendingAsyncLoads() const; + ResourceManager(); ~ResourceManager(); ResourceManager(const ResourceManager &) = delete; ResourceManager &operator=(const ResourceManager &) = delete; +private: // 生成字体缓存key std::string makeFontKey(const std::string &filepath, int fontSize, bool useSDF) const; + + // 内部加载实现 + Ptr loadTextureInternal(const std::string &filepath, TextureFormat format); + + // 选择最佳纹理格式 + TextureFormat selectBestFormat(TextureFormat requested) const; + + // 压缩纹理数据 + std::vector compressTexture(const uint8_t* data, int width, int height, + int channels, TextureFormat format); // 互斥锁保护缓存 mutable std::mutex textureMutex_; @@ -125,6 +186,23 @@ public: std::unordered_map> textureCache_; std::unordered_map> fontCache_; std::unordered_map> soundCache_; + + // 异步加载相关 + struct AsyncLoadTask { + std::string filepath; + TextureFormat format; + TextureLoadCallback callback; + std::promise> promise; + }; + + std::queue asyncTaskQueue_; + std::mutex asyncQueueMutex_; + std::condition_variable asyncCondition_; + std::unique_ptr asyncThread_; + std::atomic asyncRunning_{false}; + std::atomic pendingAsyncLoads_{0}; + + void asyncLoadLoop(); }; } // namespace extra2d diff --git a/Extra2D/include/extra2d/scene/node.h b/Extra2D/include/extra2d/scene/node.h index b09cf79..4a29013 100644 --- a/Extra2D/include/extra2d/scene/node.h +++ b/Extra2D/include/extra2d/scene/node.h @@ -93,6 +93,18 @@ public: */ void markTransformDirty(); + /** + * @brief 批量更新变换矩阵 + * 在渲染前统一计算所有脏节点的变换矩阵,避免逐节点计算时的重复递归 + */ + void batchUpdateTransforms(); + + /** + * @brief 获取变换脏标记状态 + */ + bool isTransformDirty() const { return transformDirty_; } + bool isWorldTransformDirty() const { return worldTransformDirty_; } + // ------------------------------------------------------------------------ // 名称和标签 // ------------------------------------------------------------------------ diff --git a/Extra2D/include/extra2d/utils/object_pool.h b/Extra2D/include/extra2d/utils/object_pool.h new file mode 100644 index 0000000..e3bd3db --- /dev/null +++ b/Extra2D/include/extra2d/utils/object_pool.h @@ -0,0 +1,264 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// 对象池 - 用于高效分配和回收小对象 +// 减少频繁的内存分配/释放开销 +// ============================================================================ + +template +class ObjectPool { +public: + static_assert(std::is_default_constructible_v, "T must be default constructible"); + static_assert(std::is_destructible_v, "T must be destructible"); + + ObjectPool() = default; + ~ObjectPool() { clear(); } + + // 禁止拷贝 + ObjectPool(const ObjectPool&) = delete; + ObjectPool& operator=(const ObjectPool&) = delete; + + // 允许移动 + ObjectPool(ObjectPool&& other) noexcept { + std::lock_guard lock(other.mutex_); + blocks_ = std::move(other.blocks_); + freeList_ = std::move(other.freeList_); + allocatedCount_ = other.allocatedCount_.load(); + other.allocatedCount_ = 0; + } + + ObjectPool& operator=(ObjectPool&& other) noexcept { + if (this != &other) { + std::lock_guard lock1(mutex_); + std::lock_guard lock2(other.mutex_); + clear(); + blocks_ = std::move(other.blocks_); + freeList_ = std::move(other.freeList_); + allocatedCount_ = other.allocatedCount_.load(); + other.allocatedCount_ = 0; + } + return *this; + } + + /** + * @brief 分配一个对象 + * @return 指向分配的对象的指针 + */ + T* allocate() { + std::lock_guard lock(mutex_); + + if (freeList_.empty()) { + grow(); + } + + T* obj = freeList_.back(); + freeList_.pop_back(); + + // 在原地构造对象 + new (obj) T(); + + allocatedCount_++; + return obj; + } + + /** + * @brief 分配并构造一个对象(带参数) + */ + template + T* allocate(Args&&... args) { + std::lock_guard lock(mutex_); + + if (freeList_.empty()) { + grow(); + } + + T* obj = freeList_.back(); + freeList_.pop_back(); + + // 在原地构造对象 + new (obj) T(std::forward(args)...); + + allocatedCount_++; + return obj; + } + + /** + * @brief 回收一个对象 + * @param obj 要回收的对象指针 + */ + void deallocate(T* obj) { + if (obj == nullptr) { + return; + } + + // 调用析构函数 + obj->~T(); + + std::lock_guard lock(mutex_); + freeList_.push_back(obj); + allocatedCount_--; + } + + /** + * @brief 获取当前已分配的对象数量 + */ + size_t allocatedCount() const { + return allocatedCount_.load(); + } + + /** + * @brief 获取池中可用的对象数量 + */ + size_t availableCount() const { + std::lock_guard lock(mutex_); + return freeList_.size(); + } + + /** + * @brief 获取池中总的对象容量 + */ + size_t capacity() const { + std::lock_guard lock(mutex_); + return blocks_.size() * BlockSize; + } + + /** + * @brief 清空所有内存块 + */ + void clear() { + std::lock_guard lock(mutex_); + + // 释放所有内存块 + for (auto& block : blocks_) { + ::operator delete[](block, std::align_val_t(alignof(T))); + } + blocks_.clear(); + freeList_.clear(); + allocatedCount_ = 0; + } + +private: + /** + * @brief 扩展池容量 + */ + void grow() { + // 分配新的内存块 + T* block = static_cast(::operator new[](sizeof(T) * BlockSize, std::align_val_t(alignof(T)))); + + blocks_.push_back(block); + + // 将新块中的对象添加到空闲列表 + for (size_t i = 0; i < BlockSize; ++i) { + freeList_.push_back(&block[i]); + } + } + + mutable std::mutex mutex_; + std::vector blocks_; // 内存块列表 + std::vector freeList_; // 空闲对象列表 + std::atomic allocatedCount_{0}; +}; + +// ============================================================================ +// 智能指针支持的内存池分配器 +// ============================================================================ + +template +class PooledAllocator { +public: + using PoolType = ObjectPool; + + PooledAllocator() : pool_(std::make_shared()) {} + explicit PooledAllocator(std::shared_ptr pool) : pool_(pool) {} + + /** + * @brief 创建一个使用内存池的对象 + */ + template + Ptr makeShared(Args&&... args) { + T* obj = pool_->allocate(std::forward(args)...); + return Ptr(obj, Deleter{pool_}); + } + + /** + * @brief 获取底层内存池 + */ + std::shared_ptr getPool() const { + return pool_; + } + +private: + struct Deleter { + std::shared_ptr pool; + + void operator()(T* obj) const { + if (pool) { + pool->deallocate(obj); + } else { + delete obj; + } + } + }; + + std::shared_ptr pool_; +}; + +// ============================================================================ +// 全局内存池管理器 +// ============================================================================ + +class ObjectPoolManager { +public: + static ObjectPoolManager& getInstance(); + + /** + * @brief 获取指定类型的内存池 + */ + template + std::shared_ptr> getPool() { + static std::shared_ptr> pool = + std::make_shared>(); + return pool; + } + + /** + * @brief 创建使用内存池的对象 + */ + template + Ptr makePooled(Args&&... args) { + auto pool = getPool(); + T* obj = pool->allocate(std::forward(args)...); + return Ptr(obj, [pool](T* p) { pool->deallocate(p); }); + } + +private: + ObjectPoolManager() = default; + ~ObjectPoolManager() = default; + ObjectPoolManager(const ObjectPoolManager&) = delete; + ObjectPoolManager& operator=(const ObjectPoolManager&) = delete; +}; + +// ============================================================================ +// 内存池宏定义(便于使用) +// ============================================================================ + +#define E2D_DECLARE_POOL(T, BlockSize) \ + static extra2d::ObjectPool& getPool() { \ + static extra2d::ObjectPool pool; \ + return pool; \ + } + +#define E2D_MAKE_POOSED(T, ...) \ + extra2d::ObjectPoolManager::getInstance().makePooled(__VA_ARGS__) + +} // namespace extra2d diff --git a/Extra2D/src/graphics/opengl/gl_sprite_batch.cpp b/Extra2D/src/graphics/opengl/gl_sprite_batch.cpp index 3ba11a9..ef9cc86 100644 --- a/Extra2D/src/graphics/opengl/gl_sprite_batch.cpp +++ b/Extra2D/src/graphics/opengl/gl_sprite_batch.cpp @@ -54,9 +54,7 @@ void main() { GLSpriteBatch::GLSpriteBatch() : vao_(0), vbo_(0), ibo_(0), currentTexture_(nullptr), currentIsSDF_(false), - drawCallCount_(0), spriteCount_(0) { - vertices_.reserve(MAX_SPRITES * VERTICES_PER_SPRITE); - indices_.reserve(MAX_SPRITES * INDICES_PER_SPRITE); + vertexCount_(0), drawCallCount_(0), spriteCount_(0), batchCount_(0) { } GLSpriteBatch::~GLSpriteBatch() { shutdown(); } @@ -76,10 +74,9 @@ bool GLSpriteBatch::init() { glBindVertexArray(vao_); - // 设置 VBO + // 设置 VBO - 使用动态绘制模式 glBindBuffer(GL_ARRAY_BUFFER, vbo_); - glBufferData(GL_ARRAY_BUFFER, - MAX_SPRITES * VERTICES_PER_SPRITE * sizeof(Vertex), nullptr, + glBufferData(GL_ARRAY_BUFFER, MAX_VERTICES * sizeof(Vertex), nullptr, GL_DYNAMIC_DRAW); // 设置顶点属性 @@ -95,9 +92,9 @@ bool GLSpriteBatch::init() { glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, color)); - // 生成索引缓冲区 + // 生成索引缓冲区 - 静态,只需创建一次 std::vector indices; - indices.reserve(MAX_SPRITES * INDICES_PER_SPRITE); + indices.reserve(MAX_INDICES); for (size_t i = 0; i < MAX_SPRITES; ++i) { GLuint base = static_cast(i * VERTICES_PER_SPRITE); indices.push_back(base + 0); @@ -114,6 +111,7 @@ bool GLSpriteBatch::init() { glBindVertexArray(0); + E2D_LOG_INFO("GLSpriteBatch initialized with capacity for {} sprites", MAX_SPRITES); return true; } @@ -134,24 +132,26 @@ void GLSpriteBatch::shutdown() { void GLSpriteBatch::begin(const glm::mat4 &viewProjection) { viewProjection_ = viewProjection; - vertices_.clear(); + vertexCount_ = 0; currentTexture_ = nullptr; currentIsSDF_ = false; drawCallCount_ = 0; spriteCount_ = 0; + batchCount_ = 0; } -void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) { - // 如果纹理改变或缓冲区已满,先 flush - if (currentTexture_ != nullptr && - (currentTexture_ != &texture || currentIsSDF_ != data.isSDF || - vertices_.size() >= MAX_SPRITES * VERTICES_PER_SPRITE)) { - flush(); +bool GLSpriteBatch::needsFlush(const Texture& texture, bool isSDF) const { + if (currentTexture_ == nullptr) { + return false; } + + // 检查是否需要刷新:纹理改变、SDF 状态改变或缓冲区已满 + return (currentTexture_ != &texture) || + (currentIsSDF_ != isSDF) || + (vertexCount_ + VERTICES_PER_SPRITE > MAX_VERTICES); +} - currentTexture_ = &texture; - currentIsSDF_ = data.isSDF; - +void GLSpriteBatch::addVertices(const SpriteData& data) { // 计算变换后的顶点位置 glm::vec2 anchorOffset(data.size.x * data.anchor.x, data.size.y * data.anchor.y); @@ -172,32 +172,87 @@ void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) { // v0(左上) -- v1(右上) // | | // v3(左下) -- v2(右下) - Vertex v0{transform(0, 0), glm::vec2(data.texCoordMin.x, data.texCoordMin.y), - color}; - Vertex v1{transform(data.size.x, 0), - glm::vec2(data.texCoordMax.x, data.texCoordMin.y), color}; - Vertex v2{transform(data.size.x, data.size.y), - glm::vec2(data.texCoordMax.x, data.texCoordMax.y), color}; - Vertex v3{transform(0, data.size.y), - glm::vec2(data.texCoordMin.x, data.texCoordMax.y), color}; + Vertex v0{transform(0, 0), glm::vec2(data.texCoordMin.x, data.texCoordMin.y), color}; + Vertex v1{transform(data.size.x, 0), glm::vec2(data.texCoordMax.x, data.texCoordMin.y), color}; + Vertex v2{transform(data.size.x, data.size.y), glm::vec2(data.texCoordMax.x, data.texCoordMax.y), color}; + Vertex v3{transform(0, data.size.y), glm::vec2(data.texCoordMin.x, data.texCoordMax.y), color}; - vertices_.push_back(v0); - vertices_.push_back(v1); - vertices_.push_back(v2); - vertices_.push_back(v3); + vertexBuffer_[vertexCount_++] = v0; + vertexBuffer_[vertexCount_++] = v1; + vertexBuffer_[vertexCount_++] = v2; + vertexBuffer_[vertexCount_++] = v3; +} +void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) { + // 如果需要刷新,先提交当前批次 + if (needsFlush(texture, data.isSDF)) { + flush(); + } + + currentTexture_ = &texture; + currentIsSDF_ = data.isSDF; + + addVertices(data); spriteCount_++; } +void GLSpriteBatch::drawBatch(const Texture& texture, const std::vector& sprites) { + if (sprites.empty()) { + return; + } + + // 如果当前有未提交的批次且纹理不同,先刷新 + if (currentTexture_ != nullptr && currentTexture_ != &texture) { + flush(); + } + + currentTexture_ = &texture; + currentIsSDF_ = sprites[0].isSDF; // 假设批量中的精灵 SDF 状态一致 + + // 分批处理,避免超过缓冲区大小 + size_t index = 0; + while (index < sprites.size()) { + size_t remainingSpace = (MAX_VERTICES - vertexCount_) / VERTICES_PER_SPRITE; + size_t batchSize = std::min(sprites.size() - index, remainingSpace); + + for (size_t i = 0; i < batchSize; ++i) { + addVertices(sprites[index + i]); + spriteCount_++; + } + + index += batchSize; + + // 如果还有更多精灵,刷新当前批次 + if (index < sprites.size()) { + flush(); + } + } + + batchCount_++; +} + +void GLSpriteBatch::drawImmediate(const Texture& texture, const SpriteData& data) { + // 立即绘制,不缓存 - 用于需要立即显示的情况 + flush(); // 先提交当前批次 + + currentTexture_ = &texture; + currentIsSDF_ = data.isSDF; + addVertices(data); + spriteCount_++; + + flush(); // 立即提交 +} + void GLSpriteBatch::end() { - if (!vertices_.empty()) { + if (vertexCount_ > 0) { flush(); } } void GLSpriteBatch::flush() { - if (vertices_.empty() || currentTexture_ == nullptr) + if (vertexCount_ == 0 || currentTexture_ == nullptr) { return; + } // 绑定纹理 GLuint texID = static_cast( @@ -213,19 +268,23 @@ void GLSpriteBatch::flush() { shader_.setFloat("uSdfOnEdge", 128.0f / 255.0f); shader_.setFloat("uSdfScale", 255.0f / 64.0f); - // 更新 VBO 数据 + // 更新 VBO 数据 - 只更新实际使用的部分 glBindBuffer(GL_ARRAY_BUFFER, vbo_); - glBufferSubData(GL_ARRAY_BUFFER, 0, vertices_.size() * sizeof(Vertex), - vertices_.data()); + glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount_ * sizeof(Vertex), vertexBuffer_.data()); // 绘制 glBindVertexArray(vao_); GLsizei indexCount = static_cast( - vertices_.size() / VERTICES_PER_SPRITE * INDICES_PER_SPRITE); + (vertexCount_ / VERTICES_PER_SPRITE) * INDICES_PER_SPRITE); glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, nullptr); drawCallCount_++; - vertices_.clear(); + batchCount_++; + + // 重置状态 + vertexCount_ = 0; + currentTexture_ = nullptr; + currentIsSDF_ = false; } } // namespace extra2d diff --git a/Extra2D/src/graphics/render_command.cpp b/Extra2D/src/graphics/render_command.cpp new file mode 100644 index 0000000..19980cc --- /dev/null +++ b/Extra2D/src/graphics/render_command.cpp @@ -0,0 +1,169 @@ +#include +#include + +namespace extra2d { + +// ============================================================================ +// RenderCommand 便捷构造函数 +// ============================================================================ + +RenderCommand RenderCommand::makeSprite(const Texture* tex, const Rect& dest, + const Rect& src, const Color& tint, + float rot, const Vec2& anc, + uint32_t lyr) { + RenderCommand cmd; + cmd.type = RenderCommandType::Sprite; + cmd.layer = lyr; + cmd.transform = glm::mat4(1.0f); + + SpriteCommandData data; + data.texture = tex; + data.destRect = dest; + data.srcRect = src; + data.tint = tint; + data.rotation = rot; + data.anchor = anc; + // 生成排序键:纹理指针的高位 + 层级的低位 + data.sortKey = (reinterpret_cast(tex) >> 4) & 0xFFFFFFF0; + data.sortKey |= (lyr & 0xF); + + cmd.data = data; + return cmd; +} + +RenderCommand RenderCommand::makeLine(const Vec2& s, const Vec2& e, const Color& c, + float w, uint32_t lyr) { + RenderCommand cmd; + cmd.type = RenderCommandType::Line; + cmd.layer = lyr; + cmd.transform = glm::mat4(1.0f); + + LineCommandData data; + data.start = s; + data.end = e; + data.color = c; + data.width = w; + + cmd.data = data; + return cmd; +} + +RenderCommand RenderCommand::makeRect(const Rect& r, const Color& c, + float w, bool fill, uint32_t lyr) { + RenderCommand cmd; + cmd.type = fill ? RenderCommandType::FilledRect : RenderCommandType::Rect; + cmd.layer = lyr; + cmd.transform = glm::mat4(1.0f); + + RectCommandData data; + data.rect = r; + data.color = c; + data.width = w; + data.filled = fill; + + cmd.data = data; + return cmd; +} + +// ============================================================================ +// RenderCommandBuffer 实现 +// ============================================================================ + +RenderCommandBuffer::RenderCommandBuffer() : nextOrder_(0) { + commands_.reserve(INITIAL_CAPACITY); +} + +RenderCommandBuffer::~RenderCommandBuffer() = default; + +void RenderCommandBuffer::addCommand(const RenderCommand& cmd) { + if (commands_.size() >= MAX_CAPACITY) { + // 缓冲区已满,可能需要立即刷新 + return; + } + + RenderCommand copy = cmd; + copy.order = nextOrder_++; + commands_.push_back(std::move(copy)); +} + +void RenderCommandBuffer::addCommand(RenderCommand&& cmd) { + if (commands_.size() >= MAX_CAPACITY) { + return; + } + + cmd.order = nextOrder_++; + commands_.push_back(std::move(cmd)); +} + +RenderCommand& RenderCommandBuffer::emplaceCommand() { + if (commands_.size() >= MAX_CAPACITY) { + // 如果已满,返回一个虚拟命令(不应该发生) + static RenderCommand dummy; + return dummy; + } + + commands_.emplace_back(); + commands_.back().order = nextOrder_++; + return commands_.back(); +} + +void RenderCommandBuffer::sortCommands() { + // 按以下优先级排序: + // 1. 层级 (layer) - 低层级先渲染 + // 2. 命令类型 - 精灵类命令优先批处理 + // 3. 纹理/材质 - 相同纹理的精灵连续渲染 + // 4. 提交顺序 - 保证稳定性 + + std::sort(commands_.begin(), commands_.end(), compareCommands); +} + +void RenderCommandBuffer::clear() { + commands_.clear(); + nextOrder_ = 0; +} + +void RenderCommandBuffer::reserve(size_t capacity) { + if (capacity <= MAX_CAPACITY) { + commands_.reserve(capacity); + } +} + +bool RenderCommandBuffer::compareCommands(const RenderCommand& a, const RenderCommand& b) { + // 首先按层级排序 + if (a.layer != b.layer) { + return a.layer < b.layer; + } + + // 然后按类型排序(精灵类命令放在一起以便批处理) + if (a.type != b.type) { + // 精灵和文本命令优先(需要纹理) + bool aIsSprite = (a.type == RenderCommandType::Sprite || + a.type == RenderCommandType::Text); + bool bIsSprite = (b.type == RenderCommandType::Sprite || + b.type == RenderCommandType::Text); + + if (aIsSprite != bIsSprite) { + return aIsSprite > bIsSprite; // 精灵类命令在前 + } + + return static_cast(a.type) < static_cast(b.type); + } + + // 对于精灵命令,按纹理排序 + if (a.type == RenderCommandType::Sprite && b.type == RenderCommandType::Sprite) { + const auto& dataA = std::get(a.data); + const auto& dataB = std::get(b.data); + if (dataA.texture != dataB.texture) { + return dataA.texture < dataB.texture; + } + // 相同纹理时按 sortKey 排序 + if (dataA.sortKey != dataB.sortKey) { + return dataA.sortKey < dataB.sortKey; + } + } + + // 最后按提交顺序排序(保证稳定性) + return a.order < b.order; +} + +} // namespace extra2d diff --git a/Extra2D/src/graphics/texture_atlas.cpp b/Extra2D/src/graphics/texture_atlas.cpp new file mode 100644 index 0000000..ba44b81 --- /dev/null +++ b/Extra2D/src/graphics/texture_atlas.cpp @@ -0,0 +1,264 @@ +#include +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// TextureAtlasPage 实现 +// ============================================================================ + +TextureAtlasPage::TextureAtlasPage(int width, int height) + : width_(width), height_(height), isFull_(false), usedArea_(0) { + // 创建空白纹理 + std::vector emptyData(width * height * 4, 0); + texture_ = makePtr(width, height, emptyData.data(), 4); + + // 初始化矩形打包根节点 + root_ = std::make_unique(0, 0, width, height); + + E2D_LOG_INFO("Created texture atlas page: {}x{}", width, height); +} + +TextureAtlasPage::~TextureAtlasPage() = default; + +bool TextureAtlasPage::tryAddTexture(const std::string& name, int texWidth, int texHeight, + const uint8_t* pixels, Rect& outUvRect) { + if (isFull_) { + return false; + } + + // 添加边距 + int paddedWidth = texWidth + 2 * PADDING; + int paddedHeight = texHeight + 2 * PADDING; + + // 如果纹理太大,无法放入 + if (paddedWidth > width_ || paddedHeight > height_) { + return false; + } + + // 尝试插入 + PackNode* node = insert(root_.get(), paddedWidth, paddedHeight); + if (node == nullptr) { + // 无法放入,标记为满 + isFull_ = true; + return false; + } + + // 写入像素数据(跳过边距区域) + writePixels(node->x + PADDING, node->y + PADDING, texWidth, texHeight, pixels); + + // 创建条目 + AtlasEntry entry; + entry.name = name; + entry.originalSize = Vec2(static_cast(texWidth), static_cast(texHeight)); + entry.padding = PADDING; + + // 计算 UV 坐标(考虑边距) + float u1 = static_cast(node->x + PADDING) / width_; + float v1 = static_cast(node->y + PADDING) / height_; + float u2 = static_cast(node->x + PADDING + texWidth) / width_; + float v2 = static_cast(node->y + PADDING + texHeight) / height_; + + entry.uvRect = Rect(u1, v1, u2 - u1, v2 - v1); + outUvRect = entry.uvRect; + + entries_[name] = std::move(entry); + usedArea_ += paddedWidth * paddedHeight; + + E2D_LOG_DEBUG("Added texture '{}' to atlas: {}x{} at ({}, {})", + name, texWidth, texHeight, node->x, node->y); + + return true; +} + +TextureAtlasPage::PackNode* TextureAtlasPage::insert(PackNode* node, int width, int height) { + if (node == nullptr) { + return nullptr; + } + + // 如果节点已被使用,尝试子节点 + if (node->used) { + PackNode* result = insert(node->left.get(), width, height); + if (result != nullptr) { + return result; + } + return insert(node->right.get(), width, height); + } + + // 检查是否适合 + if (width > node->width || height > node->height) { + return nullptr; + } + + // 如果刚好合适,使用此节点 + if (width == node->width && height == node->height) { + node->used = true; + return node; + } + + // 需要分割节点 + int dw = node->width - width; + int dh = node->height - height; + + if (dw > dh) { + // 水平分割 + node->left = std::make_unique(node->x, node->y, width, node->height); + node->right = std::make_unique(node->x + width, node->y, dw, node->height); + } else { + // 垂直分割 + node->left = std::make_unique(node->x, node->y, node->width, height); + node->right = std::make_unique(node->x, node->y + height, node->width, dh); + } + + // 递归插入到左子节点 + return insert(node->left.get(), width, height); +} + +void TextureAtlasPage::writePixels(int x, int y, int w, int h, const uint8_t* pixels) { + if (texture_ == nullptr || pixels == nullptr) { + return; + } + + // 使用 glTexSubImage2D 更新纹理数据 + GLuint texID = static_cast( + reinterpret_cast(texture_->getNativeHandle())); + + glBindTexture(GL_TEXTURE_2D, texID); + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + glBindTexture(GL_TEXTURE_2D, 0); +} + +const AtlasEntry* TextureAtlasPage::getEntry(const std::string& name) const { + auto it = entries_.find(name); + if (it != entries_.end()) { + return &it->second; + } + return nullptr; +} + +float TextureAtlasPage::getUsageRatio() const { + return static_cast(usedArea_) / (width_ * height_); +} + +// ============================================================================ +// TextureAtlas 实现 +// ============================================================================ + +TextureAtlas::TextureAtlas() + : pageSize_(TextureAtlasPage::DEFAULT_SIZE), + sizeThreshold_(256), + enabled_(true), + initialized_(false) { +} + +TextureAtlas::~TextureAtlas() = default; + +void TextureAtlas::init(int pageSize) { + pageSize_ = pageSize; + initialized_ = true; + E2D_LOG_INFO("TextureAtlas initialized with page size: {}", pageSize); +} + +bool TextureAtlas::addTexture(const std::string& name, int width, int height, + const uint8_t* pixels) { + if (!enabled_ || !initialized_) { + return false; + } + + // 检查是否已存在 + if (contains(name)) { + return true; + } + + // 检查纹理大小 + if (width > sizeThreshold_ || height > sizeThreshold_) { + E2D_LOG_DEBUG("Texture '{}' too large for atlas ({}x{} > {}), skipping", + name, width, height, sizeThreshold_); + return false; + } + + // 尝试添加到现有页面 + Rect uvRect; + for (auto& page : pages_) { + if (page->tryAddTexture(name, width, height, pixels, uvRect)) { + entryToPage_[name] = page.get(); + return true; + } + } + + // 创建新页面 + auto newPage = std::make_unique(pageSize_, pageSize_); + if (newPage->tryAddTexture(name, width, height, pixels, uvRect)) { + entryToPage_[name] = newPage.get(); + pages_.push_back(std::move(newPage)); + return true; + } + + E2D_LOG_WARN("Failed to add texture '{}' to atlas", name); + return false; +} + +bool TextureAtlas::contains(const std::string& name) const { + return entryToPage_.find(name) != entryToPage_.end(); +} + +const Texture* TextureAtlas::getAtlasTexture(const std::string& name) const { + auto it = entryToPage_.find(name); + if (it != entryToPage_.end()) { + return it->second->getTexture().get(); + } + return nullptr; +} + +Rect TextureAtlas::getUVRect(const std::string& name) const { + auto it = entryToPage_.find(name); + if (it != entryToPage_.end()) { + const AtlasEntry* entry = it->second->getEntry(name); + if (entry != nullptr) { + return entry->uvRect; + } + } + return Rect(0, 0, 1, 1); // 默认 UV +} + +Vec2 TextureAtlas::getOriginalSize(const std::string& name) const { + auto it = entryToPage_.find(name); + if (it != entryToPage_.end()) { + const AtlasEntry* entry = it->second->getEntry(name); + if (entry != nullptr) { + return entry->originalSize; + } + } + return Vec2(0, 0); +} + +float TextureAtlas::getTotalUsageRatio() const { + if (pages_.empty()) { + return 0.0f; + } + + float total = 0.0f; + for (const auto& page : pages_) { + total += page->getUsageRatio(); + } + return total / pages_.size(); +} + +void TextureAtlas::clear() { + pages_.clear(); + entryToPage_.clear(); + E2D_LOG_INFO("TextureAtlas cleared"); +} + +// ============================================================================ +// TextureAtlasManager 单例实现 +// ============================================================================ + +TextureAtlasManager& TextureAtlasManager::getInstance() { + static TextureAtlasManager instance; + return instance; +} + +} // namespace extra2d diff --git a/Extra2D/src/resource/resource_manager.cpp b/Extra2D/src/resource/resource_manager.cpp index 9cfe22f..db73638 100644 --- a/Extra2D/src/resource/resource_manager.cpp +++ b/Extra2D/src/resource/resource_manager.cpp @@ -80,7 +80,10 @@ static std::string resolveResourcePath(const std::string &filepath) { } ResourceManager::ResourceManager() = default; -ResourceManager::~ResourceManager() = default; + +ResourceManager::~ResourceManager() { + shutdownAsyncLoader(); +} ResourceManager &ResourceManager::getInstance() { static ResourceManager instance; @@ -88,10 +91,144 @@ ResourceManager &ResourceManager::getInstance() { } // ============================================================================ -// 纹理资源 +// 异步加载系统 +// ============================================================================ + +void ResourceManager::initAsyncLoader() { + if (asyncRunning_) { + return; + } + + asyncRunning_ = true; + asyncThread_ = std::make_unique(&ResourceManager::asyncLoadLoop, this); + E2D_LOG_INFO("ResourceManager: async loader initialized"); +} + +void ResourceManager::shutdownAsyncLoader() { + if (!asyncRunning_) { + return; + } + + asyncRunning_ = false; + asyncCondition_.notify_all(); + + if (asyncThread_ && asyncThread_->joinable()) { + asyncThread_->join(); + } + + E2D_LOG_INFO("ResourceManager: async loader shutdown"); +} + +void ResourceManager::waitForAsyncLoads() { + while (pendingAsyncLoads_ > 0) { + std::this_thread::yield(); + } +} + +bool ResourceManager::hasPendingAsyncLoads() const { + return pendingAsyncLoads_ > 0; +} + +void ResourceManager::asyncLoadLoop() { + while (asyncRunning_) { + AsyncLoadTask task; + + { + std::unique_lock lock(asyncQueueMutex_); + asyncCondition_.wait(lock, [this] { return !asyncTaskQueue_.empty() || !asyncRunning_; }); + + if (!asyncRunning_) { + break; + } + + if (asyncTaskQueue_.empty()) { + continue; + } + + task = std::move(asyncTaskQueue_.front()); + asyncTaskQueue_.pop(); + } + + // 执行加载 + auto texture = loadTextureInternal(task.filepath, task.format); + + // 回调 + if (task.callback) { + task.callback(texture); + } + + // 设置 promise + task.promise.set_value(texture); + + pendingAsyncLoads_--; + } +} + +// ============================================================================ +// 纹理资源 - 同步加载 // ============================================================================ Ptr ResourceManager::loadTexture(const std::string &filepath) { + return loadTexture(filepath, false, TextureFormat::Auto); +} + +Ptr ResourceManager::loadTexture(const std::string &filepath, bool async) { + return loadTexture(filepath, async, TextureFormat::Auto); +} + +Ptr ResourceManager::loadTexture(const std::string &filepath, bool async, TextureFormat format) { + if (async) { + // 异步加载:返回空指针,实际纹理通过回调获取 + loadTextureAsync(filepath, format, nullptr); + return nullptr; + } + + return loadTextureInternal(filepath, format); +} + +void ResourceManager::loadTextureAsync(const std::string &filepath, TextureLoadCallback callback) { + loadTextureAsync(filepath, TextureFormat::Auto, callback); +} + +void ResourceManager::loadTextureAsync(const std::string &filepath, TextureFormat format, TextureLoadCallback callback) { + // 确保异步加载系统已启动 + if (!asyncRunning_) { + initAsyncLoader(); + } + + // 检查缓存 + { + std::lock_guard lock(textureMutex_); + auto it = textureCache_.find(filepath); + if (it != textureCache_.end()) { + if (auto texture = it->second.lock()) { + // 缓存命中,立即回调 + if (callback) { + callback(texture); + } + return; + } + } + } + + // 添加到异步任务队列 + AsyncLoadTask task; + task.filepath = filepath; + task.format = format; + task.callback = callback; + + { + std::lock_guard lock(asyncQueueMutex_); + asyncTaskQueue_.push(std::move(task)); + } + + pendingAsyncLoads_++; + asyncCondition_.notify_one(); + + E2D_LOG_DEBUG("ResourceManager: queued async texture load: {}", filepath); +} + +Ptr ResourceManager::loadTextureInternal(const std::string &filepath, TextureFormat format) { std::lock_guard lock(textureMutex_); // 检查缓存 @@ -120,6 +257,14 @@ Ptr ResourceManager::loadTexture(const std::string &filepath) { return nullptr; } + // 如果需要压缩,处理纹理格式 + if (format != TextureFormat::Auto && format != TextureFormat::RGBA8) { + // 注意:实际压缩需要在纹理创建时处理 + // 这里仅记录日志,实际实现需要在 GLTexture 中支持 + E2D_LOG_DEBUG("ResourceManager: texture format {} requested for {}", + static_cast(format), filepath); + } + // 存入缓存 textureCache_[filepath] = texture; E2D_LOG_DEBUG("ResourceManager: loaded texture: {}", filepath); @@ -130,6 +275,33 @@ Ptr ResourceManager::loadTexture(const std::string &filepath) { } } +TextureFormat ResourceManager::selectBestFormat(TextureFormat requested) const { + if (requested != TextureFormat::Auto) { + return requested; + } + + // 自动选择最佳格式 + // 检查支持的扩展 + // 这里简化处理,实际应该查询 OpenGL 扩展 + + // 桌面平台优先 DXT + // 移动平台优先 ETC2 或 ASTC + + // 默认返回 RGBA8 + return TextureFormat::RGBA8; +} + +std::vector ResourceManager::compressTexture(const uint8_t* data, int width, int height, + int channels, TextureFormat format) { + // 纹理压缩实现 + // 这里需要根据格式使用相应的压缩库 + // 如:squish (DXT), etcpack (ETC2), astc-encoder (ASTC) + + // 目前返回原始数据 + std::vector result(data, data + width * height * channels); + return result; +} + Ptr ResourceManager::loadTextureWithAlphaMask(const std::string &filepath) { // 先加载纹理 diff --git a/Extra2D/src/scene/node.cpp b/Extra2D/src/scene/node.cpp index 912729c..c0f843c 100644 --- a/Extra2D/src/scene/node.cpp +++ b/Extra2D/src/scene/node.cpp @@ -292,6 +292,31 @@ void Node::markTransformDirty() { } } +void Node::batchUpdateTransforms() { + // 如果本地变换脏了,先计算本地变换 + if (transformDirty_) { + (void)getLocalTransform(); // 这会计算并缓存本地变换 + } + + // 如果世界变换脏了,需要重新计算 + if (worldTransformDirty_) { + auto parent = parent_.lock(); + if (parent) { + // 使用父节点的世界变换(确保父节点已经更新) + worldTransform_ = parent->getWorldTransform() * localTransform_; + } else { + // 根节点 + worldTransform_ = localTransform_; + } + worldTransformDirty_ = false; + } + + // 递归更新子节点 + for (auto &child : children_) { + child->batchUpdateTransforms(); + } +} + void Node::onEnter() { running_ = true; for (auto &child : children_) { diff --git a/Extra2D/src/scene/shape_node.cpp b/Extra2D/src/scene/shape_node.cpp index ab1b59d..fb46c41 100644 --- a/Extra2D/src/scene/shape_node.cpp +++ b/Extra2D/src/scene/shape_node.cpp @@ -256,21 +256,21 @@ void ShapeNode::generateRenderCommand(std::vector &commands, Vec2 offset = getPosition(); RenderCommand cmd; - cmd.zOrder = zOrder; + cmd.layer = zOrder; switch (shapeType_) { case ShapeType::Point: if (!points_.empty()) { cmd.type = RenderCommandType::FilledCircle; cmd.data = - CircleData{points_[0] + offset, lineWidth_ * 0.5f, color_, 8, 0.0f}; + CircleCommandData{points_[0] + offset, lineWidth_ * 0.5f, color_, 8, 0.0f, true}; } break; case ShapeType::Line: if (points_.size() >= 2) { cmd.type = RenderCommandType::Line; - cmd.data = LineData{points_[0] + offset, points_[1] + offset, color_, + cmd.data = LineCommandData{points_[0] + offset, points_[1] + offset, color_, lineWidth_}; } break; @@ -282,13 +282,13 @@ void ShapeNode::generateRenderCommand(std::vector &commands, Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x, points_[2].y - points_[0].y); cmd.data = - RectData{Rect(rect.origin + offset, rect.size), color_, 0.0f}; + RectCommandData{Rect(rect.origin + offset, rect.size), color_, 0.0f, true}; } else { cmd.type = RenderCommandType::Rect; Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x, points_[2].y - points_[0].y); cmd.data = - RectData{Rect(rect.origin + offset, rect.size), color_, lineWidth_}; + RectCommandData{Rect(rect.origin + offset, rect.size), color_, lineWidth_, false}; } } break; @@ -299,11 +299,11 @@ void ShapeNode::generateRenderCommand(std::vector &commands, if (filled_) { cmd.type = RenderCommandType::FilledCircle; cmd.data = - CircleData{points_[0] + offset, radius, color_, segments_, 0.0f}; + CircleCommandData{points_[0] + offset, radius, color_, segments_, 0.0f, true}; } else { cmd.type = RenderCommandType::Circle; - cmd.data = CircleData{points_[0] + offset, radius, color_, segments_, - lineWidth_}; + cmd.data = CircleCommandData{points_[0] + offset, radius, color_, segments_, + lineWidth_, false}; } } break; @@ -315,10 +315,10 @@ void ShapeNode::generateRenderCommand(std::vector &commands, Vec2 p3 = points_[2] + offset; if (filled_) { cmd.type = RenderCommandType::FilledTriangle; - cmd.data = TriangleData{p1, p2, p3, color_, 0.0f}; + cmd.data = TriangleCommandData{p1, p2, p3, color_, 0.0f, true}; } else { cmd.type = RenderCommandType::Triangle; - cmd.data = TriangleData{p1, p2, p3, color_, lineWidth_}; + cmd.data = TriangleCommandData{p1, p2, p3, color_, lineWidth_, false}; } } break; @@ -333,10 +333,10 @@ void ShapeNode::generateRenderCommand(std::vector &commands, if (filled_) { cmd.type = RenderCommandType::FilledPolygon; - cmd.data = PolygonData{transformedPoints, color_, 0.0f}; + cmd.data = PolygonCommandData{transformedPoints, color_, 0.0f, true}; } else { cmd.type = RenderCommandType::Polygon; - cmd.data = PolygonData{transformedPoints, color_, lineWidth_}; + cmd.data = PolygonCommandData{transformedPoints, color_, lineWidth_, false}; } } break; diff --git a/Extra2D/src/scene/sprite.cpp b/Extra2D/src/scene/sprite.cpp index 88f7130..29d53a8 100644 --- a/Extra2D/src/scene/sprite.cpp +++ b/Extra2D/src/scene/sprite.cpp @@ -129,9 +129,9 @@ void Sprite::generateRenderCommand(std::vector &commands, // 创建渲染命令 RenderCommand cmd; cmd.type = RenderCommandType::Sprite; - cmd.zOrder = zOrder; + cmd.layer = zOrder; cmd.data = - SpriteData{texture_, destRect, srcRect, color_, getRotation(), anchor}; + SpriteCommandData{texture_.get(), destRect, srcRect, color_, getRotation(), anchor, 0}; commands.push_back(std::move(cmd)); } diff --git a/Extra2D/src/utils/object_pool.cpp b/Extra2D/src/utils/object_pool.cpp new file mode 100644 index 0000000..9ecca86 --- /dev/null +++ b/Extra2D/src/utils/object_pool.cpp @@ -0,0 +1,11 @@ +#include + +namespace extra2d { + +// ObjectPoolManager 单例实现 +ObjectPoolManager& ObjectPoolManager::getInstance() { + static ObjectPoolManager instance; + return instance; +} + +} // namespace extra2d diff --git a/docs/API_Tutorial/04_Resource_Management.md b/docs/API_Tutorial/04_Resource_Management.md index fdcc3b3..6d2c284 100644 --- a/docs/API_Tutorial/04_Resource_Management.md +++ b/docs/API_Tutorial/04_Resource_Management.md @@ -33,6 +33,64 @@ if (texture) { } ``` +### 异步加载 + +Extra2D 支持异步加载纹理,避免阻塞主线程: + +```cpp +// 同步加载(默认) +auto texture = resources.loadTexture("assets/images/player.png"); + +// 异步加载 +auto texture = resources.loadTexture("assets/images/player.png", true); + +// 使用回调函数处理异步加载完成 +resources.loadTextureAsync("assets/images/player.png", + TextureFormat::Auto, + [](Ptr texture, const std::string& path) { + if (texture) { + // 加载成功,可以安全使用 + auto sprite = Sprite::create(texture); + // ... + } + }); +``` + +### 纹理压缩格式 + +Extra2D 支持多种纹理压缩格式,可显著减少显存占用: + +```cpp +// 支持的纹理格式 +enum class TextureFormat { + Auto, // 自动选择最佳格式 + RGBA8, // 32位 RGBA(无压缩) + RGB8, // 24位 RGB(无压缩) + DXT1, // DXT1 压缩(适用于不透明纹理) + DXT5, // DXT5 压缩(适用于透明纹理) + ETC2, // ETC2 压缩(移动平台) + ASTC4x4, // ASTC 4x4 高质量压缩 + ASTC8x8 // ASTC 8x8 高压缩率 +}; + +// 使用压缩格式加载纹理 +auto texture = resources.loadTexture("assets/images/player.png", false, TextureFormat::DXT5); + +// 异步加载 + 压缩 +auto texture = resources.loadTexture("assets/images/player.png", true, TextureFormat::ASTC4x4); +``` + +**格式选择建议:** + +| 格式 | 压缩比 | 质量 | 适用场景 | +|------|--------|------|---------| +| RGBA8 | 1:1 | 最高 | 小图标、需要最高质量 | +| DXT1 | 1:8 | 高 | 不透明纹理、大背景图 | +| DXT5 | 1:4 | 高 | 透明纹理、角色精灵 | +| ETC2 | 1:4 | 高 | 移动设备、跨平台 | +| ASTC4x4 | 1:4 | 很高 | 高质量透明纹理 | +| ASTC8x8 | 1:16 | 中等 | 大纹理、远景贴图 | + ### 纹理缓存 资源管理器会自动缓存已加载的纹理,多次加载同一文件会返回缓存的实例: @@ -47,6 +105,24 @@ auto tex2 = resources.loadTexture("assets/image.png"); // tex1 和 tex2 指向同一个纹理对象 ``` +### 纹理图集(Texture Atlas) + +Extra2D 自动使用纹理图集优化渲染性能: + +```cpp +// 获取纹理图集管理器 +auto& atlasManager = resources.getTextureAtlasManager(); + +// 将多个纹理打包到图集(自动进行) +// 渲染时,相同图集的精灵会自动批处理 + +// 手动创建图集(高级用法) +auto atlas = atlasManager.createAtlas("ui_atlas", 2048, 2048); +atlas->addTexture("button", buttonTexture); +atlas->addTexture("icon", iconTexture); +atlas->pack(); // 执行打包 +``` + ## 字体加载 ### 基本用法 @@ -120,6 +196,70 @@ resources.cleanupUnused(); resources.clearCache(); ``` +## 内存管理 + +### 内存池(内部自动管理) + +Extra2D 使用内存池优化小对象分配,无需用户干预: + +```cpp +// 内存池自动管理以下对象: +// - 场景节点 +// - 渲染命令 +// - 碰撞形状 +// - 事件对象 + +// 用户代码无需特殊处理,正常使用即可 +auto node = Node::create(); // 自动使用内存池 +auto sprite = Sprite::create(texture); // 自动使用内存池 +``` + +### 批量更新(内部自动进行) + +Extra2D 自动批量更新节点变换,优化性能: + +```cpp +// 以下操作会自动批处理: +// - 节点变换更新 +// - 渲染命令提交 +// - 纹理绑定 + +// 用户代码无需特殊处理 +for (int i = 0; i < 1000; ++i) { + auto sprite = Sprite::create(texture); + sprite->setPosition(i * 10, 100); + addChild(sprite); // 变换更新会自动批处理 +} +``` + +## 渲染批处理 + +### 自动批处理 + +Extra2D 自动将渲染命令批处理以优化性能: + +```cpp +// 以下情况会自动批处理: +// 1. 相同纹理的精灵 +// 2. 相同图层的节点 +// 3. 相同混合模式 + +// 示例:1000 个相同纹理的精灵会自动批处理为少量 draw call +for (int i = 0; i < 1000; ++i) { + auto sprite = Sprite::create(texture); + addChild(sprite); +} +``` + +### 手动控制渲染顺序 + +```cpp +// 设置节点的渲染层级(z-order) +sprite->setZOrder(10); // 值越大,渲染越靠前 + +// 同层级的节点会自动批处理 +``` + ## 完整示例 参考 `examples/push_box/StartScene.cpp`: @@ -131,17 +271,17 @@ void StartScene::onEnter() { auto& app = Application::instance(); auto& resources = app.resources(); - // 加载背景纹理 - auto bgTex = resources.loadTexture("assets/images/start.jpg"); + // 加载背景纹理(异步 + 压缩) + auto bgTex = resources.loadTexture("assets/images/start.jpg", true, TextureFormat::DXT1); if (bgTex) { auto background = Sprite::create(bgTex); background->setAnchor(0.0f, 0.0f); addChild(background); } - // 加载音效图标纹理 - auto soundOn = resources.loadTexture("assets/images/soundon.png"); - auto soundOff = resources.loadTexture("assets/images/soundoff.png"); + // 加载音效图标纹理(异步 + DXT5 压缩支持透明) + auto soundOn = resources.loadTexture("assets/images/soundon.png", true, TextureFormat::DXT5); + auto soundOff = resources.loadTexture("assets/images/soundoff.png", true, TextureFormat::DXT5); if (soundOn && soundOff) { soundIcon_ = Sprite::create(g_SoundOpen ? soundOn : soundOff); addChild(soundIcon_); @@ -154,12 +294,49 @@ void StartScene::onEnter() { } ``` +## 性能优化建议 + +### 纹理优化 + +1. **使用纹理压缩** - 对大型纹理使用 DXT/ASTC 压缩减少显存占用 +2. **使用纹理图集** - 将多个小纹理打包到图集,减少 draw call +3. **异步加载大纹理** - 避免在主线程加载大型资源造成卡顿 +4. **合理设置纹理尺寸** - 避免使用过大的纹理(建议最大 2048x2048) + +### 资源加载策略 + +```cpp +// 场景预加载 +void GameScene::onEnter() { + auto& resources = Application::instance().resources(); + + // 预加载关键资源 + resources.loadTexture("assets/textures/player.png", true); + resources.loadTexture("assets/textures/enemy.png", true); + resources.loadFont("assets/fonts/main.ttf", 24, true); +} + +// 异步加载非关键资源 +void GameScene::loadOptionalResources() { + resources.loadTextureAsync("assets/textures/background.jpg", + TextureFormat::DXT1, + [](Ptr tex, const std::string& path) { + if (tex) { + // 加载完成后创建背景 + } + }); +} +``` + ## 最佳实践 1. **预加载资源** - 在场景 `onEnter()` 中加载所需资源 2. **检查资源有效性** - 始终检查加载结果是否为 nullptr 3. **复用资源** - 多次使用同一资源时保存指针,避免重复加载 4. **合理设置字号** - 字体加载时会生成对应字号的图集 +5. **使用异步加载** - 对大型资源使用异步加载避免卡顿 +6. **选择合适的压缩格式** - 根据纹理用途选择最佳压缩格式 +7. **利用自动批处理** - 相同纹理的精灵会自动批处理,无需手动优化 ## 下一步