From 41817c9e8a90c5a6d7bb4c5c2b59d1b3ecf26c6d Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Mon, 16 Mar 2026 17:33:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(renderer):=20=E6=B7=BB=E5=8A=A0=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96=E5=92=8C=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E7=83=AD=E9=87=8D=E8=BD=BD=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 AssetFileSystem 封装文件系统操作,支持 RomFS - 新增 AssetAsyncLoader 实现异步资源加载 - 新增 AssetDependencyTracker 跟踪资源依赖关系 - 新增 AssetHotReloader 实现纹理和着色器热重载 - 优化 CommandQueue 性能:添加缓存机制,减少冗余状态切换 - 扩展 RenderGraph::execute 支持传递总时间参数 - 改进渲染统计信息,添加性能分析数据 - 重构 AssetsModule 使用新的子系统,提高代码可维护性 --- include/assets/assets_module.h | 62 +-- include/assets/async/asset_async_loader.h | 45 ++ .../assets/deps/asset_dependency_tracker.h | 41 ++ .../assets/hot_reload/asset_hot_reloader.h | 47 ++ include/assets/io/asset_file_system.h | 18 + include/assets/loaders/shader_loader.h | 6 + include/renderer/command_queue.h | 15 +- include/renderer/render_graph.h | 3 +- include/renderer/renderer_module.h | 10 + include/renderer/rhi/opengl/gl_command_list.h | 5 + src/assets/assets_module.cpp | 412 ++++-------------- src/assets/async/asset_async_loader.cpp | 98 +++++ src/assets/deps/asset_dependency_tracker.cpp | 89 ++++ src/assets/hot_reload/asset_hot_reloader.cpp | 91 ++++ src/assets/io/asset_file_system.cpp | 48 ++ src/assets/loaders/shader_loader.cpp | 42 +- src/renderer/command_queue.cpp | 73 +++- src/renderer/render_graph.cpp | 5 +- src/renderer/renderer_module.cpp | 44 +- src/renderer/rhi/opengl/gl_command_list.cpp | 24 +- 20 files changed, 731 insertions(+), 447 deletions(-) create mode 100644 include/assets/async/asset_async_loader.h create mode 100644 include/assets/deps/asset_dependency_tracker.h create mode 100644 include/assets/hot_reload/asset_hot_reloader.h create mode 100644 include/assets/io/asset_file_system.h create mode 100644 src/assets/async/asset_async_loader.cpp create mode 100644 src/assets/deps/asset_dependency_tracker.cpp create mode 100644 src/assets/hot_reload/asset_hot_reloader.cpp create mode 100644 src/assets/io/asset_file_system.cpp diff --git a/include/assets/assets_module.h b/include/assets/assets_module.h index 7248a75..8897caf 100644 --- a/include/assets/assets_module.h +++ b/include/assets/assets_module.h @@ -1,12 +1,14 @@ #pragma once #include +#include +#include +#include +#include #include #include #include -#include #include -#include #include #include #include @@ -18,7 +20,6 @@ #include #include #include -#include #include #include @@ -239,38 +240,15 @@ public: void setHotReloadInterval(float interval); private: - /** - * @brief 资源文件监控信息 - */ - struct FileWatchInfo { - std::string path; - std::filesystem::file_time_type lastWriteTime; - Handle textureHandle; - Handle shaderHandle; - bool isTexture = false; - }; - - /** - * @brief 添加文件监控 - */ - void addFileWatch(const std::string &path, Handle handle); - void addFileWatch(const std::string &path, Handle 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 defaultMaterial_; Handle defaultQuad_; - // 热重载 - bool hotReloadEnabled_ = false; - float hotReloadInterval_ = 1.0f; - float hotReloadTimer_ = 0.0f; - std::vector 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 loadQueue_; - std::vector workerThreads_; - std::mutex queueMutex_; - std::condition_variable queueCV_; - std::atomic asyncLoaderRunning_{false}; - - // 完成的回调队列(主线程处理) - std::vector> completedCallbacks_; - std::mutex callbackMutex_; - - void workerThreadLoop(); + AssetAsyncLoader asyncLoader_; //=========================================================================== // 资源依赖跟踪 @@ -412,10 +373,7 @@ public: void notifyShaderReloaded(Handle shader); private: - // 资源依赖映射 - std::unordered_map textureDependencies_; - std::unordered_map shaderDependencies_; - std::shared_mutex dependencyMutex_; + AssetDependencyTracker dependencyTracker_; }; // 全局访问 diff --git a/include/assets/async/asset_async_loader.h b/include/assets/async/asset_async_loader.h new file mode 100644 index 0000000..bc2f56e --- /dev/null +++ b/include/assets/async/asset_async_loader.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +class AssetAsyncLoader { +public: + enum class Priority { Low = 0, Normal = 1, High = 2 }; + + struct Task { + Priority priority = Priority::Normal; + std::function work; + std::function 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 queue_; + std::vector workers_; + std::mutex queueMutex_; + std::condition_variable queueCV_; + std::atomic running_{false}; + + std::vector> completedCallbacks_; + std::mutex callbackMutex_; +}; + +} // namespace extra2d + diff --git a/include/assets/deps/asset_dependency_tracker.h b/include/assets/deps/asset_dependency_tracker.h new file mode 100644 index 0000000..5ac6d1c --- /dev/null +++ b/include/assets/deps/asset_dependency_tracker.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +class AssetDependencyTracker { +public: + struct DependencyInfo { + Handle texture; + Handle shader; + std::vector> dependentMaterials; + }; + + void clear(); + void registerMaterialDependency(Handle material, + Handle texture); + void registerMaterialDependency(Handle material, + Handle shader); + void notifyTextureReloaded( + Handle texture, + const std::function)> &onMaterialUpdate) const; + void notifyShaderReloaded( + Handle shader, + const std::function)> &onMaterialUpdate) const; + +private: + std::unordered_map textureDependencies_; + std::unordered_map shaderDependencies_; + mutable std::shared_mutex mutex_; +}; + +} // namespace extra2d diff --git a/include/assets/hot_reload/asset_hot_reloader.h b/include/assets/hot_reload/asset_hot_reloader.h new file mode 100644 index 0000000..3b8cf11 --- /dev/null +++ b/include/assets/hot_reload/asset_hot_reloader.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +class AssetHotReloader { +public: + struct FileWatchInfo { + std::string path; + std::filesystem::file_time_type lastWriteTime; + Handle textureHandle; + Handle 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 handle); + void addFileWatch(const std::string &path, Handle handle); + void clear(); + void checkForChanges( + const std::function &reloadTexture, + const std::function &reloadShader); + +private: + void addFileWatchInternal(const std::string &path, FileWatchInfo &&info); + + const AssetFileSystem &fileSystem_; + bool enabled_ = false; + float interval_ = 1.0f; + std::vector watchList_; + mutable std::shared_mutex mutex_; +}; + +} // namespace extra2d diff --git a/include/assets/io/asset_file_system.h b/include/assets/io/asset_file_system.h new file mode 100644 index 0000000..e6823e5 --- /dev/null +++ b/include/assets/io/asset_file_system.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +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 + diff --git a/include/assets/loaders/shader_loader.h b/include/assets/loaders/shader_loader.h index adcee90..37ab808 100644 --- a/include/assets/loaders/shader_loader.h +++ b/include/assets/loaders/shader_loader.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace extra2d { @@ -12,6 +13,8 @@ namespace extra2d { */ class ShaderLoader : public AssetLoader { public: + ShaderLoader() = default; + explicit ShaderLoader(AssetFileSystem fileSystem); /** * @brief 从文件加载着色器 * @param path 单个文件路径(自动推断 .vert/.frag) @@ -50,6 +53,9 @@ public: std::vector getExtensions() const override { return {".glsl", ".vert", ".frag"}; } + +private: + AssetFileSystem fileSystem_; }; } // namespace extra2d diff --git a/include/renderer/command_queue.h b/include/renderer/command_queue.h index 3ef6a61..8d783b1 100644 --- a/include/renderer/command_queue.h +++ b/include/renderer/command_queue.h @@ -181,6 +181,7 @@ private: std::vector commands_; // 命令缓冲区(复用) std::vector 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, Ptr 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 { diff --git a/include/renderer/render_graph.h b/include/renderer/render_graph.h index cac789b..a46e840 100644 --- a/include/renderer/render_graph.h +++ b/include/renderer/render_graph.h @@ -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 获取纹理资源 diff --git a/include/renderer/renderer_module.h b/include/renderer/renderer_module.h index f818aaf..518290b 100644 --- a/include/renderer/renderer_module.h +++ b/include/renderer/renderer_module.h @@ -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 diff --git a/include/renderer/rhi/opengl/gl_command_list.h b/include/renderer/rhi/opengl/gl_command_list.h index 6b4b174..036c5ba 100644 --- a/include/renderer/rhi/opengl/gl_command_list.h +++ b/include/renderer/rhi/opengl/gl_command_list.h @@ -2,6 +2,8 @@ #include #include +#include +#include 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> + uniformLocationCache_; // 统计信息(调试用) struct Stats { diff --git a/src/assets/assets_module.cpp b/src/assets/assets_module.cpp index 835bf5d..3b61bfa 100644 --- a/src/assets/assets_module.cpp +++ b/src/assets/assets_module.cpp @@ -4,69 +4,14 @@ #include #include #include -#include -#include #include #include namespace extra2d { -namespace { - -/** - * @brief 获取 FileModule 实例 - */ -FileModule *getFileModule() { return getModule(); } - -/** - * @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(); - shaderLoader_ = std::make_unique(); + shaderLoader_ = std::make_unique(fileSystem_); // 监听窗口显示事件,在 OpenGL 上下文创建完成后创建默认资源 onShowListener_ = std::make_unique(); @@ -125,15 +70,10 @@ void AssetsModule::shutdown() { texturePathCache_.clear(); shaderPathCache_.clear(); - fileWatchList_.clear(); } - // 清空依赖关系 - { - std::unique_lock lock(dependencyMutex_); - textureDependencies_.clear(); - shaderDependencies_.clear(); - } + hotReloader_.clear(); + dependencyTracker_.clear(); textureLoader_.reset(); shaderLoader_.reset(); @@ -191,8 +131,8 @@ Handle AssetsModule::load(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 AssetsModule::load(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 AssetsModule::load(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(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( const std::string &path, std::function)> callback) { // 确保异步加载系统已初始化 - if (!asyncLoaderRunning_) { + if (!asyncLoader_.isRunning()) { initAsyncLoader(); } @@ -414,7 +354,7 @@ template <> void AssetsModule::loadAsync( const std::string &path, std::function)> 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 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 handle) { - FileWatchInfo info; - info.textureHandle = handle; - info.isTexture = true; - addFileWatchInternal(path, std::move(info)); -} - -void AssetsModule::addFileWatch(const std::string &path, - Handle 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 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 lock(queueMutex_); - loadQueue_.clear(); - + asyncLoader_.shutdown(); E2D_INFO("异步加载器已关闭"); } void AssetsModule::submitLoadTask(const LoadTask &task) { - { - std::lock_guard lock(queueMutex_); - loadQueue_.push_back(task); + AssetAsyncLoader::Task asyncTask; + asyncTask.priority = static_cast(task.priority); - // 按优先级排序 - std::sort(loadQueue_.begin(), loadQueue_.end(), - [](const LoadTask &a, const LoadTask &b) { - return static_cast(a.priority) > - static_cast(b.priority); - }); - } - queueCV_.notify_one(); -} - -void AssetsModule::workerThreadLoop() { - while (asyncLoaderRunning_) { - LoadTask task; - - { - std::unique_lock 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::invalid()); + asyncTask.work = [this, path = task.path, handle]() { + *handle = load(path); + }; + if (task.textureCallback) { + asyncTask.onComplete = [handle, callback = task.textureCallback]() { + callback(*handle); + }; } - - // 执行加载任务 - if (task.type == LoadTask::Type::Texture) { - Handle handle = load(task.path); - - if (task.textureCallback) { - std::lock_guard callbackLock(callbackMutex_); - completedCallbacks_.push_back( - [handle, callback = task.textureCallback]() { callback(handle); }); - } - } else if (task.type == LoadTask::Type::Shader) { - Handle handle; - if (task.secondaryPath.empty()) { - handle = load(task.path); + } else { + auto handle = std::make_shared>(Handle::invalid()); + asyncTask.work = [this, path = task.path, secondary = task.secondaryPath, + handle]() { + if (secondary.empty()) { + *handle = load(path); } else { - handle = load(task.path, task.secondaryPath); - } - - if (task.shaderCallback) { - std::lock_guard callbackLock(callbackMutex_); - completedCallbacks_.push_back( - [handle, callback = task.shaderCallback]() { callback(handle); }); + *handle = load(path, secondary); } + }; + if (task.shaderCallback) { + asyncTask.onComplete = [handle, callback = task.shaderCallback]() { + callback(*handle); + }; } } + + asyncLoader_.submit(asyncTask); } void AssetsModule::processAsyncCallbacks() { - std::vector> callbacks; - - { - std::lock_guard 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, Handle texture) { - if (!material.isValid() || !texture.isValid()) { - return; - } - - std::unique_lock 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, Handle shader) { - if (!material.isValid() || !shader.isValid()) { - return; - } - - std::unique_lock 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) { - if (!texture.isValid()) { - return; - } - - std::shared_lock 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 materialHandle) { + Material *material = materials_.get(materialHandle); + if (material) { + E2D_DEBUG("材质 {} 已更新为重载后的纹理", materialHandle.index()); + } + }); } void AssetsModule::notifyShaderReloaded(Handle shader) { - if (!shader.isValid()) { - return; - } - - std::shared_lock 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 materialHandle) { + Material *material = materials_.get(materialHandle); + if (material) { + material->setShader(getPtr(shader)); + E2D_DEBUG("材质 {} 已更新为重载后的着色器", materialHandle.index()); + } + }); } //=========================================================================== diff --git a/src/assets/async/asset_async_loader.cpp b/src/assets/async/asset_async_loader.cpp new file mode 100644 index 0000000..be2503b --- /dev/null +++ b/src/assets/async/asset_async_loader.cpp @@ -0,0 +1,98 @@ +#include +#include + +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 lock(queueMutex_); + queue_.clear(); + } +} + +bool AssetAsyncLoader::isRunning() const { return running_; } + +void AssetAsyncLoader::submit(const Task &task) { + { + std::lock_guard lock(queueMutex_); + queue_.push_back(task); + std::sort(queue_.begin(), queue_.end(), [](const Task &a, const Task &b) { + return static_cast(a.priority) > static_cast(b.priority); + }); + } + queueCV_.notify_one(); +} + +void AssetAsyncLoader::workerThreadLoop() { + while (running_) { + Task task; + { + std::unique_lock 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 callbackLock(callbackMutex_); + completedCallbacks_.push_back(task.onComplete); + } + } +} + +void AssetAsyncLoader::processCallbacks() { + std::vector> callbacks; + { + std::lock_guard lock(callbackMutex_); + callbacks = std::move(completedCallbacks_); + completedCallbacks_.clear(); + } + + for (auto &callback : callbacks) { + callback(); + } +} + +} // namespace extra2d + diff --git a/src/assets/deps/asset_dependency_tracker.cpp b/src/assets/deps/asset_dependency_tracker.cpp new file mode 100644 index 0000000..bc36978 --- /dev/null +++ b/src/assets/deps/asset_dependency_tracker.cpp @@ -0,0 +1,89 @@ +#include +#include + +namespace extra2d { + +void AssetDependencyTracker::clear() { + std::unique_lock lock(mutex_); + textureDependencies_.clear(); + shaderDependencies_.clear(); +} + +void AssetDependencyTracker::registerMaterialDependency(Handle material, + Handle texture) { + if (!material.isValid() || !texture.isValid()) { + return; + } + + std::unique_lock 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, + Handle shader) { + if (!material.isValid() || !shader.isValid()) { + return; + } + + std::unique_lock 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, + const std::function)> &onMaterialUpdate) const { + if (!texture.isValid()) { + return; + } + + std::vector> materials; + { + std::shared_lock 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, + const std::function)> &onMaterialUpdate) const { + if (!shader.isValid()) { + return; + } + + std::vector> materials; + { + std::shared_lock 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 + diff --git a/src/assets/hot_reload/asset_hot_reloader.cpp b/src/assets/hot_reload/asset_hot_reloader.cpp new file mode 100644 index 0000000..1f6cf0d --- /dev/null +++ b/src/assets/hot_reload/asset_hot_reloader.cpp @@ -0,0 +1,91 @@ +#include +#include + +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 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 handle) { + FileWatchInfo info; + info.textureHandle = handle; + info.isTexture = true; + addFileWatchInternal(path, std::move(info)); +} + +void AssetHotReloader::addFileWatch(const std::string &path, + Handle handle) { + FileWatchInfo info; + info.shaderHandle = handle; + info.isTexture = false; + addFileWatchInternal(path, std::move(info)); +} + +void AssetHotReloader::clear() { + std::unique_lock lock(mutex_); + watchList_.clear(); +} + +void AssetHotReloader::checkForChanges( + const std::function &reloadTexture, + const std::function &reloadShader) { + if (!enabled_) { + return; + } + + std::unique_lock 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 + diff --git a/src/assets/io/asset_file_system.cpp b/src/assets/io/asset_file_system.cpp new file mode 100644 index 0000000..eb36b44 --- /dev/null +++ b/src/assets/io/asset_file_system.cpp @@ -0,0 +1,48 @@ +#include +#include +#include + +namespace extra2d { + +std::string AssetFileSystem::assetPath(const std::string &path) const { + FileModule *fileModule = getModule(); + if (!fileModule) { + return path; + } + return fileModule->assetPath(path); +} + +bool AssetFileSystem::isRomfsPath(const std::string &path) const { + FileModule *fileModule = getModule(); + if (!fileModule) { + return false; + } + return fileModule->isRomfsPath(path); +} + +bool AssetFileSystem::exists(const std::string &path) const { + FileModule *fileModule = getModule(); + if (!fileModule) { + return false; + } + return fileModule->exists(path); +} + +std::string AssetFileSystem::readString(const std::string &path) const { + FileModule *fileModule = getModule(); + 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 + diff --git a/src/assets/loaders/shader_loader.cpp b/src/assets/loaders/shader_loader.cpp index 82fd6d4..fa1cb36 100644 --- a/src/assets/loaders/shader_loader.cpp +++ b/src/assets/loaders/shader_loader.cpp @@ -1,41 +1,11 @@ #include -#include -#include #include #include namespace extra2d { -namespace { - -/** - * @brief 获取 FileModule 实例 - */ -FileModule *getFileModule() { return getModule(); } - -/** - * @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 ShaderLoader::load(const std::string &path) { std::string basePath = path; @@ -48,10 +18,10 @@ Ptr 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 ShaderLoader::load(const std::string &path) { Ptr 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(); } // 读取片段着色器文件 - std::string fsSource = readFileToString(fragPath); + std::string fsSource = fileSystem_.readString(fragPath); if (fsSource.empty()) { E2D_ERROR("ShaderLoader: 读取片段着色器失败: {}", fragPath); return Ptr(); diff --git a/src/renderer/command_queue.cpp b/src/renderer/command_queue.cpp index 9e5eea1..703d5ad 100644 --- a/src/renderer/command_queue.cpp +++ b/src/renderer/command_queue.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -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, Ptr 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, Ptr mesh, // 构建排序键 uint32_t materialId = getMaterialId(material.get()); - cmd.key = DrawKey::make(materialId, 0, 0); + uint16_t depth = static_cast((sortKey >> 8) & 0xFFFF); + uint8_t layer = static_cast(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(screenWidth); globalUBOData_.screenSize[1] = static_cast(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(sortEnd - sortStart).count(); + profilingStats_.batchMs = + std::chrono::duration(batchEnd - batchStart).count(); + profilingStats_.executeMs = std::chrono::duration( + executeEnd - executeStart) + .count(); + profilingStats_.totalMs = + std::chrono::duration(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(-1); + uint32_t lastMaterialSize = static_cast(-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; + } } // 设置模型矩阵 diff --git a/src/renderer/render_graph.cpp b/src/renderer/render_graph.cpp index c1cf21d..b9a4942 100644 --- a/src/renderer/render_graph.cpp +++ b/src/renderer/render_graph.cpp @@ -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 上下文 diff --git a/src/renderer/renderer_module.cpp b/src/renderer/renderer_module.cpp index 7e0dee8..660749d 100644 --- a/src/renderer/renderer_module.cpp +++ b/src/renderer/renderer_module.cpp @@ -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), Ptr(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); } } diff --git a/src/renderer/rhi/opengl/gl_command_list.cpp b/src/renderer/rhi/opengl/gl_command_list.cpp index 2c2fe9d..00186e2 100644 --- a/src/renderer/rhi/opengl/gl_command_list.cpp +++ b/src/renderer/rhi/opengl/gl_command_list.cpp @@ -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)); }