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
+14 -6
View File
@@ -5,32 +5,40 @@
#ifndef HELLOVULKAN_COMMANDMANAGER_H
#define HELLOVULKAN_COMMANDMANAGER_H
#include <Engine/Core/Types.h>
#include <Engine/Core/FrameContext.h>
#include <vulkan/vulkan.hpp>
#include <vector>
namespace Engine {
class Device;
// 每个帧槽位独立命令缓冲。
//
// 关键正确性约束:kMaxFramesInFlight 帧并发时,第 N 帧录制时第 N-1 帧的
// 命令缓冲可能仍在 GPU 上执行,因此绝不能共享/复用同一个 command buffer。
// 每个 frameSlot 一个 buffer,reset 只发生在"该槽位 fence 已确认完成"之后。
class CommandManager {
public:
CommandManager(Device& device);
CommandManager(Device& device, u32 frameCount);
~CommandManager();
CommandManager(const CommandManager&) = delete;
CommandManager& operator=(const CommandManager&) = delete;
// 开始录制
vk::CommandBuffer BeginFrame();
// 开始录制指定帧槽位的命令缓冲
vk::CommandBuffer BeginFrame(u32 frameSlot);
// 结束录制
void EndFrame();
void EndFrame(u32 frameSlot);
vk::CommandBuffer GetBuffer() const { return m_Buffer; }
vk::CommandBuffer GetBuffer(u32 frameSlot) const { return m_Buffers[frameSlot]; }
private:
Device& m_Device;
vk::CommandPool m_Pool;
vk::CommandBuffer m_Buffer;
std::vector<vk::CommandBuffer> m_Buffers;
};
} // namespace Engine
+5
View File
@@ -34,6 +34,11 @@ public:
uint32_t FindMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags props) const;
// 在候选列表中找到第一个满足 tiling+features 的格式,找不到则 FATAL
vk::Format FindSupportedFormat(const std::vector<vk::Format>& candidates,
vk::ImageTiling tiling,
vk::FormatFeatureFlags features) const;
// 1.4 特性查询
bool IsDynamicRenderingSupported() const { return m_DynamicRenderingSupported; }
bool IsShaderObjectSupported() const { return m_ShaderObjectSupported; }
+2 -13
View File
@@ -33,10 +33,8 @@ namespace Engine
const std::vector<vk::Image>& GetImages() const { return m_Images; }
const std::vector<vk::ImageView>& GetImageViews() const { return m_ImageViews; }
vk::ImageView GetDepthImageView() const { return m_DepthImageView; }
vk::Format GetDepthFormat() const { return m_DepthFormat; }
vk::Image GetDepthImage() const { return m_DepthImage; }
// 深度图不在 Swapchain 管理——渲染目标(含深度)统一由 RenderGraph 创建。
// Swapchain 只负责"呈现"这一件事。
void Recreate(Window& window);
private:
@@ -47,10 +45,6 @@ namespace Engine
vk::PresentModeKHR ChoosePresentMode(const std::vector<vk::PresentModeKHR>& modes);
vk::Extent2D ChooseExtent(const vk::SurfaceCapabilitiesKHR& caps, Window& window);
void CreateDepthResources();
void DestroyDepthResources();
vk::Format FindDepthFormat();
Device& m_Device;
@@ -58,11 +52,6 @@ namespace Engine
std::vector<vk::Image> m_Images;
std::vector<vk::ImageView> m_ImageViews;
vk::Image m_DepthImage;
vk::DeviceMemory m_DepthMemory;
vk::ImageView m_DepthImageView;
vk::Format m_DepthFormat;
MemoryAllocator& m_Allocator;
vk::Format m_Format;
+17 -8
View File
@@ -5,33 +5,42 @@
#ifndef HELLOVULKAN_UNIFORMBUFFER_H
#define HELLOVULKAN_UNIFORMBUFFER_H
#include <Engine/Core/Types.h>
#include <Engine/Core/FrameContext.h>
#include <Engine/Vulkan/Buffer.h>
#include <vulkan/vulkan.hpp>
#include <vector>
namespace Engine {
class Device;
class MemoryAllocator;
// Per-frame UBO ring:每个帧槽位一个独立 buffer。
//
// 正确性:CPU 在第 N 帧写 UBO 时,GPU 可能仍在读取第 N-1 帧的同槽位数据。
// 共享单个 buffer 会产生数据竞争;ring 化后每槽位的写入严格发生在
// 该槽位 fence 确认完成之后。
// 后续演进方向:单个大 ring buffer + 动态 offset(省描述符、省内存),
// 接口已按"写入哪个槽位"设计,届时只是内部实现替换。
class UniformBuffer {
public:
UniformBuffer(Device& device, MemoryAllocator& allocator, u64 size);
~UniformBuffer();
UniformBuffer(Device& device, MemoryAllocator& allocator,
u64 size, u32 frameCount);
~UniformBuffer() = default;
UniformBuffer(const UniformBuffer&) = delete;
UniformBuffer& operator=(const UniformBuffer&) = delete;
vk::Buffer Get() const { return m_Buffer->Get(); }
vk::Buffer Get(u32 frameSlot) const { return m_Buffers[frameSlot]->Get(); }
u64 GetSize() const { return m_Size; }
// 每帧更新数据
void Update(const void* data, u64 size);
// 写入指定帧槽位的 UBO(仅在该槽位 fence 确认完成后调用)
void Update(u32 frameSlot, const void* data, u64 size);
private:
Scope<Buffer> m_Buffer;
u64 m_Size;
std::vector<Scope<Buffer>> m_Buffers;
u64 m_Size = 0;
};
} // namespace Engine