feat(renderer): 添加渲染性能优化和资源热重载系统

- 新增 AssetFileSystem 封装文件系统操作,支持 RomFS
- 新增 AssetAsyncLoader 实现异步资源加载
- 新增 AssetDependencyTracker 跟踪资源依赖关系
- 新增 AssetHotReloader 实现纹理和着色器热重载
- 优化 CommandQueue 性能:添加缓存机制,减少冗余状态切换
- 扩展 RenderGraph::execute 支持传递总时间参数
- 改进渲染统计信息,添加性能分析数据
- 重构 AssetsModule 使用新的子系统,提高代码可维护性
This commit is contained in:
ChestnutYueyue 2026-03-16 17:33:57 +08:00
parent 4a902134dc
commit 41817c9e8a
20 changed files with 731 additions and 447 deletions

View File

@ -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_;
};
// 全局访问

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

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

View File

@ -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

View File

@ -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

View File

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

View File

@ -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());
}
});
}
//===========================================================================

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>();

View File

@ -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;
}
}
// 设置模型矩阵

View File

@ -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 上下文

View File

@ -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);
}
}

View File

@ -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));
}