feat: 模型异步加载——assimp 解析移后台线程(SunTemple 3s 卡顿消除)

Model 拆两段式:ParseFromDisk(static 纯 CPU,assimp 解析 → 几何 +
材质引用,worker 可调)/ FromParsed(GPU 构建:纹理上传 / mesh 缓冲 /
材质描述符,主线程),同步路径等价两段串行。第二个异步任务类型落地,
线程收割骨架抽成 Core/AsyncTask<T>(thread + atomic + Take,覆盖式
Run;pending 接力语义留调用方)。

实测(SunTemple 1056 mesh):解析 2.5s 全程在 worker,主线程持续
渲染;GPU 构建 0.56s。assets 层经 IAssetUploader/AssetManager 重载
传预解析产物(路径缓存去重,路径已缓存则产物丢弃)。

已知边界:纹理解码(backpack 4k PNG ~1s)仍在 GPU 段主线程——
Texture 需支持预解码像素注入,后续演进。
This commit is contained in:
2026-09-24 19:31:14 +08:00
parent 6df3be2846
commit 99e6712cdf
9 changed files with 260 additions and 26 deletions
+82
View File
@@ -0,0 +1,82 @@
//
// Created by atdunbg on 2026/9/24.
//
// AsyncTask:单 in-flight 异步任务的通用收割工具(header-only)。
//
// 异步加载的公共骨架(已验证两次:环境 DecodeHdr / 模型 ParseFromDisk):
// worker 线程跑纯 CPU 任务 → 原子标志通知 → 主线程每帧轮询收割。
// Vulkan 提交/描述符等 GPU 工作全部留在主线程(设备非线程安全)。
//
// AsyncTask<ParsedModel> task;
// task.Run([] { return Model::ParseFromDisk(path); }); // 发起
// if (task.Done()) { auto p = task.Take(); ... } // 每帧收割
// // 析构自动 join(worker 只做纯 CPU 工作,join 恒安全)
//
// 语义:Run 在忙碌时先 join 并丢弃旧结果(覆盖式);"快速连切只保留
// 最终意图"的 pending 接力由调用方持有(参照 main.cpp 环境加载)。
//
#ifndef HELLOVULKAN_ASYNCTASK_H
#define HELLOVULKAN_ASYNCTASK_H
#include <Engine/Core/Types.h>
#include <atomic>
#include <functional>
#include <thread>
#include <utility>
namespace Engine {
template <typename T>
class AsyncTask {
public:
AsyncTask() = default;
~AsyncTask() { Join(); }
AsyncTask(const AsyncTask&) = delete;
AsyncTask& operator=(const AsyncTask&) = delete;
// 发起任务。忙碌时先等待旧任务完成并放弃其结果。
void Run(std::function<T()> job)
{
Join();
m_Busy = true;
m_Done.store(false);
m_Worker = std::thread([this, j = std::move(job)]() {
m_Result = j();
m_Done.store(true); // release:结果对主线程 acquire 可见
});
}
bool Busy() const { return m_Busy; }
bool Done() const { return m_Busy && m_Done.load(); }
// 收割:join worker 并移交结果。只在 Done() 为 true 时调用。
T Take()
{
m_Worker.join(); // join = 同步点,此后结果归主线程
m_Busy = false;
T out = std::move(m_Result);
m_Result = T{};
return out;
}
// 关闭前兜底等待(析构自动调用)
void Join()
{
if (m_Worker.joinable()) {
m_Worker.join();
}
m_Busy = false;
}
private:
std::thread m_Worker;
std::atomic<bool> m_Done{false};
T m_Result{};
bool m_Busy = false;
};
} // namespace Engine
#endif //HELLOVULKAN_ASYNCTASK_H