feat(渲染系统): 实现渲染命令批处理和自动排序功能
refactor(资源管理): 重构资源管理器支持异步加载和纹理压缩 perf(对象池): 新增对象池实现优化小对象分配性能 docs(文档): 更新资源管理文档说明异步加载和纹理压缩功能 style(代码): 统一渲染命令数据结构命名规范
This commit is contained in:
parent
f299d9e765
commit
d1a61ab235
|
|
@ -7,19 +7,22 @@
|
||||||
#include <extra2d/graphics/texture.h>
|
#include <extra2d/graphics/texture.h>
|
||||||
#include <glm/mat4x4.hpp>
|
#include <glm/mat4x4.hpp>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// OpenGL 精灵批渲染器
|
// OpenGL 精灵批渲染器 - 优化版本
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class GLSpriteBatch {
|
class GLSpriteBatch {
|
||||||
public:
|
public:
|
||||||
static constexpr size_t MAX_SPRITES = 10000;
|
static constexpr size_t MAX_SPRITES = 10000;
|
||||||
static constexpr size_t VERTICES_PER_SPRITE = 4;
|
static constexpr size_t VERTICES_PER_SPRITE = 4;
|
||||||
static constexpr size_t INDICES_PER_SPRITE = 6;
|
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 {
|
struct Vertex {
|
||||||
glm::vec2 position;
|
glm::vec2 position;
|
||||||
|
|
@ -48,9 +51,19 @@ public:
|
||||||
void draw(const Texture &texture, const SpriteData &data);
|
void draw(const Texture &texture, const SpriteData &data);
|
||||||
void end();
|
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 getDrawCallCount() const { return drawCallCount_; }
|
||||||
uint32_t getSpriteCount() const { return spriteCount_; }
|
uint32_t getSpriteCount() const { return spriteCount_; }
|
||||||
|
uint32_t getBatchCount() const { return batchCount_; }
|
||||||
|
|
||||||
|
// 检查是否需要刷新
|
||||||
|
bool needsFlush(const Texture& texture, bool isSDF) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GLuint vao_;
|
GLuint vao_;
|
||||||
|
|
@ -58,8 +71,9 @@ private:
|
||||||
GLuint ibo_;
|
GLuint ibo_;
|
||||||
GLShader shader_;
|
GLShader shader_;
|
||||||
|
|
||||||
std::vector<Vertex> vertices_;
|
// 使用固定大小数组减少内存分配
|
||||||
std::vector<GLuint> indices_;
|
std::array<Vertex, MAX_VERTICES> vertexBuffer_;
|
||||||
|
size_t vertexCount_;
|
||||||
|
|
||||||
const Texture *currentTexture_;
|
const Texture *currentTexture_;
|
||||||
bool currentIsSDF_;
|
bool currentIsSDF_;
|
||||||
|
|
@ -67,9 +81,13 @@ private:
|
||||||
|
|
||||||
uint32_t drawCallCount_;
|
uint32_t drawCallCount_;
|
||||||
uint32_t spriteCount_;
|
uint32_t spriteCount_;
|
||||||
|
uint32_t batchCount_;
|
||||||
|
|
||||||
void flush();
|
void flush();
|
||||||
void setupShader();
|
void setupShader();
|
||||||
|
|
||||||
|
// 添加顶点到缓冲区
|
||||||
|
void addVertices(const SpriteData& data);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
#include <extra2d/core/color.h>
|
||||||
#include <extra2d/core/math_types.h>
|
#include <extra2d/core/math_types.h>
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/texture.h>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
#include <cstdint>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -13,110 +14,210 @@ namespace extra2d {
|
||||||
class Texture;
|
class Texture;
|
||||||
class FontAtlas;
|
class FontAtlas;
|
||||||
|
|
||||||
// ============================================================================
|
/**
|
||||||
// 渲染命令类型
|
* @brief 渲染命令类型枚举
|
||||||
// ============================================================================
|
*/
|
||||||
enum class RenderCommandType {
|
enum class RenderCommandType : uint8_t {
|
||||||
Sprite,
|
None = 0,
|
||||||
Line,
|
Sprite, // 精灵绘制
|
||||||
Rect,
|
Line, // 线条绘制
|
||||||
FilledRect,
|
Rect, // 矩形绘制
|
||||||
Circle,
|
FilledRect, // 填充矩形
|
||||||
FilledCircle,
|
Circle, // 圆形绘制
|
||||||
Triangle,
|
FilledCircle, // 填充圆形
|
||||||
FilledTriangle,
|
Triangle, // 三角形绘制
|
||||||
Polygon,
|
FilledTriangle, // 填充三角形
|
||||||
FilledPolygon,
|
Polygon, // 多边形绘制
|
||||||
Text
|
FilledPolygon, // 填充多边形
|
||||||
|
Text, // 文本绘制
|
||||||
|
Custom // 自定义绘制
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
/**
|
||||||
// 精灵数据
|
* @brief 精灵渲染命令数据
|
||||||
// ============================================================================
|
*/
|
||||||
struct SpriteData {
|
struct SpriteCommandData {
|
||||||
Ptr<Texture> texture;
|
const Texture* texture;
|
||||||
Rect destRect;
|
Rect destRect;
|
||||||
Rect srcRect;
|
Rect srcRect;
|
||||||
Color tint;
|
Color tint;
|
||||||
float rotation;
|
float rotation;
|
||||||
Vec2 anchor;
|
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) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
/**
|
||||||
// 直线数据
|
* @brief 线条渲染命令数据
|
||||||
// ============================================================================
|
*/
|
||||||
struct LineData {
|
struct LineCommandData {
|
||||||
Vec2 start;
|
Vec2 start;
|
||||||
Vec2 end;
|
Vec2 end;
|
||||||
Color color;
|
Color color;
|
||||||
float width;
|
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) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
/**
|
||||||
// 矩形数据
|
* @brief 矩形渲染命令数据
|
||||||
// ============================================================================
|
*/
|
||||||
struct RectData {
|
struct RectCommandData {
|
||||||
Rect rect;
|
Rect rect;
|
||||||
Color color;
|
Color color;
|
||||||
float width;
|
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) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
/**
|
||||||
// 圆形数据
|
* @brief 圆形渲染命令数据
|
||||||
// ============================================================================
|
*/
|
||||||
struct CircleData {
|
struct CircleCommandData {
|
||||||
Vec2 center;
|
Vec2 center;
|
||||||
float radius;
|
float radius;
|
||||||
Color color;
|
Color color;
|
||||||
int segments;
|
int segments;
|
||||||
float width;
|
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) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
/**
|
||||||
// 三角形数据
|
* @brief 三角形渲染命令数据
|
||||||
// ============================================================================
|
*/
|
||||||
struct TriangleData {
|
struct TriangleCommandData {
|
||||||
Vec2 p1;
|
Vec2 p1, p2, p3;
|
||||||
Vec2 p2;
|
|
||||||
Vec2 p3;
|
|
||||||
Color color;
|
Color color;
|
||||||
float width;
|
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) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
/**
|
||||||
// 多边形数据
|
* @brief 多边形渲染命令数据
|
||||||
// ============================================================================
|
*/
|
||||||
struct PolygonData {
|
struct PolygonCommandData {
|
||||||
std::vector<Vec2> points;
|
std::vector<Vec2> points;
|
||||||
Color color;
|
Color color;
|
||||||
float width;
|
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) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
/**
|
||||||
// 文字数据
|
* @brief 文本渲染命令数据
|
||||||
// ============================================================================
|
*/
|
||||||
struct TextData {
|
struct TextCommandData {
|
||||||
Ptr<FontAtlas> font;
|
const FontAtlas* font;
|
||||||
std::string text;
|
std::string text;
|
||||||
Vec2 position;
|
Vec2 position;
|
||||||
Color color;
|
Color color;
|
||||||
|
|
||||||
|
TextCommandData() : font(nullptr), text(), position(), color(Colors::White) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
/**
|
||||||
// 渲染命令
|
* @brief 统一渲染命令结构
|
||||||
// ============================================================================
|
* 使用 variant 存储不同类型的命令数据,减少内存分配
|
||||||
|
*/
|
||||||
struct RenderCommand {
|
struct RenderCommand {
|
||||||
RenderCommandType type;
|
RenderCommandType type;
|
||||||
int zOrder;
|
uint32_t layer; // 渲染层级,用于排序
|
||||||
|
uint32_t order; // 提交顺序,保证同层级内稳定排序
|
||||||
|
glm::mat4 transform; // 变换矩阵
|
||||||
|
|
||||||
std::variant<SpriteData, LineData, RectData, CircleData, TriangleData,
|
// 使用 variant 存储具体数据
|
||||||
PolygonData, TextData>
|
std::variant<
|
||||||
data;
|
SpriteCommandData,
|
||||||
|
LineCommandData,
|
||||||
|
RectCommandData,
|
||||||
|
CircleCommandData,
|
||||||
|
TriangleCommandData,
|
||||||
|
PolygonCommandData,
|
||||||
|
TextCommandData
|
||||||
|
> data;
|
||||||
|
|
||||||
// 用于排序
|
RenderCommand() : type(RenderCommandType::None), layer(0), order(0),
|
||||||
bool operator<(const RenderCommand &other) const {
|
transform(1.0f) {}
|
||||||
return zOrder < other.zOrder;
|
|
||||||
}
|
// 便捷构造函数
|
||||||
|
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
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -5,15 +5,37 @@
|
||||||
#include <extra2d/graphics/alpha_mask.h>
|
#include <extra2d/graphics/alpha_mask.h>
|
||||||
#include <extra2d/graphics/font.h>
|
#include <extra2d/graphics/font.h>
|
||||||
#include <extra2d/graphics/texture.h>
|
#include <extra2d/graphics/texture.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <future>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <queue>
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
namespace extra2d {
|
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 {
|
class ResourceManager {
|
||||||
public:
|
public:
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
@ -22,12 +44,24 @@ public:
|
||||||
static ResourceManager &getInstance();
|
static ResourceManager &getInstance();
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 纹理资源
|
// 纹理资源 - 同步加载
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
/// 加载纹理(带缓存)
|
/// 加载纹理(带缓存)
|
||||||
Ptr<Texture> loadTexture(const std::string &filepath);
|
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遮罩(用于不规则形状图片)
|
/// 加载纹理并生成Alpha遮罩(用于不规则形状图片)
|
||||||
Ptr<Texture> loadTextureWithAlphaMask(const std::string &filepath);
|
Ptr<Texture> loadTextureWithAlphaMask(const std::string &filepath);
|
||||||
|
|
||||||
|
|
@ -107,15 +141,42 @@ public:
|
||||||
size_t getFontCacheSize() const;
|
size_t getFontCacheSize() const;
|
||||||
size_t getSoundCacheSize() const;
|
size_t getSoundCacheSize() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 异步加载控制
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 初始化异步加载系统(可选,自动在首次异步加载时初始化)
|
||||||
|
void initAsyncLoader();
|
||||||
|
|
||||||
|
/// 关闭异步加载系统
|
||||||
|
void shutdownAsyncLoader();
|
||||||
|
|
||||||
|
/// 等待所有异步加载完成
|
||||||
|
void waitForAsyncLoads();
|
||||||
|
|
||||||
|
/// 检查是否有正在进行的异步加载
|
||||||
|
bool hasPendingAsyncLoads() const;
|
||||||
|
|
||||||
ResourceManager();
|
ResourceManager();
|
||||||
~ResourceManager();
|
~ResourceManager();
|
||||||
ResourceManager(const ResourceManager &) = delete;
|
ResourceManager(const ResourceManager &) = delete;
|
||||||
ResourceManager &operator=(const ResourceManager &) = delete;
|
ResourceManager &operator=(const ResourceManager &) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
// 生成字体缓存key
|
// 生成字体缓存key
|
||||||
std::string makeFontKey(const std::string &filepath, int fontSize,
|
std::string makeFontKey(const std::string &filepath, int fontSize,
|
||||||
bool useSDF) const;
|
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 textureMutex_;
|
||||||
mutable std::mutex fontMutex_;
|
mutable std::mutex fontMutex_;
|
||||||
|
|
@ -125,6 +186,23 @@ public:
|
||||||
std::unordered_map<std::string, WeakPtr<Texture>> textureCache_;
|
std::unordered_map<std::string, WeakPtr<Texture>> textureCache_;
|
||||||
std::unordered_map<std::string, WeakPtr<FontAtlas>> fontCache_;
|
std::unordered_map<std::string, WeakPtr<FontAtlas>> fontCache_;
|
||||||
std::unordered_map<std::string, WeakPtr<Sound>> soundCache_;
|
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
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,18 @@ public:
|
||||||
*/
|
*/
|
||||||
void markTransformDirty();
|
void markTransformDirty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 批量更新变换矩阵
|
||||||
|
* 在渲染前统一计算所有脏节点的变换矩阵,避免逐节点计算时的重复递归
|
||||||
|
*/
|
||||||
|
void batchUpdateTransforms();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取变换脏标记状态
|
||||||
|
*/
|
||||||
|
bool isTransformDirty() const { return transformDirty_; }
|
||||||
|
bool isWorldTransformDirty() const { return worldTransformDirty_; }
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 名称和标签
|
// 名称和标签
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -54,9 +54,7 @@ void main() {
|
||||||
|
|
||||||
GLSpriteBatch::GLSpriteBatch()
|
GLSpriteBatch::GLSpriteBatch()
|
||||||
: vao_(0), vbo_(0), ibo_(0), currentTexture_(nullptr), currentIsSDF_(false),
|
: vao_(0), vbo_(0), ibo_(0), currentTexture_(nullptr), currentIsSDF_(false),
|
||||||
drawCallCount_(0), spriteCount_(0) {
|
vertexCount_(0), drawCallCount_(0), spriteCount_(0), batchCount_(0) {
|
||||||
vertices_.reserve(MAX_SPRITES * VERTICES_PER_SPRITE);
|
|
||||||
indices_.reserve(MAX_SPRITES * INDICES_PER_SPRITE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GLSpriteBatch::~GLSpriteBatch() { shutdown(); }
|
GLSpriteBatch::~GLSpriteBatch() { shutdown(); }
|
||||||
|
|
@ -76,10 +74,9 @@ bool GLSpriteBatch::init() {
|
||||||
|
|
||||||
glBindVertexArray(vao_);
|
glBindVertexArray(vao_);
|
||||||
|
|
||||||
// 设置 VBO
|
// 设置 VBO - 使用动态绘制模式
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||||
glBufferData(GL_ARRAY_BUFFER,
|
glBufferData(GL_ARRAY_BUFFER, MAX_VERTICES * sizeof(Vertex), nullptr,
|
||||||
MAX_SPRITES * VERTICES_PER_SPRITE * sizeof(Vertex), nullptr,
|
|
||||||
GL_DYNAMIC_DRAW);
|
GL_DYNAMIC_DRAW);
|
||||||
|
|
||||||
// 设置顶点属性
|
// 设置顶点属性
|
||||||
|
|
@ -95,9 +92,9 @@ bool GLSpriteBatch::init() {
|
||||||
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
||||||
(void *)offsetof(Vertex, color));
|
(void *)offsetof(Vertex, color));
|
||||||
|
|
||||||
// 生成索引缓冲区
|
// 生成索引缓冲区 - 静态,只需创建一次
|
||||||
std::vector<GLuint> indices;
|
std::vector<GLuint> indices;
|
||||||
indices.reserve(MAX_SPRITES * INDICES_PER_SPRITE);
|
indices.reserve(MAX_INDICES);
|
||||||
for (size_t i = 0; i < MAX_SPRITES; ++i) {
|
for (size_t i = 0; i < MAX_SPRITES; ++i) {
|
||||||
GLuint base = static_cast<GLuint>(i * VERTICES_PER_SPRITE);
|
GLuint base = static_cast<GLuint>(i * VERTICES_PER_SPRITE);
|
||||||
indices.push_back(base + 0);
|
indices.push_back(base + 0);
|
||||||
|
|
@ -114,6 +111,7 @@ bool GLSpriteBatch::init() {
|
||||||
|
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
E2D_LOG_INFO("GLSpriteBatch initialized with capacity for {} sprites", MAX_SPRITES);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,24 +132,26 @@ void GLSpriteBatch::shutdown() {
|
||||||
|
|
||||||
void GLSpriteBatch::begin(const glm::mat4 &viewProjection) {
|
void GLSpriteBatch::begin(const glm::mat4 &viewProjection) {
|
||||||
viewProjection_ = viewProjection;
|
viewProjection_ = viewProjection;
|
||||||
vertices_.clear();
|
vertexCount_ = 0;
|
||||||
currentTexture_ = nullptr;
|
currentTexture_ = nullptr;
|
||||||
currentIsSDF_ = false;
|
currentIsSDF_ = false;
|
||||||
drawCallCount_ = 0;
|
drawCallCount_ = 0;
|
||||||
spriteCount_ = 0;
|
spriteCount_ = 0;
|
||||||
|
batchCount_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) {
|
bool GLSpriteBatch::needsFlush(const Texture& texture, bool isSDF) const {
|
||||||
// 如果纹理改变或缓冲区已满,先 flush
|
if (currentTexture_ == nullptr) {
|
||||||
if (currentTexture_ != nullptr &&
|
return false;
|
||||||
(currentTexture_ != &texture || currentIsSDF_ != data.isSDF ||
|
|
||||||
vertices_.size() >= MAX_SPRITES * VERTICES_PER_SPRITE)) {
|
|
||||||
flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentTexture_ = &texture;
|
// 检查是否需要刷新:纹理改变、SDF 状态改变或缓冲区已满
|
||||||
currentIsSDF_ = data.isSDF;
|
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,
|
glm::vec2 anchorOffset(data.size.x * data.anchor.x,
|
||||||
data.size.y * data.anchor.y);
|
data.size.y * data.anchor.y);
|
||||||
|
|
@ -172,32 +172,87 @@ void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) {
|
||||||
// v0(左上) -- v1(右上)
|
// v0(左上) -- v1(右上)
|
||||||
// | |
|
// | |
|
||||||
// v3(左下) -- v2(右下)
|
// v3(左下) -- v2(右下)
|
||||||
Vertex v0{transform(0, 0), glm::vec2(data.texCoordMin.x, data.texCoordMin.y),
|
Vertex v0{transform(0, 0), glm::vec2(data.texCoordMin.x, data.texCoordMin.y), color};
|
||||||
color};
|
Vertex v1{transform(data.size.x, 0), glm::vec2(data.texCoordMax.x, data.texCoordMin.y), color};
|
||||||
Vertex v1{transform(data.size.x, 0),
|
Vertex v2{transform(data.size.x, data.size.y), glm::vec2(data.texCoordMax.x, data.texCoordMax.y), color};
|
||||||
glm::vec2(data.texCoordMax.x, data.texCoordMin.y), color};
|
Vertex v3{transform(0, data.size.y), glm::vec2(data.texCoordMin.x, data.texCoordMax.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);
|
vertexBuffer_[vertexCount_++] = v0;
|
||||||
vertices_.push_back(v1);
|
vertexBuffer_[vertexCount_++] = v1;
|
||||||
vertices_.push_back(v2);
|
vertexBuffer_[vertexCount_++] = v2;
|
||||||
vertices_.push_back(v3);
|
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_++;
|
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() {
|
void GLSpriteBatch::end() {
|
||||||
if (!vertices_.empty()) {
|
if (vertexCount_ > 0) {
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLSpriteBatch::flush() {
|
void GLSpriteBatch::flush() {
|
||||||
if (vertices_.empty() || currentTexture_ == nullptr)
|
if (vertexCount_ == 0 || currentTexture_ == nullptr) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 绑定纹理
|
// 绑定纹理
|
||||||
GLuint texID = static_cast<GLuint>(
|
GLuint texID = static_cast<GLuint>(
|
||||||
|
|
@ -213,19 +268,23 @@ void GLSpriteBatch::flush() {
|
||||||
shader_.setFloat("uSdfOnEdge", 128.0f / 255.0f);
|
shader_.setFloat("uSdfOnEdge", 128.0f / 255.0f);
|
||||||
shader_.setFloat("uSdfScale", 255.0f / 64.0f);
|
shader_.setFloat("uSdfScale", 255.0f / 64.0f);
|
||||||
|
|
||||||
// 更新 VBO 数据
|
// 更新 VBO 数据 - 只更新实际使用的部分
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, vertices_.size() * sizeof(Vertex),
|
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount_ * sizeof(Vertex), vertexBuffer_.data());
|
||||||
vertices_.data());
|
|
||||||
|
|
||||||
// 绘制
|
// 绘制
|
||||||
glBindVertexArray(vao_);
|
glBindVertexArray(vao_);
|
||||||
GLsizei indexCount = static_cast<GLsizei>(
|
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);
|
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, nullptr);
|
||||||
|
|
||||||
drawCallCount_++;
|
drawCallCount_++;
|
||||||
vertices_.clear();
|
batchCount_++;
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
vertexCount_ = 0;
|
||||||
|
currentTexture_ = nullptr;
|
||||||
|
currentIsSDF_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -80,7 +80,10 @@ static std::string resolveResourcePath(const std::string &filepath) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceManager::ResourceManager() = default;
|
ResourceManager::ResourceManager() = default;
|
||||||
ResourceManager::~ResourceManager() = default;
|
|
||||||
|
ResourceManager::~ResourceManager() {
|
||||||
|
shutdownAsyncLoader();
|
||||||
|
}
|
||||||
|
|
||||||
ResourceManager &ResourceManager::getInstance() {
|
ResourceManager &ResourceManager::getInstance() {
|
||||||
static ResourceManager instance;
|
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) {
|
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_);
|
std::lock_guard<std::mutex> lock(textureMutex_);
|
||||||
|
|
||||||
// 检查缓存
|
// 检查缓存
|
||||||
|
|
@ -120,6 +257,14 @@ Ptr<Texture> ResourceManager::loadTexture(const std::string &filepath) {
|
||||||
return nullptr;
|
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;
|
textureCache_[filepath] = texture;
|
||||||
E2D_LOG_DEBUG("ResourceManager: loaded texture: {}", filepath);
|
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>
|
Ptr<Texture>
|
||||||
ResourceManager::loadTextureWithAlphaMask(const std::string &filepath) {
|
ResourceManager::loadTextureWithAlphaMask(const std::string &filepath) {
|
||||||
// 先加载纹理
|
// 先加载纹理
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
void Node::onEnter() {
|
||||||
running_ = true;
|
running_ = true;
|
||||||
for (auto &child : children_) {
|
for (auto &child : children_) {
|
||||||
|
|
|
||||||
|
|
@ -256,21 +256,21 @@ void ShapeNode::generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||||
|
|
||||||
Vec2 offset = getPosition();
|
Vec2 offset = getPosition();
|
||||||
RenderCommand cmd;
|
RenderCommand cmd;
|
||||||
cmd.zOrder = zOrder;
|
cmd.layer = zOrder;
|
||||||
|
|
||||||
switch (shapeType_) {
|
switch (shapeType_) {
|
||||||
case ShapeType::Point:
|
case ShapeType::Point:
|
||||||
if (!points_.empty()) {
|
if (!points_.empty()) {
|
||||||
cmd.type = RenderCommandType::FilledCircle;
|
cmd.type = RenderCommandType::FilledCircle;
|
||||||
cmd.data =
|
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;
|
break;
|
||||||
|
|
||||||
case ShapeType::Line:
|
case ShapeType::Line:
|
||||||
if (points_.size() >= 2) {
|
if (points_.size() >= 2) {
|
||||||
cmd.type = RenderCommandType::Line;
|
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_};
|
lineWidth_};
|
||||||
}
|
}
|
||||||
break;
|
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,
|
Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x,
|
||||||
points_[2].y - points_[0].y);
|
points_[2].y - points_[0].y);
|
||||||
cmd.data =
|
cmd.data =
|
||||||
RectData{Rect(rect.origin + offset, rect.size), color_, 0.0f};
|
RectCommandData{Rect(rect.origin + offset, rect.size), color_, 0.0f, true};
|
||||||
} else {
|
} else {
|
||||||
cmd.type = RenderCommandType::Rect;
|
cmd.type = RenderCommandType::Rect;
|
||||||
Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x,
|
Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x,
|
||||||
points_[2].y - points_[0].y);
|
points_[2].y - points_[0].y);
|
||||||
cmd.data =
|
cmd.data =
|
||||||
RectData{Rect(rect.origin + offset, rect.size), color_, lineWidth_};
|
RectCommandData{Rect(rect.origin + offset, rect.size), color_, lineWidth_, false};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -299,11 +299,11 @@ void ShapeNode::generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||||
if (filled_) {
|
if (filled_) {
|
||||||
cmd.type = RenderCommandType::FilledCircle;
|
cmd.type = RenderCommandType::FilledCircle;
|
||||||
cmd.data =
|
cmd.data =
|
||||||
CircleData{points_[0] + offset, radius, color_, segments_, 0.0f};
|
CircleCommandData{points_[0] + offset, radius, color_, segments_, 0.0f, true};
|
||||||
} else {
|
} else {
|
||||||
cmd.type = RenderCommandType::Circle;
|
cmd.type = RenderCommandType::Circle;
|
||||||
cmd.data = CircleData{points_[0] + offset, radius, color_, segments_,
|
cmd.data = CircleCommandData{points_[0] + offset, radius, color_, segments_,
|
||||||
lineWidth_};
|
lineWidth_, false};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -315,10 +315,10 @@ void ShapeNode::generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||||
Vec2 p3 = points_[2] + offset;
|
Vec2 p3 = points_[2] + offset;
|
||||||
if (filled_) {
|
if (filled_) {
|
||||||
cmd.type = RenderCommandType::FilledTriangle;
|
cmd.type = RenderCommandType::FilledTriangle;
|
||||||
cmd.data = TriangleData{p1, p2, p3, color_, 0.0f};
|
cmd.data = TriangleCommandData{p1, p2, p3, color_, 0.0f, true};
|
||||||
} else {
|
} else {
|
||||||
cmd.type = RenderCommandType::Triangle;
|
cmd.type = RenderCommandType::Triangle;
|
||||||
cmd.data = TriangleData{p1, p2, p3, color_, lineWidth_};
|
cmd.data = TriangleCommandData{p1, p2, p3, color_, lineWidth_, false};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -333,10 +333,10 @@ void ShapeNode::generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||||
|
|
||||||
if (filled_) {
|
if (filled_) {
|
||||||
cmd.type = RenderCommandType::FilledPolygon;
|
cmd.type = RenderCommandType::FilledPolygon;
|
||||||
cmd.data = PolygonData{transformedPoints, color_, 0.0f};
|
cmd.data = PolygonCommandData{transformedPoints, color_, 0.0f, true};
|
||||||
} else {
|
} else {
|
||||||
cmd.type = RenderCommandType::Polygon;
|
cmd.type = RenderCommandType::Polygon;
|
||||||
cmd.data = PolygonData{transformedPoints, color_, lineWidth_};
|
cmd.data = PolygonCommandData{transformedPoints, color_, lineWidth_, false};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -129,9 +129,9 @@ void Sprite::generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||||
// 创建渲染命令
|
// 创建渲染命令
|
||||||
RenderCommand cmd;
|
RenderCommand cmd;
|
||||||
cmd.type = RenderCommandType::Sprite;
|
cmd.type = RenderCommandType::Sprite;
|
||||||
cmd.zOrder = zOrder;
|
cmd.layer = zOrder;
|
||||||
cmd.data =
|
cmd.data =
|
||||||
SpriteData{texture_, destRect, srcRect, color_, getRotation(), anchor};
|
SpriteCommandData{texture_.get(), destRect, srcRect, color_, getRotation(), anchor, 0};
|
||||||
|
|
||||||
commands.push_back(std::move(cmd));
|
commands.push_back(std::move(cmd));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
#include <extra2d/utils/object_pool.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ObjectPoolManager 单例实现
|
||||||
|
ObjectPoolManager& ObjectPoolManager::getInstance() {
|
||||||
|
static ObjectPoolManager instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -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 指向同一个纹理对象
|
// 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();
|
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`:
|
参考 `examples/push_box/StartScene.cpp`:
|
||||||
|
|
@ -131,17 +271,17 @@ void StartScene::onEnter() {
|
||||||
auto& app = Application::instance();
|
auto& app = Application::instance();
|
||||||
auto& resources = app.resources();
|
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) {
|
if (bgTex) {
|
||||||
auto background = Sprite::create(bgTex);
|
auto background = Sprite::create(bgTex);
|
||||||
background->setAnchor(0.0f, 0.0f);
|
background->setAnchor(0.0f, 0.0f);
|
||||||
addChild(background);
|
addChild(background);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载音效图标纹理
|
// 加载音效图标纹理(异步 + DXT5 压缩支持透明)
|
||||||
auto soundOn = resources.loadTexture("assets/images/soundon.png");
|
auto soundOn = resources.loadTexture("assets/images/soundon.png", true, TextureFormat::DXT5);
|
||||||
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
|
auto soundOff = resources.loadTexture("assets/images/soundoff.png", true, TextureFormat::DXT5);
|
||||||
if (soundOn && soundOff) {
|
if (soundOn && soundOff) {
|
||||||
soundIcon_ = Sprite::create(g_SoundOpen ? soundOn : soundOff);
|
soundIcon_ = Sprite::create(g_SoundOpen ? soundOn : soundOff);
|
||||||
addChild(soundIcon_);
|
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()` 中加载所需资源
|
1. **预加载资源** - 在场景 `onEnter()` 中加载所需资源
|
||||||
2. **检查资源有效性** - 始终检查加载结果是否为 nullptr
|
2. **检查资源有效性** - 始终检查加载结果是否为 nullptr
|
||||||
3. **复用资源** - 多次使用同一资源时保存指针,避免重复加载
|
3. **复用资源** - 多次使用同一资源时保存指针,避免重复加载
|
||||||
4. **合理设置字号** - 字体加载时会生成对应字号的图集
|
4. **合理设置字号** - 字体加载时会生成对应字号的图集
|
||||||
|
5. **使用异步加载** - 对大型资源使用异步加载避免卡顿
|
||||||
|
6. **选择合适的压缩格式** - 根据纹理用途选择最佳压缩格式
|
||||||
|
7. **利用自动批处理** - 相同纹理的精灵会自动批处理,无需手动优化
|
||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue