feat: 引入 RenderGraph、Application 框架、ECS 与资产句柄系统

架构分层(自上而下):
  App(SandboxApp) → Application(生命周期+事件) → Scene(ECS) / AssetManager(句柄) → Renderer → RenderGraph → Vulkan 模块

- RenderGraph:声明式帧图。虚拟资源+生成号句柄、死 pass 剔除、
  拓扑排序、资源生命周期、barrier 编译期预算(Synchronization2)、
  物理镜像池按帧槽位隔离(帧间竞争从根上消除)
- 帧同步修复:per-frame-slot 命令缓冲与 UBO ring(原先单份存在
  数据竞争);统一 kMaxFramesInFlight / FrameContext
- Application:生命周期钩子(OnInit/OnUpdate/OnRender/OnShutdown)
  + 事件系统(GLFW 回调 → POD 事件 → 类型化钩子);main 只 new 派生类
- ECS:自研稀疏集 Registry(Entity=(index,generation) 防悬空,
  O(1) 增删查,Each 取最小池遍历);Scene 延迟销毁队列;
  IdComponent{UUID} 稳定外部身份
- Assets:AssetManager + AssetHandle。组件存句柄不存裸指针/Ref
  (单一所有者 + 多使用者),Renderer 删除 LoadModel 改为纯消费
- 修复退出析构:成员析构前 GPU waitIdle(资产层先于 Renderer
  析构导致 VUID-00922 资源销毁时仍在使用)
- 引入 entt 子模块备用

validation 全程零错误零警告(RTX 5060 实测)
This commit is contained in:
2026-09-22 13:46:05 +08:00
parent 7d628f8d8a
commit 8afdd9b245
33 changed files with 3013 additions and 561 deletions
+101
View File
@@ -0,0 +1,101 @@
//
// Created by atdunbg on 2026/9/22.
//
// Application:程序生命周期基类。
//
// 分层目标:main 不再知道 Window/Renderer 的存在——
// main 只 new 一个派生类并 Run();
// 派生类通过生命周期钩子(OnInit/OnUpdate/OnRender/OnShutdown)
// 和事件钩子(OnEvent + 类型化事件)定义程序行为;
// runtime、editor 等不同程序形态只是不同的派生类。
//
// 主循环固定为:PollEvents → OnUpdate(dt) → OnRender → 判退出。
// dt 由框架统一计时(含首帧与卡顿钳制),派生层无需自己管理。
//
#ifndef HELLOVULKAN_APPLICATION_H
#define HELLOVULKAN_APPLICATION_H
#include <Engine/Core/Types.h>
#include <Engine/Core/Event.h>
#include <chrono>
#include <string>
namespace Engine {
class Window;
class Renderer;
namespace ECS { class Scene; }
namespace Assets { class AssetManager; }
class Application {
public:
Application(std::string title, u32 width, u32 height);
virtual ~Application();
Application(const Application&) = delete;
Application& operator=(const Application&) = delete;
// 进入主循环(阻塞直到 Close() 或窗口关闭)
void Run();
// 请求退出(当帧结束后生效)
void Close() { m_Running = false; }
// ---- 供派生类访问的模块 ----
Window& GetWindow() const { return *m_Window; }
Renderer& GetRenderer() const { return *m_Renderer; }
ECS::Scene& GetScene() const { return *m_Scene; }
Assets::AssetManager& GetAssets() const { return *m_Assets; }
f32 GetDeltaTime() const { return m_DeltaTime; }
u64 GetFrameCount() const { return m_FrameCount; }
protected:
// ---- 生命周期钩子 ----
// 框架装配完成(Window/Renderer 可用),派生类做初始化(建场景等)
virtual void OnInit() {}
// 每帧逻辑更新(输入、动画、物理……)
virtual void OnUpdate(f32 deltaTime) {}
// 每帧渲染;默认实现驱动 Renderer 主循环,派生类可覆写
virtual void OnRender();
// 主循环结束后、析构前(保存、清理场景等)
virtual void OnShutdown() {}
// ---- 事件钩子 ----
// OnEvent 收到所有事件(原始入口,适合做全局日志/拦截);
// 类型化钩子按事件类型分别回调,派生类按需覆写。
virtual void OnEvent(const Event& event) {}
virtual void OnWindowClose(const Event& event);
virtual void OnWindowResize(const Event& event) {}
virtual void OnWindowFocus(const Event& event) {}
virtual void OnWindowLostFocus(const Event& event) {}
virtual void OnKeyPress(const Event& event) {}
virtual void OnKeyRelease(const Event& event) {}
virtual void OnMouseButtonPress(const Event& event) {}
virtual void OnMouseButtonRelease(const Event& event) {}
virtual void OnMouseMove(const Event& event) {}
virtual void OnMouseScroll(const Event& event) {}
private:
void DispatchEvent(const Event& event);
Scope<Window> m_Window;
Scope<ECS::Scene> m_Scene;
Scope<Renderer> m_Renderer;
Scope<Assets::AssetManager> m_Assets;
bool m_Running = false;
f32 m_DeltaTime = 0.0f;
u64 m_FrameCount = 0;
std::chrono::steady_clock::time_point m_LastFrameTime;
};
} // namespace Engine
#endif //HELLOVULKAN_APPLICATION_H
+76
View File
@@ -0,0 +1,76 @@
//
// Created by atdunbg on 2026/9/22.
//
// Event:轻量级事件。
//
// 设计取舍:不用继承体系(每事件一个类 + dynamic_cast 分发),
// 用单个 POD 结构 + 类型枚举。事件每帧可能产生成百上千条
// (MouseMove 尤其频繁),POD 按值传递/排队最便宜;
// 类型安全由字段约定保证,规模上来后再演进为 variant/队列池。
//
#ifndef HELLOVULKAN_EVENT_H
#define HELLOVULKAN_EVENT_H
#include <Engine/Core/Types.h>
namespace Engine {
enum class EventType : u8 {
None = 0,
// 窗口
WindowClose,
WindowResize,
WindowFocus,
WindowLostFocus,
// 键盘
KeyPress,
KeyRelease,
// 鼠标
MouseButtonPress,
MouseButtonRelease,
MouseMove,
MouseScroll,
};
struct Event {
EventType type = EventType::None;
// 键盘(GLFW key code)/ 鼠标按钮
i32 key = 0;
i32 button = 0;
// 鼠标位置 / 滚轮
f32 x = 0.0f;
f32 y = 0.0f;
f32 deltaX = 0.0f;
f32 deltaY = 0.0f;
// 窗口尺寸
i32 width = 0;
i32 height = 0;
};
inline const char* ToString(EventType type)
{
switch (type) {
case EventType::WindowClose: return "WindowClose";
case EventType::WindowResize: return "WindowResize";
case EventType::WindowFocus: return "WindowFocus";
case EventType::WindowLostFocus: return "WindowLostFocus";
case EventType::KeyPress: return "KeyPress";
case EventType::KeyRelease: return "KeyRelease";
case EventType::MouseButtonPress: return "MouseButtonPress";
case EventType::MouseButtonRelease: return "MouseButtonRelease";
case EventType::MouseMove: return "MouseMove";
case EventType::MouseScroll: return "MouseScroll";
default: return "None";
}
}
} // namespace Engine
#endif //HELLOVULKAN_EVENT_H
+29
View File
@@ -0,0 +1,29 @@
//
// Created by atdunbg on 2026/9/22.
//
// FrameContext:一帧在 GPU 侧的全部上下文。
// 引擎中所有 per-frame 资源(命令缓冲、UBO ring、描述符池等)
// 都以 frameSlot 为索引,保证 kMaxFramesInFlight 帧并发时互不踩踏。
//
#ifndef HELLOVULKAN_FRAMECONTEXT_H
#define HELLOVULKAN_FRAMECONTEXT_H
#include <Engine/Core/Types.h>
#include <vulkan/vulkan.hpp>
namespace Engine {
// 引擎统一的最大并发帧数。
// 所有 per-frame 资源必须以此为 ring 长度,不允许再出现"单独一个"的帧资源。
inline constexpr u32 kMaxFramesInFlight = 2;
struct FrameContext {
u32 frameSlot = 0; // 帧-in-flight 槽位 [0, kMaxFramesInFlight)
u32 imageIndex = 0; // 本次 acquire 到的 swapchain image 索引
vk::CommandBuffer cmd;
};
} // namespace Engine
#endif //HELLOVULKAN_FRAMECONTEXT_H
+52
View File
@@ -0,0 +1,52 @@
//
// Created by atdunbg on 2026/9/22.
//
// UUID:64 位随机唯一标识,实体的稳定外部身份。
//
// 用途:序列化、跨场景引用、编辑器对象选择——这些场景需要
// "实体销毁重建后 ID 不变"或"独立于槽位分配"的稳定标识。
// ECS 内部遍历仍用 (index, generation) 句柄(性能),UUID 只做外部身份。
//
#ifndef HELLOVULKAN_UUID_H
#define HELLOVULKAN_UUID_H
#include <Engine/Core/Types.h>
#include <functional>
#include <random>
namespace Engine {
struct UUID {
UUID() = default;
explicit UUID(u64 value) : m_Value(value) {}
// 生成全局唯一 ID(非确定性种子,跨进程不重复)
static UUID New()
{
static std::mt19937_64 rng{std::random_device{}()};
return UUID{rng()};
}
u64 Value() const { return m_Value; }
bool Valid() const { return m_Value != 0; }
bool operator==(const UUID& o) const = default;
bool operator!=(const UUID& o) const = default;
private:
u64 m_Value = 0; // 0 保留为"无效"
};
} // namespace Engine
namespace std {
template<> struct hash<Engine::UUID> {
size_t operator()(const Engine::UUID& id) const noexcept {
return std::hash<std::uint64_t>{}(id.Value());
}
};
}
#endif //HELLOVULKAN_UUID_H
+14
View File
@@ -11,6 +11,7 @@
#include <functional>
#include "Types.h"
#include "Event.h"
struct GLFWwindow;
@@ -27,6 +28,11 @@ namespace Engine {
bool ShouldClose() const;
void PollEvents();
// 事件出口:GLFW 回调 → Event → 上层回调(由 Application 注册)。
// 未注册时事件直接丢弃。
using EventCallbackFn = std::function<void(const Event&)>;
void SetEventCallback(EventCallbackFn callback) { m_EventCallback = std::move(callback); }
vk::SurfaceKHR CreateSurface(vk::Instance instance);
void DestroySurface(vk::Instance instance, vk::SurfaceKHR surface);
@@ -57,7 +63,15 @@ namespace Engine {
std::string m_Title;
bool m_FramebufferResized = false;
EventCallbackFn m_EventCallback;
// GLFW 静态回调(通过 window user pointer 找回 this)
static void FramebufferResizeCallback(GLFWwindow* window, int width, int height);
static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
static void MouseButtonCallback(GLFWwindow* window, int button, int action, int mods);
static void CursorPosCallback(GLFWwindow* window, double xpos, double ypos);
static void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset);
static void WindowFocusCallback(GLFWwindow* window, int focused);
};
} // namespace Engine