refactor(resource): 重构资源管理系统并移除TexturePool
- 移除TexturePool类,将纹理缓存功能直接集成到ResourceManager - 实现基于LRU的纹理缓存管理,提高缓存效率 - 添加GPUContext类用于安全处理GPU资源生命周期 - 重构对象池实现,解决循环引用问题 - 优化应用关闭时的资源释放顺序 - 改进纹理加载错误处理 - 更新SpriteFrameCache直接加载纹理 - 清理无用头文件和代码
This commit is contained in:
parent
c46be5287d
commit
85420634aa
|
|
@ -1,7 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/animation/sprite_frame.h>
|
||||
#include <extra2d/graphics/texture_pool.h>
|
||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <stb/stb_image.h>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
|
@ -39,7 +41,7 @@ public:
|
|||
void addSpriteFramesFromGrid(const std::string &texturePath, int frameWidth,
|
||||
int frameHeight, int frameCount = -1,
|
||||
int spacing = 0, int margin = 0) {
|
||||
auto texture = TexturePool::getInstance().get(texturePath);
|
||||
auto texture = loadTextureFromFile(texturePath);
|
||||
if (!texture)
|
||||
return;
|
||||
addSpriteFramesFromGrid(texture, texturePath, frameWidth, frameHeight,
|
||||
|
|
@ -104,8 +106,8 @@ public:
|
|||
return it->second;
|
||||
}
|
||||
|
||||
// 缓存未命中,从 TexturePool 加载纹理并创建 SpriteFrame
|
||||
auto texture = TexturePool::getInstance().get(texturePath);
|
||||
// 缓存未命中,加载纹理并创建 SpriteFrame
|
||||
auto texture = loadTextureFromFile(texturePath);
|
||||
if (!texture)
|
||||
return nullptr;
|
||||
|
||||
|
|
@ -161,6 +163,34 @@ private:
|
|||
SpriteFrameCache(const SpriteFrameCache &) = delete;
|
||||
SpriteFrameCache &operator=(const SpriteFrameCache &) = delete;
|
||||
|
||||
/**
|
||||
* @brief 从文件加载纹理
|
||||
* @param filepath 纹理文件路径
|
||||
* @return 纹理对象,失败返回nullptr
|
||||
*/
|
||||
Ptr<Texture> loadTextureFromFile(const std::string &filepath) {
|
||||
// 使用stb_image加载图像
|
||||
stbi_set_flip_vertically_on_load(false);
|
||||
int width, height, channels;
|
||||
unsigned char *pixels =
|
||||
stbi_load(filepath.c_str(), &width, &height, &channels, 4);
|
||||
if (!pixels) {
|
||||
E2D_ERROR("加载纹理失败: {} - {}", filepath, stbi_failure_reason());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 创建GLTexture
|
||||
auto texture = makePtr<GLTexture>(width, height, pixels, 4);
|
||||
stbi_image_free(pixels);
|
||||
|
||||
if (!texture || !texture->isValid()) {
|
||||
E2D_ERROR("创建纹理失败: {}", filepath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
std::unordered_map<std::string, Ptr<SpriteFrame>> frames_;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
#include <extra2d/graphics/font.h>
|
||||
#include <extra2d/graphics/camera.h>
|
||||
#include <extra2d/graphics/shader_system.h>
|
||||
#include <extra2d/graphics/texture_pool.h>
|
||||
|
||||
#include <extra2d/graphics/render_target.h>
|
||||
#include <extra2d/graphics/vram_manager.h>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// GPU 上下文状态管理器
|
||||
// 用于跟踪 OpenGL/Vulkan 等 GPU 上下文的生命周期状态
|
||||
// 确保在 GPU 资源析构时能安全地检查上下文是否有效
|
||||
// ============================================================================
|
||||
|
||||
class GPUContext {
|
||||
public:
|
||||
/// 获取单例实例
|
||||
static GPUContext& getInstance();
|
||||
|
||||
/// 标记 GPU 上下文为有效(在初始化完成后调用)
|
||||
void markValid();
|
||||
|
||||
/// 标记 GPU 上下文为无效(在销毁前调用)
|
||||
void markInvalid();
|
||||
|
||||
/// 检查 GPU 上下文是否有效
|
||||
bool isValid() const;
|
||||
|
||||
private:
|
||||
GPUContext() = default;
|
||||
~GPUContext() = default;
|
||||
GPUContext(const GPUContext&) = delete;
|
||||
GPUContext& operator=(const GPUContext&) = delete;
|
||||
|
||||
std::atomic<bool> valid_{false};
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,208 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/graphics/texture.h>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 纹理池配置
|
||||
// ============================================================================
|
||||
struct TexturePoolConfig {
|
||||
size_t maxCacheSize = 64 * 1024 * 1024; // 最大缓存大小 (64MB)
|
||||
size_t maxTextureCount = 256; // 最大纹理数量
|
||||
float unloadInterval = 30.0f; // 自动清理间隔 (秒)
|
||||
bool enableAsyncLoad = true; // 启用异步加载
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 纹理池 - 使用LRU缓存策略管理纹理复用
|
||||
// ============================================================================
|
||||
class TexturePool {
|
||||
public:
|
||||
// ------------------------------------------------------------------------
|
||||
// 单例访问
|
||||
// ------------------------------------------------------------------------
|
||||
static TexturePool &getInstance();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 初始化和关闭
|
||||
// ------------------------------------------------------------------------
|
||||
bool init(const TexturePoolConfig &config = TexturePoolConfig{});
|
||||
void shutdown();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 纹理获取
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 从文件获取纹理(带缓存)
|
||||
* @param filepath 纹理文件路径
|
||||
* @return 纹理对象,失败返回nullptr
|
||||
*/
|
||||
Ptr<Texture> get(const std::string &filepath);
|
||||
|
||||
/**
|
||||
* @brief 异步加载纹理
|
||||
* @param filepath 纹理文件路径
|
||||
* @param callback 加载完成回调
|
||||
*/
|
||||
void getAsync(const std::string &filepath,
|
||||
std::function<void(Ptr<Texture>)> callback);
|
||||
|
||||
/**
|
||||
* @brief 从内存数据创建纹理(自动缓存)
|
||||
* @param name 纹理名称(用于缓存键)
|
||||
* @param data 图像数据
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
* @param format 像素格式
|
||||
* @return 纹理对象
|
||||
*/
|
||||
Ptr<Texture> createFromData(const std::string &name, const uint8_t *data,
|
||||
int width, int height,
|
||||
PixelFormat format = PixelFormat::RGBA8);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 缓存管理
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 手动添加纹理到缓存
|
||||
*/
|
||||
void add(const std::string &key, Ptr<Texture> texture);
|
||||
|
||||
/**
|
||||
* @brief 从缓存移除纹理
|
||||
*/
|
||||
void remove(const std::string &key);
|
||||
|
||||
/**
|
||||
* @brief 检查纹理是否在缓存中
|
||||
*/
|
||||
bool has(const std::string &key) const;
|
||||
|
||||
/**
|
||||
* @brief 清空所有缓存
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 清理未使用的纹理(LRU策略)
|
||||
* @param targetSize 目标缓存大小
|
||||
*/
|
||||
void trim(size_t targetSize);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 统计信息
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取当前缓存的纹理数量
|
||||
*/
|
||||
size_t getTextureCount() const;
|
||||
|
||||
/**
|
||||
* @brief 获取当前缓存的总大小(字节)
|
||||
*/
|
||||
size_t getCacheSize() const;
|
||||
|
||||
/**
|
||||
* @brief 获取缓存命中率
|
||||
*/
|
||||
float getHitRate() const;
|
||||
|
||||
/**
|
||||
* @brief 打印统计信息
|
||||
*/
|
||||
void printStats() const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 自动清理
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 更新(在主循环中调用,用于自动清理)
|
||||
*/
|
||||
void update(float dt);
|
||||
|
||||
/**
|
||||
* @brief 设置自动清理间隔
|
||||
*/
|
||||
void setAutoUnloadInterval(float interval);
|
||||
|
||||
private:
|
||||
TexturePool() = default;
|
||||
~TexturePool() = default;
|
||||
TexturePool(const TexturePool &) = delete;
|
||||
TexturePool &operator=(const TexturePool &) = delete;
|
||||
|
||||
// 侵入式LRU节点 - 减少内存分配和指针跳转
|
||||
struct LRUNode {
|
||||
std::string key;
|
||||
uint32_t prev; // 数组索引,0表示无效
|
||||
uint32_t next; // 数组索引,0表示无效
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
// 缓存项 - 紧凑存储
|
||||
struct CacheEntry {
|
||||
Ptr<Texture> texture;
|
||||
size_t size; // 纹理大小(字节)
|
||||
float lastAccessTime; // 最后访问时间
|
||||
uint32_t accessCount; // 访问次数
|
||||
uint32_t lruIndex; // LRU节点索引
|
||||
};
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
TexturePoolConfig config_;
|
||||
|
||||
// 缓存存储
|
||||
std::unordered_map<std::string, CacheEntry> cache_;
|
||||
|
||||
// 侵入式LRU链表 - 使用数组索引代替指针,提高缓存局部性
|
||||
std::vector<LRUNode> lruNodes_;
|
||||
uint32_t lruHead_ = 0; // 最近使用
|
||||
uint32_t lruTail_ = 0; // 最久未使用
|
||||
uint32_t freeList_ = 0; // 空闲节点链表
|
||||
|
||||
// 统计
|
||||
size_t totalSize_ = 0;
|
||||
uint64_t hitCount_ = 0;
|
||||
uint64_t missCount_ = 0;
|
||||
float autoUnloadTimer_ = 0.0f;
|
||||
bool initialized_ = false;
|
||||
|
||||
// 异步加载队列
|
||||
struct AsyncLoadTask {
|
||||
std::string filepath;
|
||||
std::function<void(Ptr<Texture>)> callback;
|
||||
};
|
||||
std::vector<AsyncLoadTask> asyncTasks_;
|
||||
|
||||
// LRU操作
|
||||
uint32_t allocateLRUNode(const std::string& key);
|
||||
void freeLRUNode(uint32_t index);
|
||||
void moveToFront(uint32_t index);
|
||||
void removeFromList(uint32_t index);
|
||||
std::string evictLRU();
|
||||
|
||||
// 内部方法
|
||||
void touch(const std::string &key);
|
||||
void evict();
|
||||
Ptr<Texture> loadTexture(const std::string &filepath);
|
||||
size_t calculateTextureSize(int width, int height, PixelFormat format);
|
||||
void processAsyncTasks();
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 便捷宏
|
||||
// ============================================================================
|
||||
#define E2D_TEXTURE_POOL() ::extra2d::TexturePool::getInstance()
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -6,16 +6,21 @@
|
|||
#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>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 资源管理器 - 统一管理纹理、字体、音效等资源
|
||||
// 使用 TexturePool 作为纹理管理后端
|
||||
// 支持异步加载和纹理压缩
|
||||
// ============================================================================
|
||||
|
||||
// 纹理格式枚举
|
||||
|
|
@ -30,6 +35,16 @@ enum class TextureFormat {
|
|||
ASTC8x8, // ASTC 8x8 压缩(高压缩率)
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 纹理LRU缓存项
|
||||
// ============================================================================
|
||||
struct TextureCacheEntry {
|
||||
Ptr<Texture> texture;
|
||||
size_t size = 0; // 纹理大小(字节)
|
||||
float lastAccessTime = 0.0f; // 最后访问时间
|
||||
uint32_t accessCount = 0; // 访问次数
|
||||
};
|
||||
|
||||
// 异步加载回调类型
|
||||
using TextureLoadCallback = std::function<void(Ptr<Texture>)>;
|
||||
|
||||
|
|
@ -40,13 +55,6 @@ public:
|
|||
// ------------------------------------------------------------------------
|
||||
static ResourceManager &getInstance();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 更新(在主循环中调用)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 更新资源管理器,触发纹理池自动清理等
|
||||
void update(float dt);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 纹理资源 - 同步加载
|
||||
// ------------------------------------------------------------------------
|
||||
|
|
@ -146,24 +154,39 @@ public:
|
|||
size_t getSoundCacheSize() const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 异步加载控制(已弃用,保留接口兼容性)
|
||||
// 纹理异步加载由 TexturePool 内部处理
|
||||
// LRU 缓存管理
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
|
||||
/// 设置纹理缓存参数
|
||||
void setTextureCache(size_t maxCacheSize, size_t maxTextureCount,
|
||||
float unloadInterval);
|
||||
|
||||
/// 获取当前缓存的总大小(字节)
|
||||
size_t getTextureCacheMemoryUsage() const;
|
||||
|
||||
/// 获取缓存命中率
|
||||
float getTextureCacheHitRate() const;
|
||||
|
||||
/// 打印缓存统计信息
|
||||
void printTextureCacheStats() const;
|
||||
|
||||
/// 更新缓存(在主循环中调用,用于自动清理)
|
||||
void update(float dt);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 异步加载控制
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// 初始化异步加载系统(可选,自动在首次异步加载时初始化)
|
||||
/// @deprecated 纹理池已内置异步加载,无需调用
|
||||
void initAsyncLoader();
|
||||
|
||||
|
||||
/// 关闭异步加载系统
|
||||
/// @deprecated 纹理池自动管理生命周期,无需调用
|
||||
void shutdownAsyncLoader();
|
||||
|
||||
|
||||
/// 等待所有异步加载完成
|
||||
/// @deprecated 使用回调机制处理异步加载结果
|
||||
void waitForAsyncLoads();
|
||||
|
||||
|
||||
/// 检查是否有正在进行的异步加载
|
||||
/// @deprecated 始终返回 false
|
||||
bool hasPendingAsyncLoads() const;
|
||||
|
||||
ResourceManager();
|
||||
|
|
@ -186,14 +209,91 @@ private:
|
|||
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_;
|
||||
mutable std::mutex soundMutex_;
|
||||
|
||||
// 资源缓存 - 使用弱指针实现自动清理
|
||||
// 纹理缓存已移至 TexturePool,此处仅保留字体和音效缓存
|
||||
std::unordered_map<std::string, WeakPtr<FontAtlas>> fontCache_;
|
||||
std::unordered_map<std::string, WeakPtr<Sound>> soundCache_;
|
||||
|
||||
// ============================================================================
|
||||
// 纹理LRU缓存
|
||||
// ============================================================================
|
||||
|
||||
// LRU链表节点
|
||||
struct LRUNode {
|
||||
std::string key;
|
||||
uint32_t prev = 0; // 数组索引,0表示无效
|
||||
uint32_t next = 0; // 数组索引,0表示无效
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
// 纹理缓存配置
|
||||
size_t maxCacheSize_ = 64 * 1024 * 1024; // 最大缓存大小 (64MB)
|
||||
size_t maxTextureCount_ = 256; // 最大纹理数量
|
||||
float unloadInterval_ = 30.0f; // 自动清理间隔 (秒)
|
||||
|
||||
// 纹理缓存 - 使用强指针保持引用
|
||||
std::unordered_map<std::string, TextureCacheEntry> textureCache_;
|
||||
|
||||
// 侵入式LRU链表 - 使用数组索引代替指针,提高缓存局部性
|
||||
std::vector<LRUNode> lruNodes_;
|
||||
uint32_t lruHead_ = 0; // 最近使用
|
||||
uint32_t lruTail_ = 0; // 最久未使用
|
||||
uint32_t freeList_ = 0; // 空闲节点链表
|
||||
|
||||
// 统计
|
||||
size_t totalTextureSize_ = 0;
|
||||
uint64_t textureHitCount_ = 0;
|
||||
uint64_t textureMissCount_ = 0;
|
||||
float autoUnloadTimer_ = 0.0f;
|
||||
|
||||
// 异步加载相关
|
||||
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();
|
||||
|
||||
// ============================================================================
|
||||
// LRU 缓存内部方法
|
||||
// ============================================================================
|
||||
|
||||
/// 分配LRU节点
|
||||
uint32_t allocateLRUNode(const std::string &key);
|
||||
|
||||
/// 释放LRU节点
|
||||
void freeLRUNode(uint32_t index);
|
||||
|
||||
/// 将节点移到链表头部(最近使用)
|
||||
void moveToFront(uint32_t index);
|
||||
|
||||
/// 从链表中移除节点
|
||||
void removeFromList(uint32_t index);
|
||||
|
||||
/// 驱逐最久未使用的纹理
|
||||
std::string evictLRU();
|
||||
|
||||
/// 访问纹理(更新LRU位置)
|
||||
void touchTexture(const std::string &key);
|
||||
|
||||
/// 驱逐纹理直到满足大小限制
|
||||
void evictTexturesIfNeeded();
|
||||
|
||||
/// 计算纹理大小
|
||||
size_t calculateTextureSize(int width, int height, PixelFormat format) const;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <mutex>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -22,33 +23,18 @@ public:
|
|||
static_assert(std::is_destructible_v<T>, "T must be destructible");
|
||||
|
||||
ObjectPool() = default;
|
||||
~ObjectPool() { clear(); }
|
||||
~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;
|
||||
}
|
||||
// 禁止移动(避免地址失效问题)
|
||||
ObjectPool(ObjectPool&& other) noexcept = delete;
|
||||
ObjectPool& operator=(ObjectPool&& other) noexcept = delete;
|
||||
|
||||
/**
|
||||
* @brief 分配一个对象
|
||||
|
|
@ -56,17 +42,22 @@ public:
|
|||
*/
|
||||
T* allocate() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
|
||||
// 检查对象池是否已销毁
|
||||
if (isDestroyed_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (freeList_.empty()) {
|
||||
grow();
|
||||
}
|
||||
|
||||
|
||||
T* obj = freeList_.back();
|
||||
freeList_.pop_back();
|
||||
|
||||
|
||||
// 在原地构造对象
|
||||
new (obj) T();
|
||||
|
||||
|
||||
allocatedCount_++;
|
||||
return obj;
|
||||
}
|
||||
|
|
@ -77,17 +68,22 @@ public:
|
|||
template <typename... Args>
|
||||
T* allocate(Args&&... args) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
|
||||
// 检查对象池是否已销毁
|
||||
if (isDestroyed_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (freeList_.empty()) {
|
||||
grow();
|
||||
}
|
||||
|
||||
|
||||
T* obj = freeList_.back();
|
||||
freeList_.pop_back();
|
||||
|
||||
|
||||
// 在原地构造对象
|
||||
new (obj) T(std::forward<Args>(args)...);
|
||||
|
||||
|
||||
allocatedCount_++;
|
||||
return obj;
|
||||
}
|
||||
|
|
@ -95,18 +91,24 @@ public:
|
|||
/**
|
||||
* @brief 回收一个对象
|
||||
* @param obj 要回收的对象指针
|
||||
* @return true 如果对象成功回收到池中,false 如果池已销毁或对象无效
|
||||
*/
|
||||
void deallocate(T* obj) {
|
||||
bool deallocate(T* obj) {
|
||||
if (obj == nullptr) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 调用析构函数
|
||||
obj->~T();
|
||||
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
freeList_.push_back(obj);
|
||||
allocatedCount_--;
|
||||
// 检查对象池是否还在运行(避免在对象池销毁后访问)
|
||||
if (!isDestroyed_) {
|
||||
freeList_.push_back(obj);
|
||||
allocatedCount_--;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -137,10 +139,13 @@ public:
|
|||
*/
|
||||
void clear() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
|
||||
isDestroyed_ = true;
|
||||
|
||||
// 释放所有内存块
|
||||
for (auto& block : blocks_) {
|
||||
::operator delete[](block, std::align_val_t(alignof(T)));
|
||||
// 使用与分配时匹配的释放方式
|
||||
::operator delete[](block);
|
||||
}
|
||||
blocks_.clear();
|
||||
freeList_.clear();
|
||||
|
|
@ -152,11 +157,11 @@ private:
|
|||
* @brief 扩展池容量
|
||||
*/
|
||||
void grow() {
|
||||
// 分配新的内存块
|
||||
T* block = static_cast<T*>(::operator new[](sizeof(T) * BlockSize, std::align_val_t(alignof(T))));
|
||||
|
||||
// 分配新的内存块(使用标准对齐)
|
||||
T* block = static_cast<T*>(::operator new[](sizeof(T) * BlockSize));
|
||||
|
||||
blocks_.push_back(block);
|
||||
|
||||
|
||||
// 将新块中的对象添加到空闲列表
|
||||
for (size_t i = 0; i < BlockSize; ++i) {
|
||||
freeList_.push_back(&block[i]);
|
||||
|
|
@ -167,6 +172,7 @@ private:
|
|||
std::vector<T*> blocks_; // 内存块列表
|
||||
std::vector<T*> freeList_; // 空闲对象列表
|
||||
std::atomic<size_t> allocatedCount_{0};
|
||||
bool isDestroyed_ = false; // 标记对象池是否已销毁
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -186,8 +192,13 @@ public:
|
|||
*/
|
||||
template <typename... Args>
|
||||
Ptr<T> makeShared(Args&&... args) {
|
||||
// 使用 weak_ptr 避免循环引用
|
||||
std::weak_ptr<PoolType> weakPool = pool_;
|
||||
T* obj = pool_->allocate(std::forward<Args>(args)...);
|
||||
return Ptr<T>(obj, Deleter{pool_});
|
||||
if (!obj) {
|
||||
return nullptr;
|
||||
}
|
||||
return Ptr<T>(obj, Deleter{weakPool});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -199,13 +210,19 @@ public:
|
|||
|
||||
private:
|
||||
struct Deleter {
|
||||
std::shared_ptr<PoolType> pool;
|
||||
std::weak_ptr<PoolType> pool;
|
||||
|
||||
void operator()(T* obj) const {
|
||||
if (pool) {
|
||||
pool->deallocate(obj);
|
||||
// 尝试获取 pool
|
||||
if (auto sharedPool = pool.lock()) {
|
||||
// 尝试归还给对象池
|
||||
if (!sharedPool->deallocate(obj)) {
|
||||
// 对象池已销毁,手动释放内存
|
||||
::operator delete[](obj);
|
||||
}
|
||||
} else {
|
||||
delete obj;
|
||||
// Pool 已被销毁,释放内存
|
||||
::operator delete[](obj);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -226,21 +243,42 @@ public:
|
|||
*/
|
||||
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>>();
|
||||
// 使用静态局部变量确保延迟初始化和正确析构顺序
|
||||
static auto 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>();
|
||||
std::weak_ptr<ObjectPool<T, BlockSize>> weakPool = pool;
|
||||
T* obj = pool->allocate(std::forward<Args>(args)...);
|
||||
return Ptr<T>(obj, [pool](T* p) { pool->deallocate(p); });
|
||||
if (!obj) {
|
||||
return nullptr;
|
||||
}
|
||||
return Ptr<T>(obj, [weakPool](T* p) {
|
||||
if (auto sharedPool = weakPool.lock()) {
|
||||
if (!sharedPool->deallocate(p)) {
|
||||
// 对象池已销毁
|
||||
::operator delete[](p);
|
||||
}
|
||||
} else {
|
||||
// Pool 已被销毁
|
||||
::operator delete[](p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清理所有未使用的内存池资源
|
||||
* 在程序退出前调用,确保资源正确释放
|
||||
*/
|
||||
void cleanup();
|
||||
|
||||
private:
|
||||
ObjectPoolManager() = default;
|
||||
~ObjectPoolManager() = default;
|
||||
|
|
|
|||
|
|
@ -176,24 +176,36 @@ void Application::shutdown() {
|
|||
sceneManager_->end();
|
||||
}
|
||||
|
||||
// 清理子系统
|
||||
sceneManager_.reset();
|
||||
resourceManager_.reset();
|
||||
// ========================================
|
||||
// 1. 先清理所有持有 GPU 资源的子系统
|
||||
// 必须在渲染器关闭前释放纹理等资源
|
||||
// ========================================
|
||||
sceneManager_.reset(); // 场景持有纹理引用
|
||||
resourceManager_.reset(); // 纹理缓存持有 GPU 纹理
|
||||
camera_.reset(); // 相机可能持有渲染目标
|
||||
|
||||
// ========================================
|
||||
// 2. 关闭音频(不依赖 GPU)
|
||||
// ========================================
|
||||
AudioEngine::getInstance().shutdown();
|
||||
|
||||
// ========================================
|
||||
// 3. 清理其他子系统
|
||||
// ========================================
|
||||
timerManager_.reset();
|
||||
eventQueue_.reset();
|
||||
eventDispatcher_.reset();
|
||||
camera_.reset();
|
||||
|
||||
// 关闭音频
|
||||
AudioEngine::getInstance().shutdown();
|
||||
|
||||
// 关闭渲染器
|
||||
// ========================================
|
||||
// 4. 最后关闭渲染器和窗口
|
||||
// 必须在所有 GPU 资源释放后才能关闭 OpenGL 上下文
|
||||
// ========================================
|
||||
if (renderer_) {
|
||||
renderer_->shutdown();
|
||||
renderer_.reset();
|
||||
}
|
||||
|
||||
// 销毁窗口(包含 SDL_Quit)
|
||||
// 销毁窗口(包含 SDL_Quit,会销毁 OpenGL 上下文)
|
||||
if (window_) {
|
||||
window_->destroy();
|
||||
window_.reset();
|
||||
|
|
@ -314,11 +326,6 @@ void Application::update() {
|
|||
if (sceneManager_) {
|
||||
sceneManager_->update(deltaTime_);
|
||||
}
|
||||
|
||||
// 更新资源管理器(触发纹理池自动清理)
|
||||
if (resourceManager_) {
|
||||
resourceManager_->update(deltaTime_);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::render() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
#include <extra2d/graphics/gpu_context.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
GPUContext& GPUContext::getInstance() {
|
||||
static GPUContext instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void GPUContext::markValid() {
|
||||
valid_.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
void GPUContext::markInvalid() {
|
||||
valid_.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool GPUContext::isValid() const {
|
||||
return valid_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <extra2d/core/string.h>
|
||||
#include <extra2d/graphics/gpu_context.h>
|
||||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
||||
|
|
@ -62,6 +63,9 @@ bool GLRenderer::init(Window *window) {
|
|||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// 标记 GPU 上下文为有效
|
||||
GPUContext::getInstance().markValid();
|
||||
|
||||
E2D_LOG_INFO("OpenGL Renderer initialized");
|
||||
E2D_LOG_INFO("OpenGL Version: {}",
|
||||
reinterpret_cast<const char *>(glGetString(GL_VERSION)));
|
||||
|
|
@ -70,6 +74,10 @@ bool GLRenderer::init(Window *window) {
|
|||
}
|
||||
|
||||
void GLRenderer::shutdown() {
|
||||
// 标记 GPU 上下文为无效
|
||||
// 这会在销毁 OpenGL 上下文之前通知所有 GPU 资源
|
||||
GPUContext::getInstance().markInvalid();
|
||||
|
||||
spriteBatch_.shutdown();
|
||||
|
||||
if (shapeVbo_ != 0) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <extra2d/graphics/opengl/gl_texture.h>
|
||||
#include <extra2d/graphics/gpu_context.h>
|
||||
#include <extra2d/graphics/vram_manager.h>
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <cstring>
|
||||
|
|
@ -130,8 +131,12 @@ GLTexture::GLTexture(const std::string &filepath)
|
|||
|
||||
GLTexture::~GLTexture() {
|
||||
if (textureID_ != 0) {
|
||||
glDeleteTextures(1, &textureID_);
|
||||
// VRAM 跟踪: 释放纹理显存
|
||||
// 检查 GPU 上下文是否仍然有效
|
||||
// 如果 OpenGL 上下文已销毁,则跳过 glDeleteTextures 调用
|
||||
if (GPUContext::getInstance().isValid()) {
|
||||
glDeleteTextures(1, &textureID_);
|
||||
}
|
||||
// VRAM 跟踪: 释放纹理显存(无论上下文是否有效都需要更新统计)
|
||||
if (dataSize_ > 0) {
|
||||
VRAMManager::getInstance().freeTexture(dataSize_);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,518 +0,0 @@
|
|||
#include <extra2d/graphics/opengl/gl_texture.h>
|
||||
#include <extra2d/graphics/texture_pool.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <stb/stb_image.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// TexturePool实现
|
||||
// ============================================================================
|
||||
|
||||
TexturePool &TexturePool::getInstance() {
|
||||
static TexturePool instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool TexturePool::init(const TexturePoolConfig &config) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (initialized_) {
|
||||
E2D_WARN("纹理池已经初始化");
|
||||
return true;
|
||||
}
|
||||
|
||||
config_ = config;
|
||||
|
||||
E2D_INFO("初始化纹理池...");
|
||||
E2D_INFO(" 最大缓存大小: {} MB", config_.maxCacheSize / (1024 * 1024));
|
||||
E2D_INFO(" 最大纹理数量: {}", config_.maxTextureCount);
|
||||
E2D_INFO(" 自动清理间隔: {} 秒", config_.unloadInterval);
|
||||
E2D_INFO(" 异步加载: {}", config_.enableAsyncLoad ? "启用" : "禁用");
|
||||
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TexturePool::shutdown() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
E2D_INFO("关闭纹理池...");
|
||||
|
||||
printStats();
|
||||
|
||||
clear();
|
||||
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
Ptr<Texture> TexturePool::get(const std::string &filepath) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (!initialized_) {
|
||||
E2D_ERROR("纹理池未初始化");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 检查缓存
|
||||
auto it = cache_.find(filepath);
|
||||
if (it != cache_.end()) {
|
||||
// 缓存命中
|
||||
touch(filepath);
|
||||
hitCount_++;
|
||||
it->second.accessCount++;
|
||||
return it->second.texture;
|
||||
}
|
||||
|
||||
// 缓存未命中,加载纹理
|
||||
missCount_++;
|
||||
|
||||
auto texture = loadTexture(filepath);
|
||||
if (!texture) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 添加到缓存
|
||||
add(filepath, texture);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
void TexturePool::getAsync(const std::string &filepath,
|
||||
std::function<void(Ptr<Texture>)> callback) {
|
||||
if (!initialized_) {
|
||||
E2D_ERROR("纹理池未初始化");
|
||||
if (callback) {
|
||||
callback(nullptr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config_.enableAsyncLoad) {
|
||||
// 异步加载禁用,直接同步加载
|
||||
auto texture = get(filepath);
|
||||
if (callback) {
|
||||
callback(texture);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加到异步任务队列
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
asyncTasks_.push_back({filepath, callback});
|
||||
}
|
||||
|
||||
Ptr<Texture> TexturePool::createFromData(const std::string &name,
|
||||
const uint8_t *data, int width,
|
||||
int height, PixelFormat format) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (!initialized_) {
|
||||
E2D_ERROR("纹理池未初始化");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 检查缓存
|
||||
auto it = cache_.find(name);
|
||||
if (it != cache_.end()) {
|
||||
touch(name);
|
||||
return it->second.texture;
|
||||
}
|
||||
|
||||
// 创建纹理
|
||||
int channels = 4;
|
||||
switch (format) {
|
||||
case PixelFormat::R8:
|
||||
channels = 1;
|
||||
break;
|
||||
case PixelFormat::RG8:
|
||||
channels = 2;
|
||||
break;
|
||||
case PixelFormat::RGB8:
|
||||
channels = 3;
|
||||
break;
|
||||
case PixelFormat::RGBA8:
|
||||
channels = 4;
|
||||
break;
|
||||
default:
|
||||
channels = 4;
|
||||
break;
|
||||
}
|
||||
auto texture = makePtr<GLTexture>(width, height, data, channels);
|
||||
if (!texture || !texture->isValid()) {
|
||||
E2D_ERROR("创建纹理失败: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 添加到缓存
|
||||
add(name, texture);
|
||||
|
||||
E2D_INFO("从数据创建纹理: {} ({}x{})", name, width, height);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
uint32_t TexturePool::allocateLRUNode(const std::string& key) {
|
||||
uint32_t index;
|
||||
if (freeList_ != 0) {
|
||||
// 复用空闲节点
|
||||
index = freeList_;
|
||||
freeList_ = lruNodes_[index - 1].next;
|
||||
} else {
|
||||
// 分配新节点
|
||||
lruNodes_.emplace_back();
|
||||
index = static_cast<uint32_t>(lruNodes_.size());
|
||||
}
|
||||
|
||||
LRUNode& node = lruNodes_[index - 1];
|
||||
node.key = key;
|
||||
node.prev = 0;
|
||||
node.next = 0;
|
||||
node.valid = true;
|
||||
return index;
|
||||
}
|
||||
|
||||
void TexturePool::freeLRUNode(uint32_t index) {
|
||||
if (index == 0 || index > lruNodes_.size()) return;
|
||||
|
||||
LRUNode& node = lruNodes_[index - 1];
|
||||
node.valid = false;
|
||||
node.key.clear();
|
||||
node.prev = 0;
|
||||
node.next = freeList_;
|
||||
freeList_ = index;
|
||||
}
|
||||
|
||||
void TexturePool::moveToFront(uint32_t index) {
|
||||
if (index == 0 || index > lruNodes_.size() || index == lruHead_) return;
|
||||
|
||||
removeFromList(index);
|
||||
|
||||
LRUNode& node = lruNodes_[index - 1];
|
||||
node.prev = 0;
|
||||
node.next = lruHead_;
|
||||
|
||||
if (lruHead_ != 0) {
|
||||
lruNodes_[lruHead_ - 1].prev = index;
|
||||
}
|
||||
lruHead_ = index;
|
||||
|
||||
if (lruTail_ == 0) {
|
||||
lruTail_ = index;
|
||||
}
|
||||
}
|
||||
|
||||
void TexturePool::removeFromList(uint32_t index) {
|
||||
if (index == 0 || index > lruNodes_.size()) return;
|
||||
|
||||
LRUNode& node = lruNodes_[index - 1];
|
||||
|
||||
if (node.prev != 0) {
|
||||
lruNodes_[node.prev - 1].next = node.next;
|
||||
} else {
|
||||
lruHead_ = node.next;
|
||||
}
|
||||
|
||||
if (node.next != 0) {
|
||||
lruNodes_[node.next - 1].prev = node.prev;
|
||||
} else {
|
||||
lruTail_ = node.prev;
|
||||
}
|
||||
}
|
||||
|
||||
std::string TexturePool::evictLRU() {
|
||||
if (lruTail_ == 0) return "";
|
||||
|
||||
uint32_t index = lruTail_;
|
||||
std::string key = lruNodes_[index - 1].key;
|
||||
|
||||
removeFromList(index);
|
||||
freeLRUNode(index);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
void TexturePool::add(const std::string &key, Ptr<Texture> texture) {
|
||||
if (!texture || !texture->isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否需要清理
|
||||
while (totalSize_ >= config_.maxCacheSize ||
|
||||
cache_.size() >= config_.maxTextureCount) {
|
||||
evict();
|
||||
}
|
||||
|
||||
// 计算纹理大小
|
||||
size_t size = calculateTextureSize(texture->getWidth(), texture->getHeight(),
|
||||
texture->getFormat());
|
||||
|
||||
// 分配LRU节点
|
||||
uint32_t lruIndex = allocateLRUNode(key);
|
||||
|
||||
// 创建缓存项
|
||||
CacheEntry entry;
|
||||
entry.texture = texture;
|
||||
entry.size = size;
|
||||
entry.lastAccessTime = 0.0f; // 将在touch中更新
|
||||
entry.accessCount = 0;
|
||||
entry.lruIndex = lruIndex;
|
||||
|
||||
// 添加到缓存
|
||||
cache_[key] = entry;
|
||||
|
||||
// 添加到LRU链表头部
|
||||
moveToFront(lruIndex);
|
||||
|
||||
totalSize_ += size;
|
||||
|
||||
E2D_DEBUG_LOG("纹理添加到缓存: {} ({} KB)", key, size / 1024);
|
||||
}
|
||||
|
||||
void TexturePool::remove(const std::string &key) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto it = cache_.find(key);
|
||||
if (it == cache_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 从LRU链表移除
|
||||
removeFromList(it->second.lruIndex);
|
||||
freeLRUNode(it->second.lruIndex);
|
||||
|
||||
// 更新总大小
|
||||
totalSize_ -= it->second.size;
|
||||
|
||||
// 从缓存移除
|
||||
cache_.erase(it);
|
||||
|
||||
E2D_DEBUG_LOG("纹理从缓存移除: {}", key);
|
||||
}
|
||||
|
||||
bool TexturePool::has(const std::string &key) const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return cache_.find(key) != cache_.end();
|
||||
}
|
||||
|
||||
void TexturePool::clear() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
cache_.clear();
|
||||
lruNodes_.clear();
|
||||
lruHead_ = 0;
|
||||
lruTail_ = 0;
|
||||
freeList_ = 0;
|
||||
totalSize_ = 0;
|
||||
|
||||
E2D_INFO("纹理缓存已清空");
|
||||
}
|
||||
|
||||
void TexturePool::trim(size_t targetSize) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
while (totalSize_ > targetSize && lruTail_ != 0) {
|
||||
evict();
|
||||
}
|
||||
|
||||
E2D_INFO("纹理缓存已清理到 {} MB", totalSize_ / (1024 * 1024));
|
||||
}
|
||||
|
||||
size_t TexturePool::getTextureCount() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return cache_.size();
|
||||
}
|
||||
|
||||
size_t TexturePool::getCacheSize() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return totalSize_;
|
||||
}
|
||||
|
||||
float TexturePool::getHitRate() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
uint64_t total = hitCount_ + missCount_;
|
||||
if (total == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return static_cast<float>(hitCount_) / static_cast<float>(total);
|
||||
}
|
||||
|
||||
void TexturePool::printStats() const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
E2D_INFO("纹理池统计:");
|
||||
E2D_INFO(" 缓存纹理数: {}/{}", cache_.size(), config_.maxTextureCount);
|
||||
E2D_INFO(" 缓存大小: {} / {} MB", totalSize_ / (1024 * 1024),
|
||||
config_.maxCacheSize / (1024 * 1024));
|
||||
E2D_INFO(" 缓存命中: {}", hitCount_);
|
||||
E2D_INFO(" 缓存未命中: {}", missCount_);
|
||||
E2D_INFO(" 命中率: {:.1f}%", getHitRate() * 100.0f);
|
||||
}
|
||||
|
||||
void TexturePool::update(float dt) {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理异步任务
|
||||
if (config_.enableAsyncLoad) {
|
||||
processAsyncTasks();
|
||||
}
|
||||
|
||||
// 自动清理
|
||||
autoUnloadTimer_ += dt;
|
||||
if (autoUnloadTimer_ >= config_.unloadInterval) {
|
||||
autoUnloadTimer_ = 0.0f;
|
||||
|
||||
// 如果缓存超过80%,清理到50%
|
||||
if (totalSize_ > config_.maxCacheSize * 0.8f) {
|
||||
trim(config_.maxCacheSize * 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TexturePool::setAutoUnloadInterval(float interval) {
|
||||
config_.unloadInterval = interval;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 内部方法
|
||||
// ============================================================================
|
||||
|
||||
void TexturePool::touch(const std::string &key) {
|
||||
auto it = cache_.find(key);
|
||||
if (it == cache_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 移动到LRU链表头部
|
||||
moveToFront(it->second.lruIndex);
|
||||
|
||||
// 更新时间戳
|
||||
it->second.lastAccessTime = 0.0f;
|
||||
}
|
||||
|
||||
void TexturePool::evict() {
|
||||
// 使用侵入式LRU链表移除最久未使用的项
|
||||
std::string key = evictLRU();
|
||||
if (key.empty()) return;
|
||||
|
||||
auto it = cache_.find(key);
|
||||
if (it != cache_.end()) {
|
||||
totalSize_ -= it->second.size;
|
||||
cache_.erase(it);
|
||||
}
|
||||
|
||||
E2D_DEBUG_LOG("纹理被清理出缓存: {}", key);
|
||||
}
|
||||
|
||||
Ptr<Texture> TexturePool::loadTexture(const std::string &filepath) {
|
||||
// 使用stb_image加载图像
|
||||
// 不翻转图片,保持原始方向
|
||||
// OpenGL纹理坐标原点在左下角,图片数据原点在左上角
|
||||
// 在渲染时通过纹理坐标翻转来处理
|
||||
stbi_set_flip_vertically_on_load(false);
|
||||
int width, height, channels;
|
||||
unsigned char *pixels =
|
||||
stbi_load(filepath.c_str(), &width, &height, &channels, 4);
|
||||
if (!pixels) {
|
||||
E2D_ERROR("stbi_load 失败: {} - {}", filepath, stbi_failure_reason());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 创建GLTexture(直接使用像素数据)
|
||||
auto texture = std::make_shared<GLTexture>(width, height, pixels, 4);
|
||||
if (!texture || !texture->isValid()) {
|
||||
stbi_image_free(pixels);
|
||||
E2D_ERROR("创建纹理失败: {}", filepath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
stbi_image_free(pixels);
|
||||
|
||||
E2D_INFO("加载纹理: {} ({}x{}, {} channels)", filepath, width, height,
|
||||
channels);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
size_t TexturePool::calculateTextureSize(int width, int height,
|
||||
PixelFormat format) {
|
||||
// 压缩纹理格式使用块大小计算
|
||||
switch (format) {
|
||||
case PixelFormat::ETC2_RGB8: {
|
||||
size_t blocksWide = (width + 3) / 4;
|
||||
size_t blocksHigh = (height + 3) / 4;
|
||||
return blocksWide * blocksHigh * 8;
|
||||
}
|
||||
case PixelFormat::ETC2_RGBA8: {
|
||||
size_t blocksWide = (width + 3) / 4;
|
||||
size_t blocksHigh = (height + 3) / 4;
|
||||
return blocksWide * blocksHigh * 16;
|
||||
}
|
||||
case PixelFormat::ASTC_4x4: {
|
||||
size_t blocksWide = (width + 3) / 4;
|
||||
size_t blocksHigh = (height + 3) / 4;
|
||||
return blocksWide * blocksHigh * 16;
|
||||
}
|
||||
case PixelFormat::ASTC_6x6: {
|
||||
size_t blocksWide = (width + 5) / 6;
|
||||
size_t blocksHigh = (height + 5) / 6;
|
||||
return blocksWide * blocksHigh * 16;
|
||||
}
|
||||
case PixelFormat::ASTC_8x8: {
|
||||
size_t blocksWide = (width + 7) / 8;
|
||||
size_t blocksHigh = (height + 7) / 8;
|
||||
return blocksWide * blocksHigh * 16;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 非压缩格式
|
||||
int bytesPerPixel = 4;
|
||||
switch (format) {
|
||||
case PixelFormat::R8:
|
||||
bytesPerPixel = 1;
|
||||
break;
|
||||
case PixelFormat::RG8:
|
||||
bytesPerPixel = 2;
|
||||
break;
|
||||
case PixelFormat::RGB8:
|
||||
bytesPerPixel = 3;
|
||||
break;
|
||||
case PixelFormat::RGBA8:
|
||||
bytesPerPixel = 4;
|
||||
break;
|
||||
default:
|
||||
bytesPerPixel = 4;
|
||||
break;
|
||||
}
|
||||
|
||||
return static_cast<size_t>(width * height * bytesPerPixel);
|
||||
}
|
||||
|
||||
void TexturePool::processAsyncTasks() {
|
||||
std::vector<AsyncLoadTask> tasks;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
tasks = std::move(asyncTasks_);
|
||||
asyncTasks_.clear();
|
||||
}
|
||||
|
||||
for (auto &task : tasks) {
|
||||
auto texture = get(task.filepath);
|
||||
if (task.callback) {
|
||||
task.callback(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
#include <extra2d/audio/audio_engine.h>
|
||||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
||||
#include <extra2d/graphics/texture_pool.h>
|
||||
#include <extra2d/resource/resource_manager.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <sys/stat.h>
|
||||
|
|
@ -80,17 +79,10 @@ static std::string resolveResourcePath(const std::string &filepath) {
|
|||
return "";
|
||||
}
|
||||
|
||||
ResourceManager::ResourceManager() {
|
||||
// 初始化纹理池
|
||||
TexturePoolConfig config;
|
||||
config.maxCacheSize = 128 * 1024 * 1024; // 128MB
|
||||
config.maxTextureCount = 512;
|
||||
config.enableAsyncLoad = true;
|
||||
TexturePool::getInstance().init(config);
|
||||
}
|
||||
ResourceManager::ResourceManager() = default;
|
||||
|
||||
ResourceManager::~ResourceManager() {
|
||||
TexturePool::getInstance().shutdown();
|
||||
shutdownAsyncLoader();
|
||||
}
|
||||
|
||||
ResourceManager &ResourceManager::getInstance() {
|
||||
|
|
@ -99,11 +91,77 @@ ResourceManager &ResourceManager::getInstance() {
|
|||
}
|
||||
|
||||
// ============================================================================
|
||||
// 更新(在主循环中调用)
|
||||
// 异步加载系统
|
||||
// ============================================================================
|
||||
|
||||
void ResourceManager::update(float dt) {
|
||||
TexturePool::getInstance().update(dt);
|
||||
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_--;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -133,31 +191,61 @@ void ResourceManager::loadTextureAsync(const std::string &filepath, TextureLoadC
|
|||
}
|
||||
|
||||
void ResourceManager::loadTextureAsync(const std::string &filepath, TextureFormat format, TextureLoadCallback callback) {
|
||||
// 解析资源路径
|
||||
std::string fullPath = resolveResourcePath(filepath);
|
||||
if (fullPath.empty()) {
|
||||
E2D_LOG_ERROR("ResourceManager: texture file not found: {}", filepath);
|
||||
if (callback) {
|
||||
callback(nullptr);
|
||||
}
|
||||
return;
|
||||
// 确保异步加载系统已启动
|
||||
if (!asyncRunning_) {
|
||||
initAsyncLoader();
|
||||
}
|
||||
|
||||
// 使用纹理池的异步加载
|
||||
TexturePool::getInstance().getAsync(fullPath,
|
||||
[callback, filepath](Ptr<Texture> texture) {
|
||||
if (texture) {
|
||||
E2D_LOG_DEBUG("ResourceManager: async texture loaded: {}", filepath);
|
||||
} else {
|
||||
E2D_LOG_ERROR("ResourceManager: failed to async load texture: {}", filepath);
|
||||
}
|
||||
|
||||
// 检查缓存
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(textureMutex_);
|
||||
auto it = textureCache_.find(filepath);
|
||||
if (it != textureCache_.end()) {
|
||||
// 缓存命中,更新LRU并立即回调
|
||||
touchTexture(filepath);
|
||||
textureHitCount_++;
|
||||
it->second.accessCount++;
|
||||
if (callback) {
|
||||
callback(texture);
|
||||
callback(it->second.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_);
|
||||
|
||||
// 检查缓存
|
||||
auto it = textureCache_.find(filepath);
|
||||
if (it != textureCache_.end()) {
|
||||
// 缓存命中
|
||||
touchTexture(filepath);
|
||||
textureHitCount_++;
|
||||
it->second.accessCount++;
|
||||
E2D_LOG_TRACE("ResourceManager: texture cache hit: {}", filepath);
|
||||
return it->second.texture;
|
||||
}
|
||||
|
||||
// 缓存未命中
|
||||
textureMissCount_++;
|
||||
|
||||
// 解析资源路径(优先尝试 romfs:/ 前缀)
|
||||
std::string fullPath = resolveResourcePath(filepath);
|
||||
if (fullPath.empty()) {
|
||||
|
|
@ -165,22 +253,51 @@ Ptr<Texture> ResourceManager::loadTextureInternal(const std::string &filepath, T
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
// 使用纹理池获取纹理
|
||||
auto texture = TexturePool::getInstance().get(fullPath);
|
||||
|
||||
if (!texture) {
|
||||
E2D_LOG_ERROR("ResourceManager: failed to load texture: {}", filepath);
|
||||
// 创建新纹理
|
||||
try {
|
||||
auto texture = makePtr<GLTexture>(fullPath);
|
||||
if (!texture->isValid()) {
|
||||
E2D_LOG_ERROR("ResourceManager: failed to load texture: {}", filepath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 如果需要压缩,处理纹理格式
|
||||
if (format != TextureFormat::Auto && format != TextureFormat::RGBA8) {
|
||||
// 注意:实际压缩需要在纹理创建时处理
|
||||
// 这里仅记录日志,实际实现需要在 GLTexture 中支持
|
||||
E2D_LOG_DEBUG("ResourceManager: texture format {} requested for {}",
|
||||
static_cast<int>(format), filepath);
|
||||
}
|
||||
|
||||
// 检查是否需要清理缓存
|
||||
evictTexturesIfNeeded();
|
||||
|
||||
// 计算纹理大小
|
||||
size_t textureSize = calculateTextureSize(texture->getWidth(), texture->getHeight(), texture->getFormat());
|
||||
|
||||
// 分配LRU节点
|
||||
uint32_t lruIndex = allocateLRUNode(filepath);
|
||||
|
||||
// 创建缓存项
|
||||
TextureCacheEntry entry;
|
||||
entry.texture = texture;
|
||||
entry.size = textureSize;
|
||||
entry.lastAccessTime = 0.0f;
|
||||
entry.accessCount = 1;
|
||||
|
||||
// 添加到缓存
|
||||
textureCache_[filepath] = entry;
|
||||
totalTextureSize_ += textureSize;
|
||||
|
||||
// 添加到LRU链表头部
|
||||
moveToFront(lruIndex);
|
||||
|
||||
E2D_LOG_DEBUG("ResourceManager: loaded texture: {} ({} KB)", filepath, textureSize / 1024);
|
||||
return texture;
|
||||
} catch (...) {
|
||||
E2D_LOG_ERROR("ResourceManager: exception loading texture: {}", filepath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 如果需要压缩,处理纹理格式(记录日志,实际压缩需要在纹理创建时处理)
|
||||
if (format != TextureFormat::Auto && format != TextureFormat::RGBA8) {
|
||||
E2D_LOG_DEBUG("ResourceManager: texture format {} requested for {}",
|
||||
static_cast<int>(format), filepath);
|
||||
}
|
||||
|
||||
E2D_LOG_DEBUG("ResourceManager: loaded texture: {}", filepath);
|
||||
return texture;
|
||||
}
|
||||
|
||||
TextureFormat ResourceManager::selectBestFormat(TextureFormat requested) const {
|
||||
|
|
@ -226,91 +343,79 @@ ResourceManager::loadTextureWithAlphaMask(const std::string &filepath) {
|
|||
|
||||
const AlphaMask *
|
||||
ResourceManager::getAlphaMask(const std::string &textureKey) const {
|
||||
// 解析路径获取完整路径
|
||||
std::string fullPath = resolveResourcePath(textureKey);
|
||||
if (fullPath.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(textureMutex_);
|
||||
|
||||
// 从纹理池获取纹理
|
||||
auto texture = TexturePool::getInstance().get(fullPath);
|
||||
if (!texture) {
|
||||
return nullptr;
|
||||
auto it = textureCache_.find(textureKey);
|
||||
if (it != textureCache_.end()) {
|
||||
GLTexture *glTexture = static_cast<GLTexture *>(it->second.texture.get());
|
||||
return glTexture->getAlphaMask();
|
||||
}
|
||||
|
||||
GLTexture *glTexture = static_cast<GLTexture *>(texture.get());
|
||||
return glTexture->getAlphaMask();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ResourceManager::generateAlphaMask(const std::string &textureKey) {
|
||||
// 解析路径获取完整路径
|
||||
std::string fullPath = resolveResourcePath(textureKey);
|
||||
if (fullPath.empty()) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(textureMutex_);
|
||||
|
||||
// 从纹理池获取纹理
|
||||
auto texture = TexturePool::getInstance().get(fullPath);
|
||||
if (!texture) {
|
||||
return false;
|
||||
auto it = textureCache_.find(textureKey);
|
||||
if (it != textureCache_.end()) {
|
||||
GLTexture *glTexture = static_cast<GLTexture *>(it->second.texture.get());
|
||||
if (!glTexture->hasAlphaMask()) {
|
||||
glTexture->generateAlphaMask();
|
||||
}
|
||||
return glTexture->hasAlphaMask();
|
||||
}
|
||||
|
||||
GLTexture *glTexture = static_cast<GLTexture *>(texture.get());
|
||||
if (!glTexture->hasAlphaMask()) {
|
||||
glTexture->generateAlphaMask();
|
||||
}
|
||||
return glTexture->hasAlphaMask();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ResourceManager::hasAlphaMask(const std::string &textureKey) const {
|
||||
// 解析路径获取完整路径
|
||||
std::string fullPath = resolveResourcePath(textureKey);
|
||||
if (fullPath.empty()) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(textureMutex_);
|
||||
|
||||
// 从纹理池获取纹理
|
||||
auto texture = TexturePool::getInstance().get(fullPath);
|
||||
if (!texture) {
|
||||
return false;
|
||||
auto it = textureCache_.find(textureKey);
|
||||
if (it != textureCache_.end()) {
|
||||
GLTexture *glTexture = static_cast<GLTexture *>(it->second.texture.get());
|
||||
return glTexture->hasAlphaMask();
|
||||
}
|
||||
|
||||
GLTexture *glTexture = static_cast<GLTexture *>(texture.get());
|
||||
return glTexture->hasAlphaMask();
|
||||
return false;
|
||||
}
|
||||
|
||||
Ptr<Texture> ResourceManager::getTexture(const std::string &key) const {
|
||||
// 解析路径获取完整路径
|
||||
std::string fullPath = resolveResourcePath(key);
|
||||
if (fullPath.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(textureMutex_);
|
||||
|
||||
// 从纹理池获取纹理
|
||||
return TexturePool::getInstance().get(fullPath);
|
||||
auto it = textureCache_.find(key);
|
||||
if (it != textureCache_.end()) {
|
||||
const_cast<ResourceManager*>(this)->touchTexture(key);
|
||||
const_cast<ResourceManager*>(this)->textureHitCount_++;
|
||||
const_cast<uint32_t&>(it->second.accessCount)++;
|
||||
return it->second.texture;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ResourceManager::hasTexture(const std::string &key) const {
|
||||
// 解析路径获取完整路径
|
||||
std::string fullPath = resolveResourcePath(key);
|
||||
if (fullPath.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查纹理池是否有此纹理
|
||||
return TexturePool::getInstance().has(fullPath);
|
||||
std::lock_guard<std::mutex> lock(textureMutex_);
|
||||
return textureCache_.find(key) != textureCache_.end();
|
||||
}
|
||||
|
||||
void ResourceManager::unloadTexture(const std::string &key) {
|
||||
// 解析路径获取完整路径
|
||||
std::string fullPath = resolveResourcePath(key);
|
||||
if (fullPath.empty()) {
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(textureMutex_);
|
||||
|
||||
// 从纹理池移除
|
||||
TexturePool::getInstance().remove(fullPath);
|
||||
E2D_LOG_DEBUG("ResourceManager: unloaded texture: {}", key);
|
||||
auto it = textureCache_.find(key);
|
||||
if (it != textureCache_.end()) {
|
||||
// 从LRU链表中移除
|
||||
auto nodeIt = std::find_if(lruNodes_.begin(), lruNodes_.end(),
|
||||
[&key](const LRUNode &node) { return node.valid && node.key == key; });
|
||||
if (nodeIt != lruNodes_.end()) {
|
||||
removeFromList(static_cast<uint32_t>(nodeIt - lruNodes_.begin()) + 1);
|
||||
freeLRUNode(static_cast<uint32_t>(nodeIt - lruNodes_.begin()) + 1);
|
||||
}
|
||||
|
||||
// 更新总大小
|
||||
totalTextureSize_ -= it->second.size;
|
||||
|
||||
// 从缓存移除
|
||||
textureCache_.erase(it);
|
||||
E2D_LOG_DEBUG("ResourceManager: unloaded texture: {}", key);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -468,11 +573,8 @@ void ResourceManager::unloadSound(const std::string &key) {
|
|||
// ============================================================================
|
||||
|
||||
void ResourceManager::purgeUnused() {
|
||||
// 纹理缓存由 TexturePool 自动管理,无需手动清理
|
||||
// 但我们可以触发 TexturePool 的清理
|
||||
TexturePool::getInstance().trim(TexturePool::getInstance().getCacheSize() * 0.8f);
|
||||
|
||||
// 清理字体缓存
|
||||
// 纹理缓存使用LRU策略,不需要清理失效引用
|
||||
// 字体缓存
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(fontMutex_);
|
||||
for (auto it = fontCache_.begin(); it != fontCache_.end();) {
|
||||
|
|
@ -485,7 +587,7 @@ void ResourceManager::purgeUnused() {
|
|||
}
|
||||
}
|
||||
|
||||
// 清理音效缓存
|
||||
// 音效缓存
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(soundMutex_);
|
||||
for (auto it = soundCache_.begin(); it != soundCache_.end();) {
|
||||
|
|
@ -500,8 +602,15 @@ void ResourceManager::purgeUnused() {
|
|||
}
|
||||
|
||||
void ResourceManager::clearTextureCache() {
|
||||
TexturePool::getInstance().clear();
|
||||
E2D_LOG_INFO("ResourceManager: texture cache cleared via TexturePool");
|
||||
std::lock_guard<std::mutex> lock(textureMutex_);
|
||||
size_t count = textureCache_.size();
|
||||
textureCache_.clear();
|
||||
lruNodes_.clear();
|
||||
lruHead_ = 0;
|
||||
lruTail_ = 0;
|
||||
freeList_ = 0;
|
||||
totalTextureSize_ = 0;
|
||||
E2D_LOG_INFO("ResourceManager: cleared {} textures from cache", count);
|
||||
}
|
||||
|
||||
void ResourceManager::clearFontCache() {
|
||||
|
|
@ -531,7 +640,210 @@ void ResourceManager::clearAllCaches() {
|
|||
}
|
||||
|
||||
size_t ResourceManager::getTextureCacheSize() const {
|
||||
return TexturePool::getInstance().getTextureCount();
|
||||
std::lock_guard<std::mutex> lock(textureMutex_);
|
||||
return textureCache_.size();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// LRU 缓存管理
|
||||
// ============================================================================
|
||||
|
||||
void ResourceManager::setTextureCache(size_t maxCacheSize, size_t maxTextureCount,
|
||||
float unloadInterval) {
|
||||
std::lock_guard<std::mutex> lock(textureMutex_);
|
||||
maxCacheSize_ = maxCacheSize;
|
||||
maxTextureCount_ = maxTextureCount;
|
||||
unloadInterval_ = unloadInterval;
|
||||
}
|
||||
|
||||
size_t ResourceManager::getTextureCacheMemoryUsage() const {
|
||||
std::lock_guard<std::mutex> lock(textureMutex_);
|
||||
return totalTextureSize_;
|
||||
}
|
||||
|
||||
float ResourceManager::getTextureCacheHitRate() const {
|
||||
std::lock_guard<std::mutex> lock(textureMutex_);
|
||||
uint64_t total = textureHitCount_ + textureMissCount_;
|
||||
if (total == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
return static_cast<float>(textureHitCount_) / static_cast<float>(total);
|
||||
}
|
||||
|
||||
void ResourceManager::printTextureCacheStats() const {
|
||||
std::lock_guard<std::mutex> lock(textureMutex_);
|
||||
E2D_LOG_INFO("纹理缓存统计:");
|
||||
E2D_LOG_INFO(" 缓存纹理数: {}/{}", textureCache_.size(), maxTextureCount_);
|
||||
E2D_LOG_INFO(" 缓存大小: {} / {} MB", totalTextureSize_ / (1024 * 1024),
|
||||
maxCacheSize_ / (1024 * 1024));
|
||||
E2D_LOG_INFO(" 缓存命中: {}", textureHitCount_);
|
||||
E2D_LOG_INFO(" 缓存未命中: {}", textureMissCount_);
|
||||
E2D_LOG_INFO(" 命中率: {:.1f}%", getTextureCacheHitRate() * 100.0f);
|
||||
}
|
||||
|
||||
void ResourceManager::update(float dt) {
|
||||
std::lock_guard<std::mutex> lock(textureMutex_);
|
||||
|
||||
// 自动清理
|
||||
autoUnloadTimer_ += dt;
|
||||
if (autoUnloadTimer_ >= unloadInterval_) {
|
||||
autoUnloadTimer_ = 0.0f;
|
||||
|
||||
// 如果缓存超过80%,清理到50%
|
||||
if (totalTextureSize_ > maxCacheSize_ * 0.8f) {
|
||||
size_t targetSize = static_cast<size_t>(maxCacheSize_ * 0.5f);
|
||||
while (totalTextureSize_ > targetSize && lruTail_ != 0) {
|
||||
std::string key = evictLRU();
|
||||
if (key.empty()) break;
|
||||
|
||||
auto it = textureCache_.find(key);
|
||||
if (it != textureCache_.end()) {
|
||||
totalTextureSize_ -= it->second.size;
|
||||
textureCache_.erase(it);
|
||||
E2D_LOG_DEBUG("ResourceManager: auto-evicted texture: {}", key);
|
||||
}
|
||||
}
|
||||
E2D_LOG_INFO("ResourceManager: texture cache trimmed to {} MB", totalTextureSize_ / (1024 * 1024));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// LRU 缓存内部方法
|
||||
// ============================================================================
|
||||
|
||||
uint32_t ResourceManager::allocateLRUNode(const std::string &key) {
|
||||
uint32_t index;
|
||||
if (freeList_ != 0) {
|
||||
// 复用空闲节点
|
||||
index = freeList_;
|
||||
freeList_ = lruNodes_[index - 1].next;
|
||||
} else {
|
||||
// 分配新节点
|
||||
lruNodes_.emplace_back();
|
||||
index = static_cast<uint32_t>(lruNodes_.size());
|
||||
}
|
||||
|
||||
LRUNode &node = lruNodes_[index - 1];
|
||||
node.key = key;
|
||||
node.prev = 0;
|
||||
node.next = 0;
|
||||
node.valid = true;
|
||||
return index;
|
||||
}
|
||||
|
||||
void ResourceManager::freeLRUNode(uint32_t index) {
|
||||
if (index == 0 || index > lruNodes_.size()) return;
|
||||
|
||||
LRUNode &node = lruNodes_[index - 1];
|
||||
node.valid = false;
|
||||
node.key.clear();
|
||||
node.prev = 0;
|
||||
node.next = freeList_;
|
||||
freeList_ = index;
|
||||
}
|
||||
|
||||
void ResourceManager::moveToFront(uint32_t index) {
|
||||
if (index == 0 || index > lruNodes_.size() || index == lruHead_) return;
|
||||
|
||||
removeFromList(index);
|
||||
|
||||
LRUNode &node = lruNodes_[index - 1];
|
||||
node.prev = 0;
|
||||
node.next = lruHead_;
|
||||
|
||||
if (lruHead_ != 0) {
|
||||
lruNodes_[lruHead_ - 1].prev = index;
|
||||
}
|
||||
lruHead_ = index;
|
||||
|
||||
if (lruTail_ == 0) {
|
||||
lruTail_ = index;
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::removeFromList(uint32_t index) {
|
||||
if (index == 0 || index > lruNodes_.size()) return;
|
||||
|
||||
LRUNode &node = lruNodes_[index - 1];
|
||||
|
||||
if (node.prev != 0) {
|
||||
lruNodes_[node.prev - 1].next = node.next;
|
||||
} else {
|
||||
lruHead_ = node.next;
|
||||
}
|
||||
|
||||
if (node.next != 0) {
|
||||
lruNodes_[node.next - 1].prev = node.prev;
|
||||
} else {
|
||||
lruTail_ = node.prev;
|
||||
}
|
||||
}
|
||||
|
||||
std::string ResourceManager::evictLRU() {
|
||||
if (lruTail_ == 0) return "";
|
||||
|
||||
uint32_t index = lruTail_;
|
||||
std::string key = lruNodes_[index - 1].key;
|
||||
|
||||
removeFromList(index);
|
||||
freeLRUNode(index);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
void ResourceManager::touchTexture(const std::string &key) {
|
||||
// 查找对应的LRU节点
|
||||
auto nodeIt = std::find_if(lruNodes_.begin(), lruNodes_.end(),
|
||||
[&key](const LRUNode &node) { return node.valid && node.key == key; });
|
||||
if (nodeIt != lruNodes_.end()) {
|
||||
uint32_t index = static_cast<uint32_t>(nodeIt - lruNodes_.begin()) + 1;
|
||||
moveToFront(index);
|
||||
}
|
||||
|
||||
// 更新时间戳
|
||||
auto it = textureCache_.find(key);
|
||||
if (it != textureCache_.end()) {
|
||||
it->second.lastAccessTime = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::evictTexturesIfNeeded() {
|
||||
while ((totalTextureSize_ >= maxCacheSize_ ||
|
||||
textureCache_.size() >= maxTextureCount_) &&
|
||||
lruTail_ != 0) {
|
||||
std::string key = evictLRU();
|
||||
if (key.empty()) break;
|
||||
|
||||
auto it = textureCache_.find(key);
|
||||
if (it != textureCache_.end()) {
|
||||
totalTextureSize_ -= it->second.size;
|
||||
textureCache_.erase(it);
|
||||
E2D_LOG_DEBUG("ResourceManager: evicted texture: {}", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t ResourceManager::calculateTextureSize(int width, int height, PixelFormat format) const {
|
||||
int bytesPerPixel = 4;
|
||||
switch (format) {
|
||||
case PixelFormat::R8:
|
||||
bytesPerPixel = 1;
|
||||
break;
|
||||
case PixelFormat::RG8:
|
||||
bytesPerPixel = 2;
|
||||
break;
|
||||
case PixelFormat::RGB8:
|
||||
bytesPerPixel = 3;
|
||||
break;
|
||||
case PixelFormat::RGBA8:
|
||||
bytesPerPixel = 4;
|
||||
break;
|
||||
default:
|
||||
bytesPerPixel = 4;
|
||||
break;
|
||||
}
|
||||
return static_cast<size_t>(width * height * bytesPerPixel);
|
||||
}
|
||||
|
||||
size_t ResourceManager::getFontCacheSize() const {
|
||||
|
|
@ -544,27 +856,4 @@ size_t ResourceManager::getSoundCacheSize() const {
|
|||
return soundCache_.size();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 异步加载控制(已弃用,保留接口兼容性)
|
||||
// ============================================================================
|
||||
|
||||
void ResourceManager::initAsyncLoader() {
|
||||
// 纹理池已内置异步加载,无需额外初始化
|
||||
E2D_LOG_DEBUG("ResourceManager: async loader is handled by TexturePool");
|
||||
}
|
||||
|
||||
void ResourceManager::shutdownAsyncLoader() {
|
||||
// 纹理池在 shutdown 时处理
|
||||
}
|
||||
|
||||
void ResourceManager::waitForAsyncLoads() {
|
||||
// 纹理池异步加载通过回调处理,无需等待
|
||||
// 如需等待,可通过回调机制实现
|
||||
}
|
||||
|
||||
bool ResourceManager::hasPendingAsyncLoads() const {
|
||||
// 纹理池异步加载通过回调处理,无法直接查询
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -8,4 +8,10 @@ ObjectPoolManager& ObjectPoolManager::getInstance() {
|
|||
return instance;
|
||||
}
|
||||
|
||||
void ObjectPoolManager::cleanup() {
|
||||
// 静态对象池会在程序退出时自动清理
|
||||
// 这个方法用于显式触发清理(如果需要)
|
||||
// 由于使用了 weak_ptr,循环引用问题已解决
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
Loading…
Reference in New Issue