feat(renderer): 添加渲染性能优化和资源热重载系统
- 新增 AssetFileSystem 封装文件系统操作,支持 RomFS - 新增 AssetAsyncLoader 实现异步资源加载 - 新增 AssetDependencyTracker 跟踪资源依赖关系 - 新增 AssetHotReloader 实现纹理和着色器热重载 - 优化 CommandQueue 性能:添加缓存机制,减少冗余状态切换 - 扩展 RenderGraph::execute 支持传递总时间参数 - 改进渲染统计信息,添加性能分析数据 - 重构 AssetsModule 使用新的子系统,提高代码可维护性
This commit is contained in:
parent
4a902134dc
commit
41817c9e8a
|
|
@ -1,12 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <assets/asset_storage.h>
|
||||
#include <assets/async/asset_async_loader.h>
|
||||
#include <assets/deps/asset_dependency_tracker.h>
|
||||
#include <assets/hot_reload/asset_hot_reloader.h>
|
||||
#include <assets/io/asset_file_system.h>
|
||||
#include <assets/asset_loader.h>
|
||||
#include <assets/handle.h>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <event/events.h>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <module/module.h>
|
||||
|
|
@ -18,7 +20,6 @@
|
|||
#include <renderer/texture.h>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -239,38 +240,15 @@ public:
|
|||
void setHotReloadInterval(float interval);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 资源文件监控信息
|
||||
*/
|
||||
struct FileWatchInfo {
|
||||
std::string path;
|
||||
std::filesystem::file_time_type lastWriteTime;
|
||||
Handle<Texture> textureHandle;
|
||||
Handle<Shader> shaderHandle;
|
||||
bool isTexture = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 添加文件监控
|
||||
*/
|
||||
void addFileWatch(const std::string &path, Handle<Texture> handle);
|
||||
void addFileWatch(const std::string &path, Handle<Shader> handle);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 内部实现:添加文件监控(减少重复代码)
|
||||
*/
|
||||
void addFileWatchInternal(const std::string &path, FileWatchInfo &&info);
|
||||
|
||||
/**
|
||||
* @brief 重新加载纹理
|
||||
*/
|
||||
void reloadTexture(const FileWatchInfo &info);
|
||||
void reloadTexture(const AssetHotReloader::FileWatchInfo &info);
|
||||
|
||||
/**
|
||||
* @brief 重新加载着色器
|
||||
*/
|
||||
void reloadShader(const FileWatchInfo &info);
|
||||
void reloadShader(const AssetHotReloader::FileWatchInfo &info);
|
||||
|
||||
public:
|
||||
//===========================================================================
|
||||
|
|
@ -307,11 +285,8 @@ private:
|
|||
Handle<Material> defaultMaterial_;
|
||||
Handle<Mesh> defaultQuad_;
|
||||
|
||||
// 热重载
|
||||
bool hotReloadEnabled_ = false;
|
||||
float hotReloadInterval_ = 1.0f;
|
||||
float hotReloadTimer_ = 0.0f;
|
||||
std::vector<FileWatchInfo> fileWatchList_;
|
||||
AssetFileSystem fileSystem_;
|
||||
AssetHotReloader hotReloader_;
|
||||
|
||||
// 线程安全
|
||||
mutable std::shared_mutex mutex_;
|
||||
|
|
@ -326,9 +301,6 @@ private:
|
|||
// 异步加载系统
|
||||
//===========================================================================
|
||||
public:
|
||||
/**
|
||||
* @brief 异步加载任务
|
||||
*/
|
||||
struct LoadTask {
|
||||
enum class Type { Texture, Shader };
|
||||
enum class Priority { Low = 0, Normal = 1, High = 2 };
|
||||
|
|
@ -363,18 +335,7 @@ public:
|
|||
void processAsyncCallbacks();
|
||||
|
||||
private:
|
||||
// 异步加载队列
|
||||
std::vector<LoadTask> loadQueue_;
|
||||
std::vector<std::thread> workerThreads_;
|
||||
std::mutex queueMutex_;
|
||||
std::condition_variable queueCV_;
|
||||
std::atomic<bool> asyncLoaderRunning_{false};
|
||||
|
||||
// 完成的回调队列(主线程处理)
|
||||
std::vector<std::function<void()>> completedCallbacks_;
|
||||
std::mutex callbackMutex_;
|
||||
|
||||
void workerThreadLoop();
|
||||
AssetAsyncLoader asyncLoader_;
|
||||
|
||||
//===========================================================================
|
||||
// 资源依赖跟踪
|
||||
|
|
@ -412,10 +373,7 @@ public:
|
|||
void notifyShaderReloaded(Handle<Shader> shader);
|
||||
|
||||
private:
|
||||
// 资源依赖映射
|
||||
std::unordered_map<uint32_t, DependencyInfo> textureDependencies_;
|
||||
std::unordered_map<uint32_t, DependencyInfo> shaderDependencies_;
|
||||
std::shared_mutex dependencyMutex_;
|
||||
AssetDependencyTracker dependencyTracker_;
|
||||
};
|
||||
|
||||
// 全局访问
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class AssetAsyncLoader {
|
||||
public:
|
||||
enum class Priority { Low = 0, Normal = 1, High = 2 };
|
||||
|
||||
struct Task {
|
||||
Priority priority = Priority::Normal;
|
||||
std::function<void()> work;
|
||||
std::function<void()> onComplete;
|
||||
};
|
||||
|
||||
~AssetAsyncLoader();
|
||||
|
||||
void init(uint32_t threadCount = 0);
|
||||
void shutdown();
|
||||
bool isRunning() const;
|
||||
void submit(const Task &task);
|
||||
void processCallbacks();
|
||||
|
||||
private:
|
||||
void workerThreadLoop();
|
||||
|
||||
std::vector<Task> queue_;
|
||||
std::vector<std::thread> workers_;
|
||||
std::mutex queueMutex_;
|
||||
std::condition_variable queueCV_;
|
||||
std::atomic<bool> running_{false};
|
||||
|
||||
std::vector<std::function<void()>> completedCallbacks_;
|
||||
std::mutex callbackMutex_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include <assets/handle.h>
|
||||
#include <renderer/material.h>
|
||||
#include <renderer/shader.h>
|
||||
#include <renderer/texture.h>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class AssetDependencyTracker {
|
||||
public:
|
||||
struct DependencyInfo {
|
||||
Handle<Texture> texture;
|
||||
Handle<Shader> shader;
|
||||
std::vector<Handle<Material>> dependentMaterials;
|
||||
};
|
||||
|
||||
void clear();
|
||||
void registerMaterialDependency(Handle<Material> material,
|
||||
Handle<Texture> texture);
|
||||
void registerMaterialDependency(Handle<Material> material,
|
||||
Handle<Shader> shader);
|
||||
void notifyTextureReloaded(
|
||||
Handle<Texture> texture,
|
||||
const std::function<void(Handle<Material>)> &onMaterialUpdate) const;
|
||||
void notifyShaderReloaded(
|
||||
Handle<Shader> shader,
|
||||
const std::function<void(Handle<Material>)> &onMaterialUpdate) const;
|
||||
|
||||
private:
|
||||
std::unordered_map<uint32_t, DependencyInfo> textureDependencies_;
|
||||
std::unordered_map<uint32_t, DependencyInfo> shaderDependencies_;
|
||||
mutable std::shared_mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include <assets/handle.h>
|
||||
#include <assets/io/asset_file_system.h>
|
||||
#include <renderer/shader.h>
|
||||
#include <renderer/texture.h>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class AssetHotReloader {
|
||||
public:
|
||||
struct FileWatchInfo {
|
||||
std::string path;
|
||||
std::filesystem::file_time_type lastWriteTime;
|
||||
Handle<Texture> textureHandle;
|
||||
Handle<Shader> shaderHandle;
|
||||
bool isTexture = false;
|
||||
};
|
||||
|
||||
explicit AssetHotReloader(const AssetFileSystem &fileSystem);
|
||||
|
||||
void enable(bool enable);
|
||||
bool enabled() const;
|
||||
void setInterval(float interval);
|
||||
void addFileWatch(const std::string &path, Handle<Texture> handle);
|
||||
void addFileWatch(const std::string &path, Handle<Shader> handle);
|
||||
void clear();
|
||||
void checkForChanges(
|
||||
const std::function<void(const FileWatchInfo &)> &reloadTexture,
|
||||
const std::function<void(const FileWatchInfo &)> &reloadShader);
|
||||
|
||||
private:
|
||||
void addFileWatchInternal(const std::string &path, FileWatchInfo &&info);
|
||||
|
||||
const AssetFileSystem &fileSystem_;
|
||||
bool enabled_ = false;
|
||||
float interval_ = 1.0f;
|
||||
std::vector<FileWatchInfo> watchList_;
|
||||
mutable std::shared_mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class AssetFileSystem {
|
||||
public:
|
||||
std::string assetPath(const std::string &path) const;
|
||||
bool isRomfsPath(const std::string &path) const;
|
||||
bool exists(const std::string &path) const;
|
||||
std::string readString(const std::string &path) const;
|
||||
std::filesystem::file_time_type lastWriteTime(const std::string &path) const;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <assets/asset_loader.h>
|
||||
#include <assets/io/asset_file_system.h>
|
||||
#include <renderer/shader.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -12,6 +13,8 @@ namespace extra2d {
|
|||
*/
|
||||
class ShaderLoader : public AssetLoader<Shader> {
|
||||
public:
|
||||
ShaderLoader() = default;
|
||||
explicit ShaderLoader(AssetFileSystem fileSystem);
|
||||
/**
|
||||
* @brief 从文件加载着色器
|
||||
* @param path 单个文件路径(自动推断 .vert/.frag)
|
||||
|
|
@ -50,6 +53,9 @@ public:
|
|||
std::vector<std::string> getExtensions() const override {
|
||||
return {".glsl", ".vert", ".frag"};
|
||||
}
|
||||
|
||||
private:
|
||||
AssetFileSystem fileSystem_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@ private:
|
|||
std::vector<DrawCommand> commands_; // 命令缓冲区(复用)
|
||||
std::vector<uint32_t> sortedIndices_; // 排序索引缓冲区(复用)
|
||||
uint32_t commandCount_ = 0; // 当前命令数量
|
||||
bool needsSort_ = false;
|
||||
static constexpr uint32_t INITIAL_CAPACITY = 1024; // 初始容量
|
||||
};
|
||||
|
||||
|
|
@ -239,6 +240,14 @@ private:
|
|||
*/
|
||||
class CommandQueue {
|
||||
public:
|
||||
struct ProfilingStats {
|
||||
uint32_t commandCount = 0;
|
||||
uint32_t batchCount = 0;
|
||||
float sortMs = 0.0f;
|
||||
float batchMs = 0.0f;
|
||||
float executeMs = 0.0f;
|
||||
float totalMs = 0.0f;
|
||||
};
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
|
|
@ -286,7 +295,8 @@ public:
|
|||
* @param color 颜色
|
||||
*/
|
||||
void submitDraw(Ptr<Material> material, Ptr<Mesh> mesh,
|
||||
const struct Transform &transform, const Color &color);
|
||||
const struct Transform &transform, const Color &color,
|
||||
uint32_t sortKey = 0);
|
||||
|
||||
/**
|
||||
* @brief 提交清除命令
|
||||
|
|
@ -321,6 +331,7 @@ public:
|
|||
* @param frameIndex 当前帧索引(用于双缓冲)
|
||||
*/
|
||||
void updateGlobalUBO(const Mat4& viewProjection, float deltaTime,
|
||||
float totalTime,
|
||||
uint32_t screenWidth, uint32_t screenHeight, uint32_t frameIndex);
|
||||
|
||||
/**
|
||||
|
|
@ -340,6 +351,7 @@ public:
|
|||
* @return 渲染统计
|
||||
*/
|
||||
const RenderStats& getStats() const { return stats_; }
|
||||
const ProfilingStats &getProfilingStats() const { return profilingStats_; }
|
||||
|
||||
/**
|
||||
* @brief 重置统计信息
|
||||
|
|
@ -357,6 +369,7 @@ private:
|
|||
|
||||
// 渲染统计
|
||||
RenderStats stats_;
|
||||
ProfilingStats profilingStats_;
|
||||
|
||||
// 全局 UBO 数据 - 必须与着色器中的 std140 布局完全匹配
|
||||
// layout(std140, binding = 0) uniform GlobalUBO {
|
||||
|
|
|
|||
|
|
@ -238,7 +238,8 @@ public:
|
|||
* @param deltaTime 帧时间
|
||||
* @param viewProjection 视图投影矩阵
|
||||
*/
|
||||
void execute(float deltaTime, const Mat4& viewProjection = Mat4(1.0f));
|
||||
void execute(float deltaTime, const Mat4& viewProjection = Mat4(1.0f),
|
||||
float totalTime = 0.0f);
|
||||
|
||||
/**
|
||||
* @brief 获取纹理资源
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ private:
|
|||
* @param viewProj 视图投影矩阵
|
||||
*/
|
||||
void onRenderSetCamera(const Mat4 &viewProj);
|
||||
void onUpdate(float dt);
|
||||
|
||||
/**
|
||||
* @brief 渲染结束事件处理
|
||||
|
|
@ -199,6 +200,7 @@ private:
|
|||
events::OnRenderSubmit::Listener onRenderSubmitListener_;
|
||||
events::OnRenderSetCamera::Listener onRenderSetCameraListener_;
|
||||
events::OnRenderEnd::Listener onRenderEndListener_;
|
||||
events::OnUpdate::Listener onUpdateListener_;
|
||||
events::OnResize::Listener onResizeListener_;
|
||||
events::OnShow::Listener onShowListener_;
|
||||
|
||||
|
|
@ -216,6 +218,10 @@ private:
|
|||
uint32 commandsSubmitted = 0; // 提交的命令数
|
||||
uint32 drawCalls = 0; // 绘制调用次数
|
||||
uint32 batches = 0; // 批次数
|
||||
float queueSortMs = 0.0f;
|
||||
float queueBatchMs = 0.0f;
|
||||
float queueExecuteMs = 0.0f;
|
||||
float queueTotalMs = 0.0f;
|
||||
} stats_;
|
||||
|
||||
//===========================================================================
|
||||
|
|
@ -236,6 +242,10 @@ private:
|
|||
//===========================================================================
|
||||
|
||||
Mat4 viewProjectionMatrix_; // 当前视图投影矩阵
|
||||
float frameDeltaTime_ = 1.0f / 60.0f;
|
||||
float renderTotalTime_ = 0.0f;
|
||||
Material *defaultMaterialPtr_ = nullptr;
|
||||
Mesh *defaultMeshPtr_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include <glad/glad.h>
|
||||
#include <renderer/rhi/rhi.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -89,6 +91,7 @@ public:
|
|||
void setUniform(const char* name, const Mat4& value) override;
|
||||
|
||||
private:
|
||||
GLint getUniformLocation(GLuint program, const char *name);
|
||||
// 状态缓存机制 - 避免冗余的 OpenGL 状态切换
|
||||
struct StateCache {
|
||||
// 管线状态
|
||||
|
|
@ -150,6 +153,8 @@ private:
|
|||
|
||||
bool recording_ = false;
|
||||
StateCache stateCache_;
|
||||
std::unordered_map<GLuint, std::unordered_map<std::string, GLint>>
|
||||
uniformLocationCache_;
|
||||
|
||||
// 统计信息(调试用)
|
||||
struct Stats {
|
||||
|
|
|
|||
|
|
@ -4,69 +4,14 @@
|
|||
#include <assets/loaders/texture_loader.h>
|
||||
#include <event/events.h>
|
||||
#include <filesystem>
|
||||
#include <module/module_registry.h>
|
||||
#include <platform/file_module.h>
|
||||
#include <thread>
|
||||
#include <utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief 获取 FileModule 实例
|
||||
*/
|
||||
FileModule *getFileModule() { return getModule<FileModule>(); }
|
||||
|
||||
/**
|
||||
* @brief 检查路径是否为 RomFS 路径
|
||||
*/
|
||||
bool isRomfsPath(const std::string &path) {
|
||||
FileModule *fileModule = getFileModule();
|
||||
if (fileModule) {
|
||||
return fileModule->isRomfsPath(path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查文件是否存在(使用 FileModule)
|
||||
*/
|
||||
bool fileExists(const std::string &path) {
|
||||
FileModule *fileModule = getFileModule();
|
||||
if (fileModule) {
|
||||
return fileModule->exists(path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取文件内容为字符串(使用 FileModule,支持 RomFS)
|
||||
*/
|
||||
std::string readFileToString(const std::string &path) {
|
||||
FileModule *fileModule = getFileModule();
|
||||
if (fileModule) {
|
||||
return fileModule->readString(path);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取文件最后写入时间(支持 RomFS 路径)
|
||||
*/
|
||||
std::filesystem::file_time_type getLastWriteTime(const std::string &path) {
|
||||
if (isRomfsPath(path)) {
|
||||
// RomFS 文件是只读的,返回一个固定时间
|
||||
return std::filesystem::file_time_type::min();
|
||||
}
|
||||
return std::filesystem::last_write_time(path);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
AssetsModule *g_assetsModule = nullptr;
|
||||
|
||||
AssetsModule::AssetsModule() { g_assetsModule = this; }
|
||||
AssetsModule::AssetsModule() : hotReloader_(fileSystem_) { g_assetsModule = this; }
|
||||
|
||||
AssetsModule::~AssetsModule() {
|
||||
if (g_assetsModule == this) {
|
||||
|
|
@ -78,7 +23,7 @@ bool AssetsModule::init() {
|
|||
E2D_INFO("资源模块正在初始化...");
|
||||
|
||||
textureLoader_ = std::make_unique<TextureLoader>();
|
||||
shaderLoader_ = std::make_unique<ShaderLoader>();
|
||||
shaderLoader_ = std::make_unique<ShaderLoader>(fileSystem_);
|
||||
|
||||
// 监听窗口显示事件,在 OpenGL 上下文创建完成后创建默认资源
|
||||
onShowListener_ = std::make_unique<events::OnShow::Listener>();
|
||||
|
|
@ -125,15 +70,10 @@ void AssetsModule::shutdown() {
|
|||
|
||||
texturePathCache_.clear();
|
||||
shaderPathCache_.clear();
|
||||
fileWatchList_.clear();
|
||||
}
|
||||
|
||||
// 清空依赖关系
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
textureDependencies_.clear();
|
||||
shaderDependencies_.clear();
|
||||
}
|
||||
hotReloader_.clear();
|
||||
dependencyTracker_.clear();
|
||||
|
||||
textureLoader_.reset();
|
||||
shaderLoader_.reset();
|
||||
|
|
@ -191,8 +131,8 @@ Handle<Texture> AssetsModule::load<Texture>(const std::string &path) {
|
|||
E2D_DEBUG("已加载纹理: {} -> 句柄索引 {}", path, handle.index());
|
||||
|
||||
// 如果启用了热重载,添加文件监控
|
||||
if (hotReloadEnabled_) {
|
||||
addFileWatch(path, handle);
|
||||
if (hotReloader_.enabled()) {
|
||||
hotReloader_.addFileWatch(path, handle);
|
||||
}
|
||||
|
||||
return handle;
|
||||
|
|
@ -272,8 +212,8 @@ template <> Handle<Shader> AssetsModule::load<Shader>(const std::string &path) {
|
|||
E2D_DEBUG("已加载着色器: {} -> 句柄索引 {}", path, handle.index());
|
||||
|
||||
// 如果启用了热重载,添加文件监控
|
||||
if (hotReloadEnabled_) {
|
||||
addFileWatch(path, handle);
|
||||
if (hotReloader_.enabled()) {
|
||||
hotReloader_.addFileWatch(path, handle);
|
||||
}
|
||||
|
||||
return handle;
|
||||
|
|
@ -326,9 +266,9 @@ Handle<Shader> AssetsModule::load<Shader>(const std::string &vertPath,
|
|||
handle.index());
|
||||
|
||||
// 如果启用了热重载,添加文件监控
|
||||
if (hotReloadEnabled_) {
|
||||
addFileWatch(vertPath, handle);
|
||||
addFileWatch(fragPath, handle);
|
||||
if (hotReloader_.enabled()) {
|
||||
hotReloader_.addFileWatch(vertPath, handle);
|
||||
hotReloader_.addFileWatch(fragPath, handle);
|
||||
}
|
||||
|
||||
return handle;
|
||||
|
|
@ -346,7 +286,7 @@ AssetsModule::loadDir<Texture>(const std::string &directory,
|
|||
|
||||
try {
|
||||
std::filesystem::path dirPath(directory);
|
||||
if (!fileExists(directory)) {
|
||||
if (!fileSystem_.exists(directory)) {
|
||||
E2D_WARN("目录未找到: {}", directory);
|
||||
return handles;
|
||||
}
|
||||
|
|
@ -396,7 +336,7 @@ template <>
|
|||
void AssetsModule::loadAsync<Texture>(
|
||||
const std::string &path, std::function<void(Handle<Texture>)> callback) {
|
||||
// 确保异步加载系统已初始化
|
||||
if (!asyncLoaderRunning_) {
|
||||
if (!asyncLoader_.isRunning()) {
|
||||
initAsyncLoader();
|
||||
}
|
||||
|
||||
|
|
@ -414,7 +354,7 @@ template <>
|
|||
void AssetsModule::loadAsync<Shader>(
|
||||
const std::string &path, std::function<void(Handle<Shader>)> callback) {
|
||||
// 确保异步加载系统已初始化
|
||||
if (!asyncLoaderRunning_) {
|
||||
if (!asyncLoader_.isRunning()) {
|
||||
initAsyncLoader();
|
||||
}
|
||||
|
||||
|
|
@ -464,22 +404,17 @@ bool AssetsModule::createDefaultResources() {
|
|||
{
|
||||
// 从文件加载默认着色器
|
||||
// 使用 FileModule 的 assetPath 来获取正确的资源路径(支持 RomFS)
|
||||
FileModule *fileModule = getFileModule();
|
||||
std::string vertPath = fileModule
|
||||
? fileModule->assetPath("shader/default.vert")
|
||||
: "shader/default.vert";
|
||||
std::string fragPath = fileModule
|
||||
? fileModule->assetPath("shader/default.frag")
|
||||
: "shader/default.frag";
|
||||
std::string vertPath = fileSystem_.assetPath("shader/default.vert");
|
||||
std::string fragPath = fileSystem_.assetPath("shader/default.frag");
|
||||
|
||||
if (!fileExists(vertPath) || !fileExists(fragPath)) {
|
||||
if (!fileSystem_.exists(vertPath) || !fileSystem_.exists(fragPath)) {
|
||||
E2D_ERROR("默认着色器文件未找到: {}, {}", vertPath, fragPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 读取着色器文件内容
|
||||
std::string vsSource = readFileToString(vertPath);
|
||||
std::string fsSource = readFileToString(fragPath);
|
||||
std::string vsSource = fileSystem_.readString(vertPath);
|
||||
std::string fsSource = fileSystem_.readString(fragPath);
|
||||
if (vsSource.empty() || fsSource.empty()) {
|
||||
E2D_ERROR("读取默认着色器文件失败: {}, {}", vertPath, fragPath);
|
||||
return false;
|
||||
|
|
@ -577,7 +512,7 @@ Mesh *AssetsModule::getDefaultQuadPtr() { return meshes_.get(defaultQuad_); }
|
|||
//===========================================================================
|
||||
|
||||
void AssetsModule::enableHotReload(bool enable) {
|
||||
hotReloadEnabled_ = enable;
|
||||
hotReloader_.enable(enable);
|
||||
if (enable) {
|
||||
E2D_INFO("热重载已启用");
|
||||
} else {
|
||||
|
|
@ -586,45 +521,10 @@ void AssetsModule::enableHotReload(bool enable) {
|
|||
}
|
||||
|
||||
void AssetsModule::setHotReloadInterval(float interval) {
|
||||
hotReloadInterval_ = interval;
|
||||
hotReloader_.setInterval(interval);
|
||||
}
|
||||
|
||||
void AssetsModule::addFileWatchInternal(const std::string &path,
|
||||
FileWatchInfo &&info) {
|
||||
try {
|
||||
if (!fileExists(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
info.path = path;
|
||||
info.lastWriteTime = getLastWriteTime(path);
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
fileWatchList_.push_back(std::move(info));
|
||||
const char *type = fileWatchList_.back().isTexture ? "纹理" : "着色器";
|
||||
E2D_DEBUG("正在监控 {} 文件: {}", type, path);
|
||||
} catch (const std::exception &e) {
|
||||
E2D_ERROR("添加文件监控失败 {}: {}", path, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void AssetsModule::addFileWatch(const std::string &path,
|
||||
Handle<Texture> handle) {
|
||||
FileWatchInfo info;
|
||||
info.textureHandle = handle;
|
||||
info.isTexture = true;
|
||||
addFileWatchInternal(path, std::move(info));
|
||||
}
|
||||
|
||||
void AssetsModule::addFileWatch(const std::string &path,
|
||||
Handle<Shader> handle) {
|
||||
FileWatchInfo info;
|
||||
info.shaderHandle = handle;
|
||||
info.isTexture = false;
|
||||
addFileWatchInternal(path, std::move(info));
|
||||
}
|
||||
|
||||
void AssetsModule::reloadTexture(const FileWatchInfo &info) {
|
||||
void AssetsModule::reloadTexture(const AssetHotReloader::FileWatchInfo &info) {
|
||||
if (!textureLoader_ || !info.textureHandle.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -648,7 +548,7 @@ void AssetsModule::reloadTexture(const FileWatchInfo &info) {
|
|||
notifyTextureReloaded(info.textureHandle);
|
||||
}
|
||||
|
||||
void AssetsModule::reloadShader(const FileWatchInfo &info) {
|
||||
void AssetsModule::reloadShader(const AssetHotReloader::FileWatchInfo &info) {
|
||||
if (!shaderLoader_ || !info.shaderHandle.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -690,8 +590,8 @@ void AssetsModule::reloadShader(const FileWatchInfo &info) {
|
|||
std::string fragPath = cacheKey.substr(sepPos + 1);
|
||||
|
||||
// 读取着色器文件内容
|
||||
std::string vsSource = readFileToString(vertPath);
|
||||
std::string fsSource = readFileToString(fragPath);
|
||||
std::string vsSource = fileSystem_.readString(vertPath);
|
||||
std::string fsSource = fileSystem_.readString(fragPath);
|
||||
if (vsSource.empty() || fsSource.empty()) {
|
||||
E2D_ERROR("读取着色器文件失败: {}, {}", vertPath, fragPath);
|
||||
return;
|
||||
|
|
@ -709,42 +609,13 @@ void AssetsModule::reloadShader(const FileWatchInfo &info) {
|
|||
}
|
||||
|
||||
void AssetsModule::checkForChanges() {
|
||||
if (!hotReloadEnabled_ || fileWatchList_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
for (auto &info : fileWatchList_) {
|
||||
try {
|
||||
// 跳过 RomFS 路径(只读文件系统)
|
||||
if (isRomfsPath(info.path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!fileExists(info.path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto currentTime = getLastWriteTime(info.path);
|
||||
if (currentTime != info.lastWriteTime) {
|
||||
info.lastWriteTime = currentTime;
|
||||
|
||||
// 解锁进行重载操作
|
||||
lock.unlock();
|
||||
|
||||
if (info.isTexture) {
|
||||
reloadTexture(info);
|
||||
} else {
|
||||
reloadShader(info);
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
E2D_ERROR("检查文件 {} 时出错: {}", info.path, e.what());
|
||||
}
|
||||
}
|
||||
hotReloader_.checkForChanges(
|
||||
[this](const AssetHotReloader::FileWatchInfo &info) {
|
||||
reloadTexture(info);
|
||||
},
|
||||
[this](const AssetHotReloader::FileWatchInfo &info) {
|
||||
reloadShader(info);
|
||||
});
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
|
@ -752,119 +623,57 @@ void AssetsModule::checkForChanges() {
|
|||
//===========================================================================
|
||||
|
||||
void AssetsModule::initAsyncLoader(uint32_t threadCount) {
|
||||
if (asyncLoaderRunning_) {
|
||||
if (asyncLoader_.isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (threadCount == 0) {
|
||||
threadCount = std::max(1u, std::thread::hardware_concurrency() / 2);
|
||||
}
|
||||
|
||||
asyncLoaderRunning_ = true;
|
||||
|
||||
for (uint32_t i = 0; i < threadCount; ++i) {
|
||||
workerThreads_.emplace_back(&AssetsModule::workerThreadLoop, this);
|
||||
}
|
||||
|
||||
asyncLoader_.init(threadCount);
|
||||
E2D_INFO("异步加载器已初始化,使用 {} 个线程", threadCount);
|
||||
}
|
||||
|
||||
void AssetsModule::shutdownAsyncLoader() {
|
||||
if (!asyncLoaderRunning_) {
|
||||
if (!asyncLoader_.isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
asyncLoaderRunning_ = false;
|
||||
queueCV_.notify_all();
|
||||
|
||||
for (auto &thread : workerThreads_) {
|
||||
if (thread.joinable()) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
workerThreads_.clear();
|
||||
|
||||
std::lock_guard<std::mutex> lock(queueMutex_);
|
||||
loadQueue_.clear();
|
||||
|
||||
asyncLoader_.shutdown();
|
||||
E2D_INFO("异步加载器已关闭");
|
||||
}
|
||||
|
||||
void AssetsModule::submitLoadTask(const LoadTask &task) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex_);
|
||||
loadQueue_.push_back(task);
|
||||
AssetAsyncLoader::Task asyncTask;
|
||||
asyncTask.priority = static_cast<AssetAsyncLoader::Priority>(task.priority);
|
||||
|
||||
// 按优先级排序
|
||||
std::sort(loadQueue_.begin(), loadQueue_.end(),
|
||||
[](const LoadTask &a, const LoadTask &b) {
|
||||
return static_cast<int>(a.priority) >
|
||||
static_cast<int>(b.priority);
|
||||
});
|
||||
}
|
||||
queueCV_.notify_one();
|
||||
}
|
||||
|
||||
void AssetsModule::workerThreadLoop() {
|
||||
while (asyncLoaderRunning_) {
|
||||
LoadTask task;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queueMutex_);
|
||||
queueCV_.wait(
|
||||
lock, [this] { return !loadQueue_.empty() || !asyncLoaderRunning_; });
|
||||
|
||||
if (!asyncLoaderRunning_) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (loadQueue_.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
task = std::move(loadQueue_.back());
|
||||
loadQueue_.pop_back();
|
||||
if (task.type == LoadTask::Type::Texture) {
|
||||
auto handle = std::make_shared<Handle<Texture>>(Handle<Texture>::invalid());
|
||||
asyncTask.work = [this, path = task.path, handle]() {
|
||||
*handle = load<Texture>(path);
|
||||
};
|
||||
if (task.textureCallback) {
|
||||
asyncTask.onComplete = [handle, callback = task.textureCallback]() {
|
||||
callback(*handle);
|
||||
};
|
||||
}
|
||||
|
||||
// 执行加载任务
|
||||
if (task.type == LoadTask::Type::Texture) {
|
||||
Handle<Texture> handle = load<Texture>(task.path);
|
||||
|
||||
if (task.textureCallback) {
|
||||
std::lock_guard<std::mutex> callbackLock(callbackMutex_);
|
||||
completedCallbacks_.push_back(
|
||||
[handle, callback = task.textureCallback]() { callback(handle); });
|
||||
}
|
||||
} else if (task.type == LoadTask::Type::Shader) {
|
||||
Handle<Shader> handle;
|
||||
if (task.secondaryPath.empty()) {
|
||||
handle = load<Shader>(task.path);
|
||||
} else {
|
||||
auto handle = std::make_shared<Handle<Shader>>(Handle<Shader>::invalid());
|
||||
asyncTask.work = [this, path = task.path, secondary = task.secondaryPath,
|
||||
handle]() {
|
||||
if (secondary.empty()) {
|
||||
*handle = load<Shader>(path);
|
||||
} else {
|
||||
handle = load<Shader>(task.path, task.secondaryPath);
|
||||
}
|
||||
|
||||
if (task.shaderCallback) {
|
||||
std::lock_guard<std::mutex> callbackLock(callbackMutex_);
|
||||
completedCallbacks_.push_back(
|
||||
[handle, callback = task.shaderCallback]() { callback(handle); });
|
||||
*handle = load<Shader>(path, secondary);
|
||||
}
|
||||
};
|
||||
if (task.shaderCallback) {
|
||||
asyncTask.onComplete = [handle, callback = task.shaderCallback]() {
|
||||
callback(*handle);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
asyncLoader_.submit(asyncTask);
|
||||
}
|
||||
|
||||
void AssetsModule::processAsyncCallbacks() {
|
||||
std::vector<std::function<void()>> callbacks;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(callbackMutex_);
|
||||
callbacks = std::move(completedCallbacks_);
|
||||
completedCallbacks_.clear();
|
||||
}
|
||||
|
||||
for (auto &callback : callbacks) {
|
||||
callback();
|
||||
}
|
||||
asyncLoader_.processCallbacks();
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
|
@ -873,93 +682,36 @@ void AssetsModule::processAsyncCallbacks() {
|
|||
|
||||
void AssetsModule::registerMaterialDependency(Handle<Material> material,
|
||||
Handle<Texture> texture) {
|
||||
if (!material.isValid() || !texture.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
|
||||
uint32_t textureIndex = texture.index();
|
||||
auto &info = textureDependencies_[textureIndex];
|
||||
info.texture = texture;
|
||||
|
||||
// 检查是否已存在
|
||||
auto it = std::find(info.dependentMaterials.begin(),
|
||||
info.dependentMaterials.end(), material);
|
||||
if (it == info.dependentMaterials.end()) {
|
||||
info.dependentMaterials.push_back(material);
|
||||
E2D_DEBUG("已注册材质 {} 对纹理 {} 的依赖", material.index(), textureIndex);
|
||||
}
|
||||
dependencyTracker_.registerMaterialDependency(material, texture);
|
||||
E2D_DEBUG("已注册材质 {} 对纹理 {} 的依赖", material.index(), texture.index());
|
||||
}
|
||||
|
||||
void AssetsModule::registerMaterialDependency(Handle<Material> material,
|
||||
Handle<Shader> shader) {
|
||||
if (!material.isValid() || !shader.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
|
||||
uint32_t shaderIndex = shader.index();
|
||||
auto &info = shaderDependencies_[shaderIndex];
|
||||
info.shader = shader;
|
||||
|
||||
// 检查是否已存在
|
||||
auto it = std::find(info.dependentMaterials.begin(),
|
||||
info.dependentMaterials.end(), material);
|
||||
if (it == info.dependentMaterials.end()) {
|
||||
info.dependentMaterials.push_back(material);
|
||||
E2D_DEBUG("已注册材质 {} 对着色器 {} 的依赖", material.index(),
|
||||
shaderIndex);
|
||||
}
|
||||
dependencyTracker_.registerMaterialDependency(material, shader);
|
||||
E2D_DEBUG("已注册材质 {} 对着色器 {} 的依赖", material.index(),
|
||||
shader.index());
|
||||
}
|
||||
|
||||
void AssetsModule::notifyTextureReloaded(Handle<Texture> texture) {
|
||||
if (!texture.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
|
||||
uint32_t textureIndex = texture.index();
|
||||
auto it = textureDependencies_.find(textureIndex);
|
||||
if (it != textureDependencies_.end()) {
|
||||
E2D_INFO("通知 {} 个材质纹理已重载", it->second.dependentMaterials.size());
|
||||
|
||||
// 材质需要更新纹理引用
|
||||
// 这里可以触发材质更新事件
|
||||
for (const auto &materialHandle : it->second.dependentMaterials) {
|
||||
Material *material = materials_.get(materialHandle);
|
||||
if (material) {
|
||||
// 材质自动使用新的纹理数据
|
||||
E2D_DEBUG("材质 {} 已更新为重载后的纹理", materialHandle.index());
|
||||
}
|
||||
}
|
||||
}
|
||||
dependencyTracker_.notifyTextureReloaded(
|
||||
texture, [this](Handle<Material> materialHandle) {
|
||||
Material *material = materials_.get(materialHandle);
|
||||
if (material) {
|
||||
E2D_DEBUG("材质 {} 已更新为重载后的纹理", materialHandle.index());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void AssetsModule::notifyShaderReloaded(Handle<Shader> shader) {
|
||||
if (!shader.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
|
||||
uint32_t shaderIndex = shader.index();
|
||||
auto it = shaderDependencies_.find(shaderIndex);
|
||||
if (it != shaderDependencies_.end()) {
|
||||
E2D_INFO("通知 {} 个材质着色器已重载",
|
||||
it->second.dependentMaterials.size());
|
||||
|
||||
for (const auto &materialHandle : it->second.dependentMaterials) {
|
||||
Material *material = materials_.get(materialHandle);
|
||||
if (material) {
|
||||
// 更新材质的着色器引用
|
||||
material->setShader(getPtr(shader));
|
||||
E2D_DEBUG("材质 {} 已更新为重载后的着色器", materialHandle.index());
|
||||
}
|
||||
}
|
||||
}
|
||||
dependencyTracker_.notifyShaderReloaded(
|
||||
shader, [this, shader](Handle<Material> materialHandle) {
|
||||
Material *material = materials_.get(materialHandle);
|
||||
if (material) {
|
||||
material->setShader(getPtr(shader));
|
||||
E2D_DEBUG("材质 {} 已更新为重载后的着色器", materialHandle.index());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
#include <algorithm>
|
||||
#include <assets/async/asset_async_loader.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
AssetAsyncLoader::~AssetAsyncLoader() { shutdown(); }
|
||||
|
||||
void AssetAsyncLoader::init(uint32_t threadCount) {
|
||||
if (running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (threadCount == 0) {
|
||||
threadCount = std::max(1u, std::thread::hardware_concurrency() / 2);
|
||||
}
|
||||
|
||||
running_ = true;
|
||||
for (uint32_t i = 0; i < threadCount; ++i) {
|
||||
workers_.emplace_back(&AssetAsyncLoader::workerThreadLoop, this);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetAsyncLoader::shutdown() {
|
||||
if (!running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
running_ = false;
|
||||
queueCV_.notify_all();
|
||||
|
||||
for (auto &thread : workers_) {
|
||||
if (thread.joinable()) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
workers_.clear();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex_);
|
||||
queue_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool AssetAsyncLoader::isRunning() const { return running_; }
|
||||
|
||||
void AssetAsyncLoader::submit(const Task &task) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex_);
|
||||
queue_.push_back(task);
|
||||
std::sort(queue_.begin(), queue_.end(), [](const Task &a, const Task &b) {
|
||||
return static_cast<int>(a.priority) > static_cast<int>(b.priority);
|
||||
});
|
||||
}
|
||||
queueCV_.notify_one();
|
||||
}
|
||||
|
||||
void AssetAsyncLoader::workerThreadLoop() {
|
||||
while (running_) {
|
||||
Task task;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queueMutex_);
|
||||
queueCV_.wait(lock, [this] { return !queue_.empty() || !running_; });
|
||||
if (!running_) {
|
||||
break;
|
||||
}
|
||||
if (queue_.empty()) {
|
||||
continue;
|
||||
}
|
||||
task = std::move(queue_.back());
|
||||
queue_.pop_back();
|
||||
}
|
||||
|
||||
if (task.work) {
|
||||
task.work();
|
||||
}
|
||||
|
||||
if (task.onComplete) {
|
||||
std::lock_guard<std::mutex> callbackLock(callbackMutex_);
|
||||
completedCallbacks_.push_back(task.onComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AssetAsyncLoader::processCallbacks() {
|
||||
std::vector<std::function<void()>> callbacks;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(callbackMutex_);
|
||||
callbacks = std::move(completedCallbacks_);
|
||||
completedCallbacks_.clear();
|
||||
}
|
||||
|
||||
for (auto &callback : callbacks) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
#include <algorithm>
|
||||
#include <assets/deps/asset_dependency_tracker.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
void AssetDependencyTracker::clear() {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
textureDependencies_.clear();
|
||||
shaderDependencies_.clear();
|
||||
}
|
||||
|
||||
void AssetDependencyTracker::registerMaterialDependency(Handle<Material> material,
|
||||
Handle<Texture> texture) {
|
||||
if (!material.isValid() || !texture.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
auto &info = textureDependencies_[texture.index()];
|
||||
info.texture = texture;
|
||||
auto it = std::find(info.dependentMaterials.begin(),
|
||||
info.dependentMaterials.end(), material);
|
||||
if (it == info.dependentMaterials.end()) {
|
||||
info.dependentMaterials.push_back(material);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetDependencyTracker::registerMaterialDependency(Handle<Material> material,
|
||||
Handle<Shader> shader) {
|
||||
if (!material.isValid() || !shader.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
auto &info = shaderDependencies_[shader.index()];
|
||||
info.shader = shader;
|
||||
auto it = std::find(info.dependentMaterials.begin(),
|
||||
info.dependentMaterials.end(), material);
|
||||
if (it == info.dependentMaterials.end()) {
|
||||
info.dependentMaterials.push_back(material);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetDependencyTracker::notifyTextureReloaded(
|
||||
Handle<Texture> texture,
|
||||
const std::function<void(Handle<Material>)> &onMaterialUpdate) const {
|
||||
if (!texture.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Handle<Material>> materials;
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
auto it = textureDependencies_.find(texture.index());
|
||||
if (it == textureDependencies_.end()) {
|
||||
return;
|
||||
}
|
||||
materials = it->second.dependentMaterials;
|
||||
}
|
||||
|
||||
for (const auto &material : materials) {
|
||||
onMaterialUpdate(material);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetDependencyTracker::notifyShaderReloaded(
|
||||
Handle<Shader> shader,
|
||||
const std::function<void(Handle<Material>)> &onMaterialUpdate) const {
|
||||
if (!shader.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Handle<Material>> materials;
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
auto it = shaderDependencies_.find(shader.index());
|
||||
if (it == shaderDependencies_.end()) {
|
||||
return;
|
||||
}
|
||||
materials = it->second.dependentMaterials;
|
||||
}
|
||||
|
||||
for (const auto &material : materials) {
|
||||
onMaterialUpdate(material);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
#include <assets/hot_reload/asset_hot_reloader.h>
|
||||
#include <utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
AssetHotReloader::AssetHotReloader(const AssetFileSystem &fileSystem)
|
||||
: fileSystem_(fileSystem) {}
|
||||
|
||||
void AssetHotReloader::enable(bool enable) { enabled_ = enable; }
|
||||
|
||||
bool AssetHotReloader::enabled() const { return enabled_; }
|
||||
|
||||
void AssetHotReloader::setInterval(float interval) { interval_ = interval; }
|
||||
|
||||
void AssetHotReloader::addFileWatchInternal(const std::string &path,
|
||||
FileWatchInfo &&info) {
|
||||
try {
|
||||
if (!fileSystem_.exists(path)) {
|
||||
return;
|
||||
}
|
||||
info.path = path;
|
||||
info.lastWriteTime = fileSystem_.lastWriteTime(path);
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
watchList_.push_back(std::move(info));
|
||||
} catch (const std::exception &e) {
|
||||
E2D_ERROR("添加文件监控失败 {}: {}", path, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void AssetHotReloader::addFileWatch(const std::string &path,
|
||||
Handle<Texture> handle) {
|
||||
FileWatchInfo info;
|
||||
info.textureHandle = handle;
|
||||
info.isTexture = true;
|
||||
addFileWatchInternal(path, std::move(info));
|
||||
}
|
||||
|
||||
void AssetHotReloader::addFileWatch(const std::string &path,
|
||||
Handle<Shader> handle) {
|
||||
FileWatchInfo info;
|
||||
info.shaderHandle = handle;
|
||||
info.isTexture = false;
|
||||
addFileWatchInternal(path, std::move(info));
|
||||
}
|
||||
|
||||
void AssetHotReloader::clear() {
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
watchList_.clear();
|
||||
}
|
||||
|
||||
void AssetHotReloader::checkForChanges(
|
||||
const std::function<void(const FileWatchInfo &)> &reloadTexture,
|
||||
const std::function<void(const FileWatchInfo &)> &reloadShader) {
|
||||
if (!enabled_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
if (watchList_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &info : watchList_) {
|
||||
try {
|
||||
if (fileSystem_.isRomfsPath(info.path)) {
|
||||
continue;
|
||||
}
|
||||
if (!fileSystem_.exists(info.path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto currentTime = fileSystem_.lastWriteTime(info.path);
|
||||
if (currentTime != info.lastWriteTime) {
|
||||
info.lastWriteTime = currentTime;
|
||||
lock.unlock();
|
||||
if (info.isTexture) {
|
||||
reloadTexture(info);
|
||||
} else {
|
||||
reloadShader(info);
|
||||
}
|
||||
lock.lock();
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
E2D_ERROR("检查文件 {} 时出错: {}", info.path, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#include <assets/io/asset_file_system.h>
|
||||
#include <module/module_registry.h>
|
||||
#include <platform/file_module.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
std::string AssetFileSystem::assetPath(const std::string &path) const {
|
||||
FileModule *fileModule = getModule<FileModule>();
|
||||
if (!fileModule) {
|
||||
return path;
|
||||
}
|
||||
return fileModule->assetPath(path);
|
||||
}
|
||||
|
||||
bool AssetFileSystem::isRomfsPath(const std::string &path) const {
|
||||
FileModule *fileModule = getModule<FileModule>();
|
||||
if (!fileModule) {
|
||||
return false;
|
||||
}
|
||||
return fileModule->isRomfsPath(path);
|
||||
}
|
||||
|
||||
bool AssetFileSystem::exists(const std::string &path) const {
|
||||
FileModule *fileModule = getModule<FileModule>();
|
||||
if (!fileModule) {
|
||||
return false;
|
||||
}
|
||||
return fileModule->exists(path);
|
||||
}
|
||||
|
||||
std::string AssetFileSystem::readString(const std::string &path) const {
|
||||
FileModule *fileModule = getModule<FileModule>();
|
||||
if (!fileModule) {
|
||||
return "";
|
||||
}
|
||||
return fileModule->readString(path);
|
||||
}
|
||||
|
||||
std::filesystem::file_time_type
|
||||
AssetFileSystem::lastWriteTime(const std::string &path) const {
|
||||
if (isRomfsPath(path)) {
|
||||
return std::filesystem::file_time_type::min();
|
||||
}
|
||||
return std::filesystem::last_write_time(path);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
|
|
@ -1,41 +1,11 @@
|
|||
#include <assets/loaders/shader_loader.h>
|
||||
#include <module/module_registry.h>
|
||||
#include <platform/file_module.h>
|
||||
#include <renderer/shader.h>
|
||||
#include <utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief 获取 FileModule 实例
|
||||
*/
|
||||
FileModule *getFileModule() { return getModule<FileModule>(); }
|
||||
|
||||
/**
|
||||
* @brief 检查文件是否存在(使用 FileModule)
|
||||
*/
|
||||
bool fileExists(const std::string &path) {
|
||||
FileModule *fileModule = getFileModule();
|
||||
if (fileModule) {
|
||||
return fileModule->exists(path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取文件内容为字符串(使用 FileModule,支持 RomFS)
|
||||
*/
|
||||
std::string readFileToString(const std::string &path) {
|
||||
FileModule *fileModule = getFileModule();
|
||||
if (fileModule) {
|
||||
return fileModule->readString(path);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
ShaderLoader::ShaderLoader(AssetFileSystem fileSystem)
|
||||
: fileSystem_(std::move(fileSystem)) {}
|
||||
|
||||
Ptr<Shader> ShaderLoader::load(const std::string &path) {
|
||||
std::string basePath = path;
|
||||
|
|
@ -48,10 +18,10 @@ Ptr<Shader> ShaderLoader::load(const std::string &path) {
|
|||
std::string vertPath = basePath + ".vert";
|
||||
std::string fragPath = basePath + ".frag";
|
||||
|
||||
if (!fileExists(vertPath)) {
|
||||
if (!fileSystem_.exists(vertPath)) {
|
||||
vertPath = basePath + ".vs";
|
||||
}
|
||||
if (!fileExists(fragPath)) {
|
||||
if (!fileSystem_.exists(fragPath)) {
|
||||
fragPath = basePath + ".fs";
|
||||
}
|
||||
|
||||
|
|
@ -61,14 +31,14 @@ Ptr<Shader> ShaderLoader::load(const std::string &path) {
|
|||
Ptr<Shader> ShaderLoader::load(const std::string &vertPath,
|
||||
const std::string &fragPath) {
|
||||
// 读取顶点着色器文件
|
||||
std::string vsSource = readFileToString(vertPath);
|
||||
std::string vsSource = fileSystem_.readString(vertPath);
|
||||
if (vsSource.empty()) {
|
||||
E2D_ERROR("ShaderLoader: 读取顶点着色器失败: {}", vertPath);
|
||||
return Ptr<Shader>();
|
||||
}
|
||||
|
||||
// 读取片段着色器文件
|
||||
std::string fsSource = readFileToString(fragPath);
|
||||
std::string fsSource = fileSystem_.readString(fragPath);
|
||||
if (fsSource.empty()) {
|
||||
E2D_ERROR("ShaderLoader: 读取片段着色器失败: {}", fragPath);
|
||||
return Ptr<Shader>();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <renderer/command_queue.h>
|
||||
#include <renderer/material.h>
|
||||
|
|
@ -31,8 +32,9 @@ CommandSorter::~CommandSorter() = default;
|
|||
CommandSorter::CommandSorter(CommandSorter &&other) noexcept
|
||||
: commands_(std::move(other.commands_)),
|
||||
sortedIndices_(std::move(other.sortedIndices_)),
|
||||
commandCount_(other.commandCount_) {
|
||||
commandCount_(other.commandCount_), needsSort_(other.needsSort_) {
|
||||
other.commandCount_ = 0;
|
||||
other.needsSort_ = false;
|
||||
}
|
||||
|
||||
CommandSorter &CommandSorter::operator=(CommandSorter &&other) noexcept {
|
||||
|
|
@ -40,13 +42,18 @@ CommandSorter &CommandSorter::operator=(CommandSorter &&other) noexcept {
|
|||
commands_ = std::move(other.commands_);
|
||||
sortedIndices_ = std::move(other.sortedIndices_);
|
||||
commandCount_ = other.commandCount_;
|
||||
needsSort_ = other.needsSort_;
|
||||
other.commandCount_ = 0;
|
||||
other.needsSort_ = false;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint32_t CommandSorter::addCommand(const DrawCommand &cmd) {
|
||||
uint32_t index = commandCount_;
|
||||
if (index > 0 && cmd.key < commands_[index - 1].key) {
|
||||
needsSort_ = true;
|
||||
}
|
||||
|
||||
// 如果缓冲区不够大,扩展它
|
||||
if (index >= commands_.size()) {
|
||||
|
|
@ -62,8 +69,9 @@ uint32_t CommandSorter::addCommand(const DrawCommand &cmd) {
|
|||
}
|
||||
|
||||
void CommandSorter::sort() {
|
||||
if (commandCount_ == 0)
|
||||
if (commandCount_ == 0 || !needsSort_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 只排序有效范围的索引
|
||||
auto indicesBegin = sortedIndices_.begin();
|
||||
|
|
@ -73,11 +81,13 @@ void CommandSorter::sort() {
|
|||
std::stable_sort(indicesBegin, indicesEnd, [this](uint32_t a, uint32_t b) {
|
||||
return commands_[a].key < commands_[b].key;
|
||||
});
|
||||
needsSort_ = false;
|
||||
}
|
||||
|
||||
void CommandSorter::clear() {
|
||||
// 重置计数器,保留内存
|
||||
commandCount_ = 0;
|
||||
needsSort_ = false;
|
||||
}
|
||||
|
||||
void CommandSorter::reserve(uint32_t capacity) {
|
||||
|
|
@ -260,6 +270,7 @@ void CommandQueue::beginFrame() {
|
|||
|
||||
// 重置统计
|
||||
stats_ = {};
|
||||
profilingStats_ = {};
|
||||
|
||||
// 开始录制命令
|
||||
if (commandList_) {
|
||||
|
|
@ -346,7 +357,7 @@ UniformBuffer *CommandQueue::getCurrentMaterialUBO() const {
|
|||
|
||||
void CommandQueue::submitDraw(Ptr<Material> material, Ptr<Mesh> mesh,
|
||||
const struct Transform &transform,
|
||||
const Color &color) {
|
||||
const Color &color, uint32_t sortKey) {
|
||||
if (!material || !mesh || !material->getShader()) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -355,7 +366,9 @@ void CommandQueue::submitDraw(Ptr<Material> material, Ptr<Mesh> mesh,
|
|||
|
||||
// 构建排序键
|
||||
uint32_t materialId = getMaterialId(material.get());
|
||||
cmd.key = DrawKey::make(materialId, 0, 0);
|
||||
uint16_t depth = static_cast<uint16_t>((sortKey >> 8) & 0xFFFF);
|
||||
uint8_t layer = static_cast<uint8_t>(sortKey & 0xFF);
|
||||
cmd.key = DrawKey::make(materialId, depth, layer);
|
||||
|
||||
// 设置管线
|
||||
cmd.pipeline = material->getPipeline();
|
||||
|
|
@ -438,6 +451,7 @@ void CommandQueue::setViewport(int32_t x, int32_t y, int32_t width,
|
|||
}
|
||||
|
||||
void CommandQueue::updateGlobalUBO(const Mat4 &viewProjection, float deltaTime,
|
||||
float totalTime,
|
||||
uint32_t screenWidth, uint32_t screenHeight,
|
||||
uint32_t frameIndex) {
|
||||
if (!uboManager_) {
|
||||
|
|
@ -452,7 +466,7 @@ void CommandQueue::updateGlobalUBO(const Mat4 &viewProjection, float deltaTime,
|
|||
globalUBOData_.cameraPosition[1] = 0.0f;
|
||||
globalUBOData_.cameraPosition[2] = 0.0f;
|
||||
globalUBOData_.cameraPosition[3] = 1.0f;
|
||||
globalUBOData_.time = 0.0f; // TODO: 传递实际时间
|
||||
globalUBOData_.time = totalTime;
|
||||
globalUBOData_.deltaTime = deltaTime;
|
||||
globalUBOData_.screenSize[0] = static_cast<float>(screenWidth);
|
||||
globalUBOData_.screenSize[1] = static_cast<float>(screenHeight);
|
||||
|
|
@ -468,22 +482,43 @@ void CommandQueue::execute(uint32_t frameIndex) {
|
|||
return;
|
||||
}
|
||||
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
const auto totalStart = Clock::now();
|
||||
|
||||
// 在排序前刷新材质 UBO 数据到 GPU
|
||||
flushMaterialUBOToGPU();
|
||||
|
||||
// 排序命令
|
||||
const auto sortStart = Clock::now();
|
||||
sorter_.sort();
|
||||
const auto sortEnd = Clock::now();
|
||||
|
||||
// 批处理
|
||||
const auto batchStart = Clock::now();
|
||||
batcher_.process(sorter_);
|
||||
const auto batchEnd = Clock::now();
|
||||
|
||||
// 执行批次
|
||||
const auto executeStart = Clock::now();
|
||||
for (uint32_t i = 0; i < batcher_.getBatchCount(); ++i) {
|
||||
executeBatch(i, batcher_.getBatch(i), frameIndex);
|
||||
}
|
||||
|
||||
// 提交命令到 GPU
|
||||
commandList_->submit();
|
||||
const auto executeEnd = Clock::now();
|
||||
|
||||
profilingStats_.commandCount = sorter_.getCount();
|
||||
profilingStats_.batchCount = batcher_.getBatchCount();
|
||||
profilingStats_.sortMs =
|
||||
std::chrono::duration<float, std::milli>(sortEnd - sortStart).count();
|
||||
profilingStats_.batchMs =
|
||||
std::chrono::duration<float, std::milli>(batchEnd - batchStart).count();
|
||||
profilingStats_.executeMs = std::chrono::duration<float, std::milli>(
|
||||
executeEnd - executeStart)
|
||||
.count();
|
||||
profilingStats_.totalMs =
|
||||
std::chrono::duration<float, std::milli>(executeEnd - totalStart).count();
|
||||
}
|
||||
|
||||
void CommandQueue::executeBatch(uint32_t batchIndex, const CommandBatch &batch,
|
||||
|
|
@ -520,29 +555,43 @@ void CommandQueue::executeBatch(uint32_t batchIndex, const CommandBatch &batch,
|
|||
}
|
||||
|
||||
// 执行批次中的所有命令
|
||||
RHIBuffer *lastVertexBuffer = nullptr;
|
||||
RHIBuffer *lastIndexBuffer = nullptr;
|
||||
uint32_t lastMaterialOffset = static_cast<uint32_t>(-1);
|
||||
uint32_t lastMaterialSize = static_cast<uint32_t>(-1);
|
||||
for (uint32_t i = 0; i < batch.count; ++i) {
|
||||
const auto &cmd = batcher_.getCommand(batchIndex, i);
|
||||
|
||||
// 绑定顶点缓冲区
|
||||
if (cmd.vertexBuffer.isValid()) {
|
||||
if (cmd.vertexBuffer.isValid() && cmd.vertexBuffer.get() != lastVertexBuffer) {
|
||||
commandList_->setVertexBuffer(0, cmd.vertexBuffer.get(), 0);
|
||||
stats_.bufferBinds++;
|
||||
lastVertexBuffer = cmd.vertexBuffer.get();
|
||||
} else {
|
||||
E2D_WARN("绘制命令没有有效的顶点缓冲区!");
|
||||
if (!cmd.vertexBuffer.isValid()) {
|
||||
E2D_WARN("绘制命令没有有效的顶点缓冲区!");
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定索引缓冲区(如果有)
|
||||
if (cmd.isIndexed() && cmd.indexBuffer.isValid()) {
|
||||
if (cmd.isIndexed() && cmd.indexBuffer.isValid() &&
|
||||
cmd.indexBuffer.get() != lastIndexBuffer) {
|
||||
commandList_->setIndexBuffer(cmd.indexBuffer.get(), IndexType::UInt16, 0);
|
||||
stats_.bufferBinds++;
|
||||
lastIndexBuffer = cmd.indexBuffer.get();
|
||||
}
|
||||
|
||||
// 绑定材质 UBO (binding = 1)
|
||||
if (cmd.materialUBOSize > 0 && currentMaterialUBO_) {
|
||||
commandList_->setUniformBuffer(1, currentMaterialUBO_->getRHIBuffer(),
|
||||
cmd.materialUBOOffset,
|
||||
cmd.materialUBOSize);
|
||||
stats_.bufferBinds++;
|
||||
if (cmd.materialUBOOffset != lastMaterialOffset ||
|
||||
cmd.materialUBOSize != lastMaterialSize) {
|
||||
commandList_->setUniformBuffer(1, currentMaterialUBO_->getRHIBuffer(),
|
||||
cmd.materialUBOOffset,
|
||||
cmd.materialUBOSize);
|
||||
stats_.bufferBinds++;
|
||||
lastMaterialOffset = cmd.materialUBOOffset;
|
||||
lastMaterialSize = cmd.materialUBOSize;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置模型矩阵
|
||||
|
|
|
|||
|
|
@ -179,7 +179,8 @@ bool RenderGraph::compile() {
|
|||
return true;
|
||||
}
|
||||
|
||||
void RenderGraph::execute(float deltaTime, const Mat4 &viewProjection) {
|
||||
void RenderGraph::execute(float deltaTime, const Mat4 &viewProjection,
|
||||
float totalTime) {
|
||||
if (!compiled_) {
|
||||
if (!compile()) {
|
||||
E2D_ERROR("编译渲染图失败");
|
||||
|
|
@ -188,7 +189,7 @@ void RenderGraph::execute(float deltaTime, const Mat4 &viewProjection) {
|
|||
}
|
||||
|
||||
// 更新全局 UBO(使用双缓冲)
|
||||
commandQueue_.updateGlobalUBO(viewProjection, deltaTime, outputWidth_,
|
||||
commandQueue_.updateGlobalUBO(viewProjection, deltaTime, totalTime, outputWidth_,
|
||||
outputHeight_, frameIndex_);
|
||||
|
||||
// 获取 RHI 上下文
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ bool RendererModule::init() {
|
|||
onRenderSetCameraListener_.bind(
|
||||
[this](const Mat4 &viewProj) { onRenderSetCamera(viewProj); });
|
||||
onRenderEndListener_.bind([this]() { onRenderEnd(); });
|
||||
onUpdateListener_.bind([this](float dt) { onUpdate(dt); });
|
||||
onResizeListener_.bind([this](int32 w, int32 h) { onResize(w, h); });
|
||||
onShowListener_.bind([this]() { onWindowShow(); });
|
||||
|
||||
|
|
@ -156,6 +157,13 @@ void RendererModule::onRenderBegin() {
|
|||
if (commandQueue_) {
|
||||
commandQueue_->beginFrame();
|
||||
}
|
||||
defaultMaterialPtr_ = nullptr;
|
||||
defaultMeshPtr_ = nullptr;
|
||||
auto *assets = getAssets();
|
||||
if (assets) {
|
||||
defaultMaterialPtr_ = assets->get(assets->getDefaultMaterial());
|
||||
defaultMeshPtr_ = assets->get(assets->getDefaultQuad());
|
||||
}
|
||||
|
||||
// 帧开始
|
||||
}
|
||||
|
|
@ -166,8 +174,6 @@ void RendererModule::onRenderSubmit(const RenderCommand &cmd) {
|
|||
return;
|
||||
}
|
||||
|
||||
(void)cmd; // 避免未使用警告
|
||||
|
||||
// 将渲染命令转换为绘制命令提交到队列
|
||||
switch (cmd.type) {
|
||||
case RenderCommandType::DrawMesh: {
|
||||
|
|
@ -175,23 +181,29 @@ void RendererModule::onRenderSubmit(const RenderCommand &cmd) {
|
|||
if (!assets)
|
||||
return;
|
||||
|
||||
// 获取材质,如果没有指定则使用默认材质
|
||||
Material *material = assets->get(cmd.drawMesh.material);
|
||||
if (!material) {
|
||||
material = defaultMaterialPtr_;
|
||||
}
|
||||
if (!material) {
|
||||
material = assets->get(assets->getDefaultMaterial());
|
||||
defaultMaterialPtr_ = material;
|
||||
}
|
||||
|
||||
// 获取网格,如果没有指定则使用默认四边形
|
||||
Mesh *mesh = assets->get(cmd.drawMesh.mesh);
|
||||
if (!mesh) {
|
||||
mesh = defaultMeshPtr_;
|
||||
}
|
||||
if (!mesh) {
|
||||
mesh = assets->get(assets->getDefaultQuad());
|
||||
defaultMeshPtr_ = mesh;
|
||||
}
|
||||
|
||||
if (material && mesh) {
|
||||
Transform transform(cmd.drawMesh.pos, cmd.drawMesh.scale,
|
||||
cmd.drawMesh.rot);
|
||||
commandQueue_->submitDraw(Ptr<Material>(material), Ptr<Mesh>(mesh),
|
||||
transform, cmd.drawMesh.color);
|
||||
transform, cmd.drawMesh.color, cmd.sortKey);
|
||||
stats_.commandsSubmitted++;
|
||||
} else {
|
||||
E2D_WARN("提交绘制命令失败: 材质={}, 网格={}", material ? "有效" : "空",
|
||||
|
|
@ -224,6 +236,11 @@ void RendererModule::onRenderSetCamera(const Mat4 &viewProj) {
|
|||
// 实际的 UBO 更新会在 RenderGraph 执行时进行
|
||||
}
|
||||
|
||||
void RendererModule::onUpdate(float dt) {
|
||||
frameDeltaTime_ = dt;
|
||||
renderTotalTime_ += dt;
|
||||
}
|
||||
|
||||
void RendererModule::onRenderEnd() {
|
||||
if (!initialized_) {
|
||||
E2D_WARN("onRenderEnd: 渲染模块未初始化");
|
||||
|
|
@ -231,20 +248,31 @@ void RendererModule::onRenderEnd() {
|
|||
}
|
||||
|
||||
// 执行渲染图,传递视图投影矩阵
|
||||
renderGraph_.execute(0.016f, viewProjectionMatrix_);
|
||||
renderGraph_.execute(frameDeltaTime_, viewProjectionMatrix_,
|
||||
renderTotalTime_);
|
||||
|
||||
// 获取统计信息
|
||||
if (commandQueue_) {
|
||||
const auto &stats = commandQueue_->getStats();
|
||||
const auto &profiling = commandQueue_->getProfilingStats();
|
||||
stats_.drawCalls = stats.drawCalls;
|
||||
stats_.batches = profiling.batchCount;
|
||||
stats_.queueSortMs = profiling.sortMs;
|
||||
stats_.queueBatchMs = profiling.batchMs;
|
||||
stats_.queueExecuteMs = profiling.executeMs;
|
||||
stats_.queueTotalMs = profiling.totalMs;
|
||||
|
||||
// 每60帧输出一次统计信息
|
||||
static uint32_t frameCount = 0;
|
||||
if (++frameCount % 60 == 0) {
|
||||
E2D_INFO("渲染统计: 绘制调用={}, 三角形数={}, 顶点数={}, "
|
||||
"管线绑定={}, 纹理绑定={}, 缓冲区绑定={}",
|
||||
"管线绑定={}, 纹理绑定={}, 缓冲区绑定={}, 命令数={}, 批次数={}, "
|
||||
"排序耗时={:.3f}ms, 批处理耗时={:.3f}ms, 执行耗时={:.3f}ms, "
|
||||
"队列总耗时={:.3f}ms",
|
||||
stats.drawCalls, stats.triangles, stats.vertices,
|
||||
stats.pipelineBinds, stats.textureBinds, stats.bufferBinds);
|
||||
stats.pipelineBinds, stats.textureBinds, stats.bufferBinds,
|
||||
profiling.commandCount, profiling.batchCount, profiling.sortMs,
|
||||
profiling.batchMs, profiling.executeMs, profiling.totalMs);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,20 @@ GLCommandList::GLCommandList() = default;
|
|||
|
||||
GLCommandList::~GLCommandList() = default;
|
||||
|
||||
GLint GLCommandList::getUniformLocation(GLuint program, const char *name) {
|
||||
if (program == 0 || name == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
auto &programCache = uniformLocationCache_[program];
|
||||
auto it = programCache.find(name);
|
||||
if (it != programCache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
GLint location = glGetUniformLocation(program, name);
|
||||
programCache.emplace(name, location);
|
||||
return location;
|
||||
}
|
||||
|
||||
void GLCommandList::begin() {
|
||||
// 开始记录命令
|
||||
recording_ = true;
|
||||
|
|
@ -321,7 +335,7 @@ bool GLCommandList::isRecording() const {
|
|||
|
||||
void GLCommandList::setUniform(const char* name, float value) {
|
||||
if (stateCache_.shaderProgram != 0) {
|
||||
GLint location = glGetUniformLocation(stateCache_.shaderProgram, name);
|
||||
GLint location = getUniformLocation(stateCache_.shaderProgram, name);
|
||||
if (location != -1) {
|
||||
glUniform1f(location, value);
|
||||
}
|
||||
|
|
@ -330,7 +344,7 @@ void GLCommandList::setUniform(const char* name, float value) {
|
|||
|
||||
void GLCommandList::setUniform(const char* name, const Vec2& value) {
|
||||
if (stateCache_.shaderProgram != 0) {
|
||||
GLint location = glGetUniformLocation(stateCache_.shaderProgram, name);
|
||||
GLint location = getUniformLocation(stateCache_.shaderProgram, name);
|
||||
if (location != -1) {
|
||||
glUniform2f(location, value.x, value.y);
|
||||
}
|
||||
|
|
@ -339,7 +353,7 @@ void GLCommandList::setUniform(const char* name, const Vec2& value) {
|
|||
|
||||
void GLCommandList::setUniform(const char* name, const Vec3& value) {
|
||||
if (stateCache_.shaderProgram != 0) {
|
||||
GLint location = glGetUniformLocation(stateCache_.shaderProgram, name);
|
||||
GLint location = getUniformLocation(stateCache_.shaderProgram, name);
|
||||
if (location != -1) {
|
||||
glUniform3f(location, value.x, value.y, value.z);
|
||||
}
|
||||
|
|
@ -348,7 +362,7 @@ void GLCommandList::setUniform(const char* name, const Vec3& value) {
|
|||
|
||||
void GLCommandList::setUniform(const char* name, const Color& value) {
|
||||
if (stateCache_.shaderProgram != 0) {
|
||||
GLint location = glGetUniformLocation(stateCache_.shaderProgram, name);
|
||||
GLint location = getUniformLocation(stateCache_.shaderProgram, name);
|
||||
if (location != -1) {
|
||||
glUniform4f(location, value.r, value.g, value.b, value.a);
|
||||
}
|
||||
|
|
@ -357,7 +371,7 @@ void GLCommandList::setUniform(const char* name, const Color& value) {
|
|||
|
||||
void GLCommandList::setUniform(const char* name, const Mat4& value) {
|
||||
if (stateCache_.shaderProgram != 0) {
|
||||
GLint location = glGetUniformLocation(stateCache_.shaderProgram, name);
|
||||
GLint location = getUniformLocation(stateCache_.shaderProgram, name);
|
||||
if (location != -1) {
|
||||
glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(value));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue