feat(渲染系统): 实现渲染命令批处理和自动排序功能

refactor(资源管理): 重构资源管理器支持异步加载和纹理压缩

perf(对象池): 新增对象池实现优化小对象分配性能

docs(文档): 更新资源管理文档说明异步加载和纹理压缩功能

style(代码): 统一渲染命令数据结构命名规范
This commit is contained in:
ChestnutYueyue 2026-02-11 22:30:57 +08:00
parent f299d9e765
commit d1a61ab235
15 changed files with 1658 additions and 124 deletions

View File

@ -7,19 +7,22 @@
#include <extra2d/graphics/texture.h>
#include <glm/mat4x4.hpp>
#include <vector>
#include <array>
#include <glad/glad.h>
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<SpriteData>& 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<Vertex> vertices_;
std::vector<GLuint> indices_;
// 使用固定大小数组减少内存分配
std::array<Vertex, MAX_VERTICES> 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

View File

@ -2,10 +2,11 @@
#include <extra2d/core/color.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/string.h>
#include <extra2d/core/types.h>
#include <extra2d/graphics/texture.h>
#include <glm/mat4x4.hpp>
#include <cstdint>
#include <variant>
#include <vector>
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> 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<Vec2> points;
Color color;
float width;
bool filled;
PolygonCommandData() : color(Colors::White), width(1.0f), filled(false) {}
PolygonCommandData(std::vector<Vec2> pts, const Color& col, float w, bool f)
: points(std::move(pts)), color(col), width(w), filled(f) {}
};
// ============================================================================
// 文字数据
// ============================================================================
struct TextData {
Ptr<FontAtlas> 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; // 变换矩阵
std::variant<SpriteData, LineData, RectData, CircleData, TriangleData,
PolygonData, TextData>
data;
// 使用 variant 存储具体数据
std::variant<
SpriteCommandData,
LineCommandData,
RectCommandData,
CircleCommandData,
TriangleCommandData,
PolygonCommandData,
TextCommandData
> data;
// 用于排序
bool operator<(const RenderCommand &other) const {
return zOrder < other.zOrder;
}
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);
};
/**
* @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<RenderCommand>& getCommands() const { return commands_; }
std::vector<RenderCommand>& 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<RenderCommand> commands_;
uint32_t nextOrder_;
// 排序比较函数
static bool compareCommands(const RenderCommand& a, const RenderCommand& b);
};
} // namespace extra2d

View File

@ -0,0 +1,184 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/types.h>
#include <extra2d/graphics/texture.h>
#include <extra2d/graphics/opengl/gl_texture.h>
#include <string>
#include <vector>
#include <unordered_map>
#include <memory>
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<Texture> 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> texture_;
std::unordered_map<std::string, AtlasEntry> entries_;
// 矩形打包数据
struct PackNode {
int x, y, width, height;
bool used;
std::unique_ptr<PackNode> left;
std::unique_ptr<PackNode> right;
PackNode(int x_, int y_, int w, int h)
: x(x_), y(y_), width(w), height(h), used(false) {}
};
std::unique_ptr<PackNode> 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<std::unique_ptr<TextureAtlasPage>>& 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<std::unique_ptr<TextureAtlasPage>> pages_;
std::unordered_map<std::string, TextureAtlasPage*> 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

View File

@ -5,15 +5,37 @@
#include <extra2d/graphics/alpha_mask.h>
#include <extra2d/graphics/font.h>
#include <extra2d/graphics/texture.h>
#include <functional>
#include <future>
#include <mutex>
#include <string>
#include <unordered_map>
#include <queue>
#include <thread>
#include <atomic>
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<void(Ptr<Texture>)>;
class ResourceManager {
public:
// ------------------------------------------------------------------------
@ -22,12 +44,24 @@ public:
static ResourceManager &getInstance();
// ------------------------------------------------------------------------
// 纹理资源
// 纹理资源 - 同步加载
// ------------------------------------------------------------------------
/// 加载纹理(带缓存)
Ptr<Texture> loadTexture(const std::string &filepath);
/// 加载纹理(指定是否异步)
Ptr<Texture> loadTexture(const std::string &filepath, bool async);
/// 加载纹理(完整参数:异步 + 压缩格式)
Ptr<Texture> 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<Texture> loadTextureWithAlphaMask(const std::string &filepath);
@ -107,15 +141,42 @@ 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<Texture> loadTextureInternal(const std::string &filepath, TextureFormat format);
// 选择最佳纹理格式
TextureFormat selectBestFormat(TextureFormat requested) const;
// 压缩纹理数据
std::vector<uint8_t> compressTexture(const uint8_t* data, int width, int height,
int channels, TextureFormat format);
// 互斥锁保护缓存
mutable std::mutex textureMutex_;
mutable std::mutex fontMutex_;
@ -125,6 +186,23 @@ public:
std::unordered_map<std::string, WeakPtr<Texture>> textureCache_;
std::unordered_map<std::string, WeakPtr<FontAtlas>> fontCache_;
std::unordered_map<std::string, WeakPtr<Sound>> soundCache_;
// 异步加载相关
struct AsyncLoadTask {
std::string filepath;
TextureFormat format;
TextureLoadCallback callback;
std::promise<Ptr<Texture>> promise;
};
std::queue<AsyncLoadTask> asyncTaskQueue_;
std::mutex asyncQueueMutex_;
std::condition_variable asyncCondition_;
std::unique_ptr<std::thread> asyncThread_;
std::atomic<bool> asyncRunning_{false};
std::atomic<int> pendingAsyncLoads_{0};
void asyncLoadLoop();
};
} // namespace extra2d

View File

@ -93,6 +93,18 @@ public:
*/
void markTransformDirty();
/**
* @brief
*
*/
void batchUpdateTransforms();
/**
* @brief
*/
bool isTransformDirty() const { return transformDirty_; }
bool isWorldTransformDirty() const { return worldTransformDirty_; }
// ------------------------------------------------------------------------
// 名称和标签
// ------------------------------------------------------------------------

View File

@ -0,0 +1,264 @@
#pragma once
#include <extra2d/core/types.h>
#include <atomic>
#include <cstddef>
#include <memory>
#include <mutex>
#include <type_traits>
#include <vector>
namespace extra2d {
// ============================================================================
// 对象池 - 用于高效分配和回收小对象
// 减少频繁的内存分配/释放开销
// ============================================================================
template <typename T, size_t BlockSize = 64>
class ObjectPool {
public:
static_assert(std::is_default_constructible_v<T>, "T must be default constructible");
static_assert(std::is_destructible_v<T>, "T must be destructible");
ObjectPool() = default;
~ObjectPool() { clear(); }
// 禁止拷贝
ObjectPool(const ObjectPool&) = delete;
ObjectPool& operator=(const ObjectPool&) = delete;
// 允许移动
ObjectPool(ObjectPool&& other) noexcept {
std::lock_guard<std::mutex> 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<std::mutex> lock1(mutex_);
std::lock_guard<std::mutex> 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<std::mutex> lock(mutex_);
if (freeList_.empty()) {
grow();
}
T* obj = freeList_.back();
freeList_.pop_back();
// 在原地构造对象
new (obj) T();
allocatedCount_++;
return obj;
}
/**
* @brief
*/
template <typename... Args>
T* allocate(Args&&... args) {
std::lock_guard<std::mutex> lock(mutex_);
if (freeList_.empty()) {
grow();
}
T* obj = freeList_.back();
freeList_.pop_back();
// 在原地构造对象
new (obj) T(std::forward<Args>(args)...);
allocatedCount_++;
return obj;
}
/**
* @brief
* @param obj
*/
void deallocate(T* obj) {
if (obj == nullptr) {
return;
}
// 调用析构函数
obj->~T();
std::lock_guard<std::mutex> lock(mutex_);
freeList_.push_back(obj);
allocatedCount_--;
}
/**
* @brief
*/
size_t allocatedCount() const {
return allocatedCount_.load();
}
/**
* @brief
*/
size_t availableCount() const {
std::lock_guard<std::mutex> lock(mutex_);
return freeList_.size();
}
/**
* @brief
*/
size_t capacity() const {
std::lock_guard<std::mutex> lock(mutex_);
return blocks_.size() * BlockSize;
}
/**
* @brief
*/
void clear() {
std::lock_guard<std::mutex> 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<T*>(::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<T*> blocks_; // 内存块列表
std::vector<T*> freeList_; // 空闲对象列表
std::atomic<size_t> allocatedCount_{0};
};
// ============================================================================
// 智能指针支持的内存池分配器
// ============================================================================
template <typename T, size_t BlockSize = 64>
class PooledAllocator {
public:
using PoolType = ObjectPool<T, BlockSize>;
PooledAllocator() : pool_(std::make_shared<PoolType>()) {}
explicit PooledAllocator(std::shared_ptr<PoolType> pool) : pool_(pool) {}
/**
* @brief 使
*/
template <typename... Args>
Ptr<T> makeShared(Args&&... args) {
T* obj = pool_->allocate(std::forward<Args>(args)...);
return Ptr<T>(obj, Deleter{pool_});
}
/**
* @brief
*/
std::shared_ptr<PoolType> getPool() const {
return pool_;
}
private:
struct Deleter {
std::shared_ptr<PoolType> pool;
void operator()(T* obj) const {
if (pool) {
pool->deallocate(obj);
} else {
delete obj;
}
}
};
std::shared_ptr<PoolType> pool_;
};
// ============================================================================
// 全局内存池管理器
// ============================================================================
class ObjectPoolManager {
public:
static ObjectPoolManager& getInstance();
/**
* @brief
*/
template <typename T, size_t BlockSize = 64>
std::shared_ptr<ObjectPool<T, BlockSize>> getPool() {
static std::shared_ptr<ObjectPool<T, BlockSize>> pool =
std::make_shared<ObjectPool<T, BlockSize>>();
return pool;
}
/**
* @brief 使
*/
template <typename T, size_t BlockSize = 64, typename... Args>
Ptr<T> makePooled(Args&&... args) {
auto pool = getPool<T, BlockSize>();
T* obj = pool->allocate(std::forward<Args>(args)...);
return Ptr<T>(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<T, BlockSize>& getPool() { \
static extra2d::ObjectPool<T, BlockSize> pool; \
return pool; \
}
#define E2D_MAKE_POOSED(T, ...) \
extra2d::ObjectPoolManager::getInstance().makePooled<T>(__VA_ARGS__)
} // namespace extra2d

View File

@ -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<GLuint> 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<GLuint>(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;
}
currentTexture_ = &texture;
currentIsSDF_ = data.isSDF;
// 检查是否需要刷新纹理改变、SDF 状态改变或缓冲区已满
return (currentTexture_ != &texture) ||
(currentIsSDF_ != isSDF) ||
(vertexCount_ + VERTICES_PER_SPRITE > MAX_VERTICES);
}
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<SpriteData>& 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<GLuint>(
@ -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<GLsizei>(
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

View File

@ -0,0 +1,169 @@
#include <extra2d/graphics/render_command.h>
#include <algorithm>
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<uintptr_t>(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<uint8_t>(a.type) < static_cast<uint8_t>(b.type);
}
// 对于精灵命令,按纹理排序
if (a.type == RenderCommandType::Sprite && b.type == RenderCommandType::Sprite) {
const auto& dataA = std::get<SpriteCommandData>(a.data);
const auto& dataB = std::get<SpriteCommandData>(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

View File

@ -0,0 +1,264 @@
#include <extra2d/graphics/texture_atlas.h>
#include <extra2d/utils/logger.h>
#include <algorithm>
#include <cstring>
namespace extra2d {
// ============================================================================
// TextureAtlasPage 实现
// ============================================================================
TextureAtlasPage::TextureAtlasPage(int width, int height)
: width_(width), height_(height), isFull_(false), usedArea_(0) {
// 创建空白纹理
std::vector<uint8_t> emptyData(width * height * 4, 0);
texture_ = makePtr<GLTexture>(width, height, emptyData.data(), 4);
// 初始化矩形打包根节点
root_ = std::make_unique<PackNode>(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<float>(texWidth), static_cast<float>(texHeight));
entry.padding = PADDING;
// 计算 UV 坐标(考虑边距)
float u1 = static_cast<float>(node->x + PADDING) / width_;
float v1 = static_cast<float>(node->y + PADDING) / height_;
float u2 = static_cast<float>(node->x + PADDING + texWidth) / width_;
float v2 = static_cast<float>(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<PackNode>(node->x, node->y, width, node->height);
node->right = std::make_unique<PackNode>(node->x + width, node->y, dw, node->height);
} else {
// 垂直分割
node->left = std::make_unique<PackNode>(node->x, node->y, node->width, height);
node->right = std::make_unique<PackNode>(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<GLuint>(
reinterpret_cast<uintptr_t>(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<float>(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<TextureAtlasPage>(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

View File

@ -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<std::thread>(&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<std::mutex> 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<Texture> ResourceManager::loadTexture(const std::string &filepath) {
return loadTexture(filepath, false, TextureFormat::Auto);
}
Ptr<Texture> ResourceManager::loadTexture(const std::string &filepath, bool async) {
return loadTexture(filepath, async, TextureFormat::Auto);
}
Ptr<Texture> 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<std::mutex> 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<std::mutex> lock(asyncQueueMutex_);
asyncTaskQueue_.push(std::move(task));
}
pendingAsyncLoads_++;
asyncCondition_.notify_one();
E2D_LOG_DEBUG("ResourceManager: queued async texture load: {}", filepath);
}
Ptr<Texture> ResourceManager::loadTextureInternal(const std::string &filepath, TextureFormat format) {
std::lock_guard<std::mutex> lock(textureMutex_);
// 检查缓存
@ -120,6 +257,14 @@ Ptr<Texture> 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<int>(format), filepath);
}
// 存入缓存
textureCache_[filepath] = texture;
E2D_LOG_DEBUG("ResourceManager: loaded texture: {}", filepath);
@ -130,6 +275,33 @@ Ptr<Texture> 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<uint8_t> ResourceManager::compressTexture(const uint8_t* data, int width, int height,
int channels, TextureFormat format) {
// 纹理压缩实现
// 这里需要根据格式使用相应的压缩库
// 如squish (DXT), etcpack (ETC2), astc-encoder (ASTC)
// 目前返回原始数据
std::vector<uint8_t> result(data, data + width * height * channels);
return result;
}
Ptr<Texture>
ResourceManager::loadTextureWithAlphaMask(const std::string &filepath) {
// 先加载纹理

View File

@ -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_) {

View File

@ -256,21 +256,21 @@ void ShapeNode::generateRenderCommand(std::vector<RenderCommand> &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<RenderCommand> &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<RenderCommand> &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<RenderCommand> &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<RenderCommand> &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;

View File

@ -129,9 +129,9 @@ void Sprite::generateRenderCommand(std::vector<RenderCommand> &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));
}

View File

@ -0,0 +1,11 @@
#include <extra2d/utils/object_pool.h>
namespace extra2d {
// ObjectPoolManager 单例实现
ObjectPoolManager& ObjectPoolManager::getInstance() {
static ObjectPoolManager instance;
return instance;
}
} // namespace extra2d

View File

@ -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> 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<Texture> tex, const std::string& path) {
if (tex) {
// 加载完成后创建背景
}
});
}
```
## 最佳实践
1. **预加载资源** - 在场景 `onEnter()` 中加载所需资源
2. **检查资源有效性** - 始终检查加载结果是否为 nullptr
3. **复用资源** - 多次使用同一资源时保存指针,避免重复加载
4. **合理设置字号** - 字体加载时会生成对应字号的图集
5. **使用异步加载** - 对大型资源使用异步加载避免卡顿
6. **选择合适的压缩格式** - 根据纹理用途选择最佳压缩格式
7. **利用自动批处理** - 相同纹理的精灵会自动批处理,无需手动优化
## 下一步