From 8afdd9b24508a4a9c40cc1b20f1975cb6f0ea9fb Mon Sep 17 00:00:00 2001 From: atdunbg Date: Tue, 22 Sep 2026 13:46:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BC=95=E5=85=A5=20RenderGraph?= =?UTF-8?q?=E3=80=81Application=20=E6=A1=86=E6=9E=B6=E3=80=81ECS=20?= =?UTF-8?q?=E4=B8=8E=E8=B5=84=E4=BA=A7=E5=8F=A5=E6=9F=84=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 架构分层(自上而下): 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 实测) --- .gitmodules | 3 + App/CMakeLists.txt | 27 +- App/main.cpp | 128 ++- Engine/include/Engine/Assets/AssetHandle.h | 37 + Engine/include/Engine/Assets/AssetManager.h | 173 ++++ Engine/include/Engine/Core/Application.h | 101 +++ Engine/include/Engine/Core/Event.h | 76 ++ Engine/include/Engine/Core/FrameContext.h | 29 + Engine/include/Engine/Core/UUID.h | 52 ++ Engine/include/Engine/Core/Window.h | 14 + Engine/include/Engine/ECS/Components.h | 62 ++ Engine/include/Engine/ECS/Entity.h | 32 + Engine/include/Engine/ECS/Registry.h | 312 +++++++ Engine/include/Engine/ECS/Scene.h | 84 ++ .../include/Engine/RenderGraph/RenderGraph.h | 299 +++++++ Engine/include/Engine/Renderer/Camera.h | 66 ++ Engine/include/Engine/Renderer/Renderer.h | 72 +- Engine/include/Engine/Vulkan/CommandManager.h | 20 +- Engine/include/Engine/Vulkan/Device.h | 5 + Engine/include/Engine/Vulkan/Swapchain.h | 15 +- Engine/include/Engine/Vulkan/UniformBuffer.h | 25 +- Engine/src/Assets/AssetManager.cpp | 31 + Engine/src/Core/Application.cpp | 115 +++ Engine/src/Core/Window.cpp | 72 ++ Engine/src/ECS/Scene.cpp | 62 ++ Engine/src/RenderGraph/RenderGraph.cpp | 781 ++++++++++++++++++ Engine/src/Renderer/Camera.cpp | 64 ++ Engine/src/Renderer/Renderer.cpp | 660 +++++++-------- Engine/src/Vulkan/CommandManager.cpp | 26 +- Engine/src/Vulkan/Device.cpp | 20 + Engine/src/Vulkan/Swapchain.cpp | 78 -- Engine/src/Vulkan/UniformBuffer.cpp | 32 +- Lib/entt | 1 + 33 files changed, 3013 insertions(+), 561 deletions(-) create mode 100644 Engine/include/Engine/Assets/AssetHandle.h create mode 100644 Engine/include/Engine/Assets/AssetManager.h create mode 100644 Engine/include/Engine/Core/Application.h create mode 100644 Engine/include/Engine/Core/Event.h create mode 100644 Engine/include/Engine/Core/FrameContext.h create mode 100644 Engine/include/Engine/Core/UUID.h create mode 100644 Engine/include/Engine/ECS/Components.h create mode 100644 Engine/include/Engine/ECS/Entity.h create mode 100644 Engine/include/Engine/ECS/Registry.h create mode 100644 Engine/include/Engine/ECS/Scene.h create mode 100644 Engine/include/Engine/RenderGraph/RenderGraph.h create mode 100644 Engine/include/Engine/Renderer/Camera.h create mode 100644 Engine/src/Assets/AssetManager.cpp create mode 100644 Engine/src/Core/Application.cpp create mode 100644 Engine/src/ECS/Scene.cpp create mode 100644 Engine/src/RenderGraph/RenderGraph.cpp create mode 100644 Engine/src/Renderer/Camera.cpp create mode 160000 Lib/entt diff --git a/.gitmodules b/.gitmodules index 8bfc6a5..bf08744 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "Lib/assimp"] path = Lib/assimp url = https://github.com/assimp/assimp.git +[submodule "Lib/entt"] + path = Lib/entt + url = https://github.com/skypjack/entt.git diff --git a/App/CMakeLists.txt b/App/CMakeLists.txt index 53ec873..03a8411 100644 --- a/App/CMakeLists.txt +++ b/App/CMakeLists.txt @@ -3,19 +3,25 @@ add_executable(HelloVulkan main.cpp) target_link_libraries(HelloVulkan PRIVATE Engine) -###### 自动构建shader ###### +###### 自动构建 shader ###### find_program(GLSLC glslc REQUIRED) set(SHADER_SRC_DIR ${CMAKE_SOURCE_DIR}/shaders) set(SHADER_OUT_DIR ${CMAKE_BINARY_DIR}/bin/shaders) -# 定义一个 shader 编译目标:(glsl_file stage) -function(auto_compile_shader glsl_file stage) - # string(REGEX REPLACE "\\.glsl$" "" base "${glsl_file}") +function(auto_compile_shader glsl_file) # glsl_file 是相对 SHADER_SRC_DIR 的路径 + # 推断 stage:'.vert' -> 'vert' + get_filename_component(stage ${glsl_file} EXT) + string(SUBSTRING ${stage} 1 -1 stage) + set(spv ${SHADER_OUT_DIR}/${glsl_file}.spv) + get_filename_component(spv_dir ${spv} DIRECTORY) # 保留子目录 + add_custom_command( OUTPUT ${spv} - COMMAND ${GLSLC} -fshader-stage=${stage} -o ${spv} ${SHADER_SRC_DIR}/${glsl_file} + COMMAND ${CMAKE_COMMAND} -E make_directory ${spv_dir} + COMMAND ${GLSLC} -fshader-stage=${stage} + -o ${spv} ${SHADER_SRC_DIR}/${glsl_file} DEPENDS ${SHADER_SRC_DIR}/${glsl_file} VERBATIM) set_source_files_properties(${spv} PROPERTIES GENERATED TRUE) @@ -23,5 +29,12 @@ function(auto_compile_shader glsl_file stage) set_property(DIRECTORY APPEND PROPERTY HZ_SHADER_SPV_FILES ${spv}) endfunction() -auto_compile_shader(simple_shader.vert vert) -auto_compile_shader(simple_shader.frag frag) +file(GLOB_RECURSE SHADER_SOURCES + CONFIGURE_DEPENDS # 新增 shader 时自动重配 + RELATIVE ${SHADER_SRC_DIR} + ${SHADER_SRC_DIR}/*.vert + ${SHADER_SRC_DIR}/*.frag) + +foreach (f ${SHADER_SOURCES}) + auto_compile_shader(${f}) +endforeach() \ No newline at end of file diff --git a/App/main.cpp b/App/main.cpp index 848790c..809d38e 100644 --- a/App/main.cpp +++ b/App/main.cpp @@ -1,23 +1,131 @@ // // Created by atdunbg on 2026/9/20. // +// SandboxApp:第一个基于 Application 框架的程序。 +// +// main 的全部职责:定义一个 Application 派生类并运行它。 +// 窗口、渲染器、事件泵、帧计时、场景容器全部由框架管理; +// 这里只写"程序逻辑":创建实体、逐帧更新组件、事件响应。 +// -#include -#include +#include #include +#include +#include +#include +#include -#include "Engine/Core/Stats.h" +#include +#include + +#include + +using namespace Engine; +using namespace Engine::ECS; + +class SandboxApp final : public Application +{ +public: + SandboxApp() + : Application("HelloVulkan", 1280, 720) + { + } + +protected: + // ---- 生命周期 ---- + + void OnInit() override + { + Scene& scene = GetScene(); + + // 模型资产(AssetManager 负责加载/缓存/生命周期,返回句柄) + auto backpack = GetAssets().LoadModel("assets/models/backpack/backpack.obj"); + + // 相机实体(primary = 当前渲染视角) + m_CameraEntity = scene.CreateEntity("MainCamera"); + auto& camera = scene.AddComponent(m_CameraEntity); + camera.primary = true; + camera.camera.SetPosition(glm::vec3(0.0f, 0.0f, 7.0f)); + + // 背包实体 × 3 + for (int i = 0; i < 3; ++i) { + Entity e = scene.CreateEntity("Backpack" + std::to_string(i)); + + auto& transform = scene.AddComponent(e); + transform.world = glm::translate( + glm::mat4(1.0f), + glm::vec3((i - 1) * 1.5f, 0.0f, 0.0f)); + + scene.AddComponent(e, MeshRendererComponent{backpack}); + + m_Spinners.push_back(e); + } + + // 灯光实体 + Entity keyLight = scene.CreateEntity("KeyLight"); + scene.AddComponent(keyLight, + LightComponent{glm::vec3(-0.5f, -1.0f, -0.3f), + glm::vec4(1.0f, 0.95f, 0.9f, 1.0f)}); + + Entity fillLight = scene.CreateEntity("FillLight"); + scene.AddComponent(fillLight, + LightComponent{glm::vec3(0.5f, -0.5f, 0.3f), + glm::vec4(0.3f, 0.5f, 1.0f, 0.8f)}); + } + + void OnUpdate(f32 deltaTime) override + { + Scene& scene = GetScene(); + m_Time += deltaTime; + + // 相机(WASD/Space/Shift 移动) + scene.GetComponent(m_CameraEntity) + .camera.Update(GetWindow(), deltaTime); + + // 旋转动画:直接写实体组件 + for (size_t i = 0; i < m_Spinners.size(); ++i) { + f32 angle = m_Time * glm::radians(60.0f) + static_cast(i) * 0.7f; + + auto& transform = scene.GetComponent(m_Spinners[i]); + glm::mat4 t = glm::translate( + glm::mat4(1.0f), + glm::vec3((static_cast(i) - 1.0f) * 1.5f, 0.0f, 0.0f)); + glm::mat4 r = glm::rotate(glm::mat4(1.0f), angle, glm::vec3(0.0f, 1.0f, 0.0f)); + glm::mat4 s = glm::scale(glm::mat4(1.0f), glm::vec3(0.5)); + + transform.world = t * r * s; + } + } + + // ---- 事件 ---- + + void OnKeyPress(const Event& event) override + { + if (event.key == GLFW_KEY_F1) { + STATS.PrintComparison(); + } + if (event.key == GLFW_KEY_ESCAPE) { + Close(); + } + } + + void OnWindowResize(const Event& event) override + { + ENGINE_INFO("App: window resized to {}x{}", event.width, event.height); + } + +private: + Entity m_CameraEntity{}; + std::vector m_Spinners; + f32 m_Time = 0.0f; +}; int main() { - try { - Engine::Window window("HelloVulkan", 1280, 720); - Engine::Renderer renderer(window); + SandboxApp app; - while (!window.ShouldClose()) { - window.PollEvents(); - renderer.Render(); - } + try { + app.Run(); } catch (const std::exception& e) { ENGINE_FATAL("Exception: {}", e.what()); diff --git a/Engine/include/Engine/Assets/AssetHandle.h b/Engine/include/Engine/Assets/AssetHandle.h new file mode 100644 index 0000000..245346e --- /dev/null +++ b/Engine/include/Engine/Assets/AssetHandle.h @@ -0,0 +1,37 @@ +// +// Created by atdunbg on 2026/9/22. +// +// AssetHandle:资产句柄 = (池内索引, 生成号),与 Entity/RG 句柄同一哲学。 +// +// 组件、场景数据只存句柄;所有权归 AssetManager。 +// 悬空句柄(generation 不匹配)会被 Get 显式拒绝,杜绝 use-after-free。 +// + +#ifndef HELLOVULKAN_ASSETHANDLE_H +#define HELLOVULKAN_ASSETHANDLE_H + +#include + +// Model 前向声明(句柄不依赖完整类型) +namespace Engine { class Model; } + +namespace Engine::Assets { + + inline constexpr u32 kInvalidAssetIndex = 0xFFFFFFFF; + + template + struct AssetHandle { + u32 index = kInvalidAssetIndex; + u32 generation = 0; + + bool Valid() const { return index != kInvalidAssetIndex; } + explicit operator bool() const { return Valid(); } + bool operator==(const AssetHandle& o) const = default; + }; + + // 常用别名 + using ModelHandle = AssetHandle; + +} // namespace Engine::Assets + +#endif //HELLOVULKAN_ASSETHANDLE_H diff --git a/Engine/include/Engine/Assets/AssetManager.h b/Engine/include/Engine/Assets/AssetManager.h new file mode 100644 index 0000000..9a5a579 --- /dev/null +++ b/Engine/include/Engine/Assets/AssetManager.h @@ -0,0 +1,173 @@ +// +// Created by atdunbg on 2026/9/22. +// +// AssetManager:引擎资产层。 +// +// 职责划分(对照 Renderer): +// - 资产层:路径 → 解析/加载 → 缓存去重 → 句柄分发 + 生命周期管理 +// - 渲染层:拿已解析资产创建/绑定 GPU 资源 +// +// Model 的构造仍需要 GPU 上下文(buffer/image upload),这是现阶段的 +// 折中:资产层"使用"GPU 层的接口,但 Renderer 不再知道磁盘和缓存。 +// 后续演进:把 Model 拆成 CPU 解析数据 + GPU 上传两段,实现真正的 +// 异步加载(IO 线程解析 → 主线程/上传队列提交),本文件结构已为此留好位置。 +// +// 组件存句柄不存指针的理由: +// shared_ptr 会引入共享所有权 → 原子引用计数(swap-remove 拷贝时) +// + 不可预测的销毁时机(GPU 可能还在用)+ 热重载被引用计数卡死。 +// 句柄 + 单一所有者(本类)是引擎级标准形态。 +// + +#ifndef HELLOVULKAN_ASSETMANAGER_H +#define HELLOVULKAN_ASSETMANAGER_H + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Engine { + + class Device; + class MemoryAllocator; + class DescriptorManager; + + namespace Assets { + + // ---------- 通用资产缓存(不感知 Vulkan / 磁盘细节由 loader 决定) ---------- + + template + class AssetCache { + public: + // loader:path → 资产实例(失败返回 nullptr) + using LoaderFn = std::function(const std::string&)>; + + // 加载(带路径去重)。失败返回无效句柄。 + AssetHandle Load(const std::string& path, const LoaderFn& loader) + { + if (auto it = m_ByPath.find(path); it != m_ByPath.end()) { + return it->second; + } + + Scope asset = loader(path); + if (!asset) { + ENGINE_ERROR("AssetCache: failed to load '{}'", path); + return {}; + } + + AssetHandle h; + if (!m_Free.empty()) { + h.index = m_Free.back(); + m_Free.pop_back(); + h.generation = m_Entries[h.index].generation; + m_Entries[h.index] = Entry{path, h.generation, std::move(asset), true}; + } else { + h.index = static_cast(m_Entries.size()); + h.generation = 1; + m_Entries.push_back(Entry{path, h.generation, std::move(asset), true}); + } + + m_ByPath.emplace(path, h); + return h; + } + + // 句柄 → 资产指针。悬空句柄 FATAL(bug 信号);已卸载返回 nullptr。 + T* Get(AssetHandle h) const + { + if (!h.Valid() || h.index >= m_Entries.size()) { + ENGINE_FATAL("AssetCache::Get: invalid handle (index={}, gen={})", + h.index, h.generation); + } + const Entry& e = m_Entries[h.index]; + if (e.generation != h.generation) { + ENGINE_FATAL("AssetCache::Get: stale handle for '{}' (gen {} != {})", + e.path, h.generation, e.generation); + } + return e.alive ? e.asset.get() : nullptr; + } + + // 卸载:句柄即刻失效(generation 递增),路径缓存清除。 + // 注意:GPU 侧何时安全释放由上层(Renderer fence)保证—— + // 本类只管所有权,不管 GPU 生命周期。 + void Unload(AssetHandle h) + { + if (!h.Valid() || h.index >= m_Entries.size()) return; + + Entry& e = m_Entries[h.index]; + if (e.generation != h.generation || !e.alive) return; + + m_ByPath.erase(e.path); + e.path.clear(); + e.asset.reset(); + e.alive = false; + ++e.generation; + m_Free.push_back(h.index); + } + + const std::string& Path(AssetHandle h) const + { + static const std::string kEmpty; + if (!h.Valid() || h.index >= m_Entries.size()) return kEmpty; + const Entry& e = m_Entries[h.index]; + return (e.generation == h.generation && e.alive) ? e.path : kEmpty; + } + + u32 Count() const + { + return static_cast(m_Entries.size()) - static_cast(m_Free.size()); + } + + private: + struct Entry { + std::string path; + u32 generation = 1; + Scope asset; + bool alive = false; + }; + + std::vector m_Entries; + std::vector m_Free; + std::unordered_map> m_ByPath; + }; + + // ---------- AssetManager:全引擎资产入口 ---------- + // (ModelHandle 别名来自 AssetHandle.h) + + class AssetManager { + public: + // GPU 上下文由组合根(Application)在 Renderer 装配完成后注入 + AssetManager(Device& device, MemoryAllocator& allocator, + DescriptorManager& descriptors, + vk::DescriptorSetLayout textureLayout); + ~AssetManager() = default; + + AssetManager(const AssetManager&) = delete; + AssetManager& operator=(const AssetManager&) = delete; + + ModelHandle LoadModel(const std::string& path); + Model* GetModel(ModelHandle h) const { return m_Models.Get(h); } + void UnloadModel(ModelHandle h) { m_Models.Unload(h); } + + AssetCache& Models() { return m_Models; } + + // 后续扩展点:TextureHandle / MaterialHandle / ShaderHandle + // 各自一个 AssetCache + 便捷方法,模式与 Model 完全一致。 + + private: + Device& m_Device; + MemoryAllocator& m_Allocator; + DescriptorManager& m_Descriptors; + vk::DescriptorSetLayout m_TextureLayout; + + AssetCache m_Models; + }; + + } // namespace Assets +} // namespace Engine + +#endif //HELLOVULKAN_ASSETMANAGER_H diff --git a/Engine/include/Engine/Core/Application.h b/Engine/include/Engine/Core/Application.h new file mode 100644 index 0000000..8a20927 --- /dev/null +++ b/Engine/include/Engine/Core/Application.h @@ -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 +#include +#include +#include + +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 m_Window; + Scope m_Scene; + Scope m_Renderer; + Scope 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 diff --git a/Engine/include/Engine/Core/Event.h b/Engine/include/Engine/Core/Event.h new file mode 100644 index 0000000..6ed5584 --- /dev/null +++ b/Engine/include/Engine/Core/Event.h @@ -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 + +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 diff --git a/Engine/include/Engine/Core/FrameContext.h b/Engine/include/Engine/Core/FrameContext.h new file mode 100644 index 0000000..ccb32a7 --- /dev/null +++ b/Engine/include/Engine/Core/FrameContext.h @@ -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 +#include + +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 diff --git a/Engine/include/Engine/Core/UUID.h b/Engine/include/Engine/Core/UUID.h new file mode 100644 index 0000000..c75022e --- /dev/null +++ b/Engine/include/Engine/Core/UUID.h @@ -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 + +#include +#include + +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 { + size_t operator()(const Engine::UUID& id) const noexcept { + return std::hash{}(id.Value()); + } + }; +} + +#endif //HELLOVULKAN_UUID_H diff --git a/Engine/include/Engine/Core/Window.h b/Engine/include/Engine/Core/Window.h index 8d3f9ad..337c32f 100644 --- a/Engine/include/Engine/Core/Window.h +++ b/Engine/include/Engine/Core/Window.h @@ -11,6 +11,7 @@ #include #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 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 diff --git a/Engine/include/Engine/ECS/Components.h b/Engine/include/Engine/ECS/Components.h new file mode 100644 index 0000000..5b30cce --- /dev/null +++ b/Engine/include/Engine/ECS/Components.h @@ -0,0 +1,62 @@ +// +// Created by atdunbg on 2026/9/22. +// +// Components:内置组件集合。 +// 组件是纯数据(POD 倾向),逻辑在系统/应用层。 +// + +#ifndef HELLOVULKAN_COMPONENTS_H +#define HELLOVULKAN_COMPONENTS_H + +#include +#include +#include +#include + +#include +#include + +namespace Engine { + class Model; +} + +namespace Engine::ECS { + + // 实体稳定身份:CreateEntity 时自动分配,用于序列化/跨引用。 + // ECS 内部遍历不用它(用 index+generation 句柄,性能)。 + struct IdComponent { + UUID uuid; + }; + + // 实体名(调试/查询用;大规模场景建议演进为哈希 id) + struct NameComponent { + std::string name; + }; + + // 世界变换。当前直接存世界矩阵(渲染热路径友好); + // 需要 SRT 编辑时再加 LocalTransform 组件 + 层级同步系统。 + struct TransformComponent { + glm::mat4 world{1.0f}; + }; + + // 网格渲染体:引用共享的 Model 资产。 + // 存句柄不存指针/Ref:所有权归 AssetManager,悬空由句柄 generation 拦截。 + struct MeshRendererComponent { + Assets::ModelHandle model; + }; + + // 相机。一个场景中 primary=true 的实体是当前渲染视角。 + struct CameraComponent { + Camera camera; + bool primary = false; + }; + + // 方向光 + struct LightComponent { + glm::vec3 direction{0.0f, -1.0f, 0.0f}; + glm::vec4 color{1.0f, 1.0f, 1.0f, 1.0f}; + }; + +} // namespace Engine::ECS + +#endif //HELLOVULKAN_COMPONENTS_H diff --git a/Engine/include/Engine/ECS/Entity.h b/Engine/include/Engine/ECS/Entity.h new file mode 100644 index 0000000..d91c17d --- /dev/null +++ b/Engine/include/Engine/ECS/Entity.h @@ -0,0 +1,32 @@ +// +// Created by atdunbg on 2026/9/22. +// +// Entity:实体句柄 = (池内索引, 生成号)。 +// +// 生成号用于甄别悬空引用:实体销毁后索引会回收复用, +// 旧的 Entity 句柄因 generation 不匹配会被 Registry 拒绝, +// 从机制上杜绝"引用到复用后的错误实体"这类隐式 bug。 +// + +#ifndef HELLOVULKAN_ENTITY_H +#define HELLOVULKAN_ENTITY_H + +#include + +namespace Engine { + + struct Entity { + static constexpr u32 kInvalid = 0xFFFFFFFF; + + u32 index = kInvalid; // 实体槽位索引 + u32 generation = 0; // 槽位复用计数 + + bool Valid() const { return index != kInvalid; } + explicit operator bool() const { return Valid(); } + bool operator==(const Entity& o) const = default; + bool operator!=(const Entity& o) const = default; + }; + +} // namespace Engine + +#endif //HELLOVULKAN_ENTITY_H diff --git a/Engine/include/Engine/ECS/Registry.h b/Engine/include/Engine/ECS/Registry.h new file mode 100644 index 0000000..c33b433 --- /dev/null +++ b/Engine/include/Engine/ECS/Registry.h @@ -0,0 +1,312 @@ +// +// Created by atdunbg on 2026/9/22. +// +// Registry:稀疏集 ECS 核心。 +// +// 数据结构(与 EnTT 同族,自研实现): +// - 每个组件类型一个 ComponentPool:sparse 索引表 + dense 实体/组件数组 +// - Add/Remove/Get 全部 O(1);Remove 用 swap-remove(O(1),代价是 +// dense 顺序会变——渲染遍历只要求"每帧确定",不要求跨帧稳定) +// - Each 以最小的池为主表遍历,其余池做 O(1) Contains 探测, +// 复杂度 O(min(|A|,|B|,...))——大场景下的关键保证 +// +// 约束(引擎级正确性): +// - 悬空句柄:generation 不匹配直接 FATAL,不静默 +// - 遍历中禁止结构变更(Add/Remove 组件、Create/Destroy 实体), +// 会使池失效——销毁走延迟队列(Scene::DestroyEntity → FlushDestroyQueue) +// - 全程无虚调用开销在热路径上(Get/Add 直接走模板池) +// + +#ifndef HELLOVULKAN_REGISTRY_H +#define HELLOVULKAN_REGISTRY_H + +#include +#include +#include + +#include +#include +#include +#include + +namespace Engine::ECS { + + // ---------- 组件类型 ID(编译期静态分配) ---------- + + using ComponentTypeId = u32; + + namespace detail { + inline ComponentTypeId NextComponentTypeId() + { + static ComponentTypeId counter = 0; + return counter++; + } + } + + template + ComponentTypeId ComponentTypeIdOf() + { + static const ComponentTypeId id = detail::NextComponentTypeId(); + return id; + } + + // ---------- 类型擦除池接口(Registry 管理生命周期 + 视图主表遍历用) ---------- + + class IPool { + public: + virtual ~IPool() = default; + virtual u32 Size() const = 0; + virtual Entity EntityAt(u32 denseIndex) const = 0; + virtual bool Remove(Entity e) = 0; // 不存在返回 false + }; + + // ---------- 稀疏集组件池 ---------- + + template + class ComponentPool final : public IPool { + public: + static constexpr u32 kInvalid = 0xFFFFFFFF; + + T& Add(Entity e, T component) + { + if (Contains(e)) { + ENGINE_FATAL("ComponentPool: entity ({},{}) already has this component", + e.index, e.generation); + } + if (e.index >= m_Sparse.size()) { + m_Sparse.resize(e.index + 1, kInvalid); + } + + m_Sparse[e.index] = static_cast(m_Components.size()); + m_Entities.push_back(e); + m_Components.push_back(std::move(component)); + return m_Components.back(); + } + + bool Contains(Entity e) const + { + return e.index < m_Sparse.size() && + m_Sparse[e.index] != kInvalid && + m_Entities[m_Sparse[e.index]] == e; + } + + T& Get(Entity e) + { + if (!Contains(e)) { + ENGINE_FATAL("ComponentPool: entity ({},{}) does not have this component", + e.index, e.generation); + } + return m_Components[m_Sparse[e.index]]; + } + + const T& Get(Entity e) const + { + return const_cast(this)->Get(e); + } + + bool Remove(Entity e) override + { + if (!Contains(e)) return false; + + const u32 denseIdx = m_Sparse[e.index]; + const u32 lastIdx = static_cast(m_Components.size()) - 1; + + if (denseIdx != lastIdx) { + // swap-remove:尾元素补位 + m_Entities[denseIdx] = m_Entities[lastIdx]; + m_Components[denseIdx] = std::move(m_Components[lastIdx]); + m_Sparse[m_Entities[denseIdx].index] = denseIdx; + } + + m_Sparse[e.index] = kInvalid; + m_Entities.pop_back(); + m_Components.pop_back(); + return true; + } + + u32 Size() const override { return static_cast(m_Components.size()); } + Entity EntityAt(u32 denseIndex) const override { return m_Entities[denseIndex]; } + + private: + std::vector m_Sparse; // 实体索引 -> dense 下标(kInvalid = 无) + std::vector m_Entities; // dense:与 m_Components 平行 + std::vector m_Components; // dense:组件数据(连续内存,缓存友好) + }; + + // ---------- Registry ---------- + + class Registry { + public: + Registry() = default; + ~Registry() = default; + + Registry(const Registry&) = delete; + Registry& operator=(const Registry&) = delete; + + // ---- 实体生命周期 ---- + + Entity Create() + { + if (!m_FreeIndices.empty()) { + const u32 index = m_FreeIndices.back(); + m_FreeIndices.pop_back(); + m_Records[index].alive = true; // generation 已在 Destroy 时递增 + return Entity{index, m_Records[index].generation}; + } + + m_Records.push_back({0, true}); + return Entity{static_cast(m_Records.size() - 1), 0}; + } + + void Destroy(Entity e) + { + if (!IsAlive(e)) { + ENGINE_FATAL("Registry::Destroy: stale entity ({},{})", + e.index, e.generation); + } + + for (auto& pool : m_Pools) { + if (pool) pool->Remove(e); // 不在池中就跳过 + } + + auto& record = m_Records[e.index]; + record.alive = false; + ++record.generation; + m_FreeIndices.push_back(e.index); + } + + bool IsAlive(Entity e) const + { + return e.Valid() && e.index < m_Records.size() && + m_Records[e.index].alive && + m_Records[e.index].generation == e.generation; + } + + // ---- 组件操作 ---- + + template + T& Add(Entity e, Args&&... args) + { + if (!IsAlive(e)) { + ENGINE_FATAL("Registry::Add: stale entity ({},{})", e.index, e.generation); + } + return AssurePool().Add(e, T{std::forward(args)...}); + } + + template + T& Get(Entity e) + { + if (!IsAlive(e)) { + ENGINE_FATAL("Registry::Get: stale entity ({},{})", e.index, e.generation); + } + auto* pool = GetPool(); + if (!pool) { + ENGINE_FATAL("Registry::Get: no component pool of this type exists"); + } + return pool->Get(e); + } + + template + T* TryGet(Entity e) + { + if (!IsAlive(e)) return nullptr; + auto* pool = GetPool(); + return (pool && pool->Contains(e)) ? &pool->Get(e) : nullptr; + } + + template + bool Has(Entity e) + { + if (!IsAlive(e)) return false; + auto* pool = GetPool(); + return pool && pool->Contains(e); + } + + template + bool Remove(Entity e) + { + if (!IsAlive(e)) return false; + auto* pool = GetPool(); + return pool && pool->Remove(e); + } + + // ---- 遍历(视图)---- + // fn(Entity, Ts&...);以最小的池为主表,复杂度 O(min pool size)。 + // 遍历中禁止结构变更(见文件头约束)。 + template + void Each(Fn&& fn) + { + static_assert(sizeof...(Ts) > 0, "Each requires at least one component type"); + + std::tuple*...> pools{GetPool()...}; + + // 任一类型池不存在 → 空集 + bool allExist = true; + std::apply([&allExist](auto*... p) { + ((p || (allExist = false)) && ...); + }, pools); + if (!allExist) return; + + // 主池:取最小者 + const std::array basePools{GetPool()...}; + IPool* primary = nullptr; + u32 minSize = 0xFFFFFFFF; + for (auto* p : basePools) { + if (p && p->Size() < minSize) { + minSize = p->Size(); + primary = p; + } + } + if (!primary) return; + + for (u32 i = 0; i < primary->Size(); ++i) { + const Entity e = primary->EntityAt(i); + if (!IsAlive(e)) continue; + + bool all = true; + std::apply([&](auto*... p) { + all = (p->Contains(e) && ...); // 短路折叠 + }, pools); + if (!all) continue; + + std::apply([&](auto*... p) { + fn(e, p->Get(e)...); + }, pools); + } + } + + private: + template + ComponentPool* GetPool() + { + const ComponentTypeId id = ComponentTypeIdOf(); + if (id >= m_Pools.size() || !m_Pools[id]) return nullptr; + return static_cast*>(m_Pools[id].get()); + } + + template + ComponentPool& AssurePool() + { + const ComponentTypeId id = ComponentTypeIdOf(); + if (id >= m_Pools.size()) { + m_Pools.resize(id + 1); + } + if (!m_Pools[id]) { + m_Pools[id] = std::make_unique>(); + } + return static_cast&>(*m_Pools[id]); + } + + struct EntityRecord { + u32 generation = 0; + bool alive = false; + }; + + std::vector m_Records; + std::vector m_FreeIndices; // 可复用槽位 + std::vector> m_Pools; // 按 ComponentTypeId 索引 + }; + +} // namespace Engine::ECS + +#endif //HELLOVULKAN_REGISTRY_H diff --git a/Engine/include/Engine/ECS/Scene.h b/Engine/include/Engine/ECS/Scene.h new file mode 100644 index 0000000..04fba45 --- /dev/null +++ b/Engine/include/Engine/ECS/Scene.h @@ -0,0 +1,84 @@ +// +// Created by atdunbg on 2026/9/22. +// +// Scene:世界容器 = Registry + 场景级设置 + 安全的生命周期封装。 +// +// 销毁策略:DestroyEntity 只入队,FlushDestroyQueue 在帧尾统一执行, +// 保证遍历(Each)过程中组件池结构不变——这是 ECS 正确性的关键约束。 +// + +#ifndef HELLOVULKAN_SCENE_H +#define HELLOVULKAN_SCENE_H + +#include +#include +#include + +#include +#include + +namespace Engine::ECS { + + class Scene { + public: + Scene() = default; + ~Scene() = default; + + Scene(const Scene&) = delete; + Scene& operator=(const Scene&) = delete; + + // ---- 场景级设置 ---- + struct Settings { + glm::vec4 ambientColor{0.15f, 0.15f, 0.2f, 0.0f}; + }; + Settings& GetSettings() { return m_Settings; } + + // ---- 实体 ---- + + Entity CreateEntity(std::string name = {}); + void DestroyEntity(Entity e); // 延迟:帧尾统一销毁 + void FlushDestroyQueue(); // 每帧末尾由 Application 调用 + bool IsAlive(Entity e) const { return m_Registry.IsAlive(e); } + + // 按稳定 UUID 查实体(序列化/跨引用场景);找不到返回无效句柄 + Entity FindEntityByUUID(UUID uuid); + + Registry& GetRegistry() { return m_Registry; } + + // ---- 组件便捷接口(转发 Registry)---- + + template + T& AddComponent(Entity e, Args&&... args) + { + return m_Registry.Add(e, std::forward(args)...); + } + + template + T& GetComponent(Entity e) { return m_Registry.Get(e); } + + template + T* TryGetComponent(Entity e) { return m_Registry.TryGet(e); } + + template + bool HasComponent(Entity e) { return m_Registry.Has(e); } + + template + void RemoveComponent(Entity e) { m_Registry.Remove(e); } + + template + void Each(Fn&& fn) { m_Registry.Each(std::forward(fn)); } + + // ---- 常用查询 ---- + + // primary 相机(渲染视角来源);无则返回 nullptr + CameraComponent* GetPrimaryCamera(); + + private: + Registry m_Registry; + std::vector m_PendingDestroy; + Settings m_Settings; + }; + +} // namespace Engine::ECS + +#endif //HELLOVULKAN_SCENE_H diff --git a/Engine/include/Engine/RenderGraph/RenderGraph.h b/Engine/include/Engine/RenderGraph/RenderGraph.h new file mode 100644 index 0000000..51d9ae0 --- /dev/null +++ b/Engine/include/Engine/RenderGraph/RenderGraph.h @@ -0,0 +1,299 @@ +// +// Created by atdunbg on 2026/9/22. +// +// RenderGraph:声明式帧图内核。 +// +// 设计原则(面向引擎,非 demo): +// 1. 声明与执行分离:图结构(pass + 虚拟资源)只声明一次并 Compile, +// 每帧 Execute 零图结构开销、零堆分配(barrier 列表全部编译期预算)。 +// 2. 虚拟资源 + 生成号句柄:上层永远不持有裸 vulkan 句柄, +// 生命周期、layout、物理内存全部由图管理。 +// 3. 物理资源池:真实 GpuImage 按 (format, extent, usage) 池化复用, +// 每个虚拟资源按帧槽位拥有 kMaxFramesInFlight 个物理实例, +// 从根上避免帧间读写竞争,同时杜绝每帧重建 GPU 资源。 +// 4. 自动同步:layout/访问状态链在 Compile 期解算,产出批量 +// vk::ImageMemoryBarrier2(Synchronization2),上层不再手写 barrier。 +// +// 已知边界(后续演进点): +// - 暂不支持异步 compute queue / 跨队列信号量 +// - 暂不支持内存别名(需 VMA)与帧历史资源(TAA 等) +// - 同一 pass 内对同一资源"读+写"(input attachment / RWM)未建模 +// + +#ifndef HELLOVULKAN_RENDERGRAPH_H +#define HELLOVULKAN_RENDERGRAPH_H + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Engine::RG { + + // ---------- 句柄 ---------- + // 带生成号的索引句柄:图永远不会删除条目(只追加), + // 生成号用于上层误持过期句柄时的校验,防止 use-after-free 式错误。 + + inline constexpr u32 kInvalidHandle = 0xFFFFFFFF; + + template + struct Handle { + u32 index = kInvalidHandle; + u32 generation = 0; + + bool Valid() const { return index != kInvalidHandle; } + explicit operator bool() const { return Valid(); } + }; + + struct TextureTag; + using TextureHandle = Handle; + + // ---------- 资源使用方式 ---------- + // 声明 pass 怎么用资源;layout/access/stage 映射和 image usage + // 都由这里推导,上层不需要(也不允许)手写。 + + enum class ResourceUse : u8 { + ColorAttachment, + DepthAttachment, + ShaderRead, + TransferSrc, + TransferDst, + Present, // 仅用于 swapchain 导入资源:transition 到 ePresentSrcKHR + }; + + // ---------- 虚拟纹理描述 ---------- + + struct TextureDesc { + vk::Format format = vk::Format::eUndefined; + u32 width = 0; // 0 = 跟随目标(swapchain)尺寸 + u32 height = 0; + }; + + // ---------- 物理镜像 ---------- + + struct PhysicalImage { + vk::Image image{}; + vk::ImageView view{}; + vk::Extent2D extent{}; + vk::Format format{}; + vk::ImageUsageFlags usage{}; + MemoryAllocator::Allocation allocation{}; + bool valid = false; + }; + + // ---------- Pass 上下文(Execute 回调收到的) ---------- + + struct RenderGraphContext { + vk::CommandBuffer cmd; + FrameContext frame; + vk::Extent2D extent{}; + + // 按句柄取当前帧槽位的物理资源(image/view) + vk::ImageView View(TextureHandle h) const; + vk::Image Image(TextureHandle h) const; + + class RenderGraph* graph = nullptr; + }; + + using ExecuteFn = std::function; + + // ---------- 图内部结构 ---------- + + struct TextureEntry { + std::string name; + u32 index = 0; // 自身在 m_Textures 中的下标 + TextureDesc desc; + bool imported = false; // swapchain 导入资源无物理实例 + u32 generation = 1; + + // Compile 期推导 + vk::ImageUsageFlags usage{}; + vk::ImageAspectFlagBits aspect = vk::ImageAspectFlagBits::eColor; + i32 firstPass = -1; + i32 lastPass = -1; + + // 物理实例:每个帧槽位一个,created 资源才拥有 + std::vector physical; + + // 导入资源每帧绑定 + vk::Image importedImage{}; + vk::ImageView importedView{}; + + // 帧槽位状态:物理镜像是否已被使用过(决定 entry barrier 变体) + struct SlotState { + bool used = false; + }; + std::vector slots; + + // Compile 期状态链解算用(执行序上的"当前"状态) + vk::ImageLayout chainLayout = vk::ImageLayout::eUndefined; + vk::PipelineStageFlags2 chainStage{}; + vk::AccessFlags2 chainAccess{}; + bool chainWasWrite = false; + + // 稳态:上一轮执行结束时(lastPass 之后)的状态。 + // 同一帧槽位再次进入图时,entry barrier 从这里出发。 + vk::ImageLayout restingLayout = vk::ImageLayout::eUndefined; + vk::PipelineStageFlags2 restingStage{}; + vk::AccessFlags2 restingAccess{}; + }; + + struct PassNode { + std::string name; + struct Use { + u32 resIndex; + ResourceUse use; + vk::ShaderStageFlags readStage; // ShaderRead 时的消费 stage + }; + std::vector uses; + ExecuteFn exec; + bool sideEffect = false; // 无读写但必须执行(compute 写 buffer 等) + bool culled = false; + + // Compile 期预算好的 barrier:每个资源两个变体 + // steady = 该槽位已被使用过(从 resting 状态出发) + // first = 该槽位首次使用(从 Undefined 出发) + // imported 资源恒用 first(acquire 后 layout 语义上为 Undefined) + struct BarrierPair { + u32 resIndex; + vk::ImageMemoryBarrier2 steady; + vk::ImageMemoryBarrier2 first; + }; + std::vector barriers; + }; + + // ---------- RenderGraph ---------- + + class RenderGraph { + public: + RenderGraph(Device& device, MemoryAllocator& allocator, u32 framesInFlight); + ~RenderGraph(); + + RenderGraph(const RenderGraph&) = delete; + RenderGraph& operator=(const RenderGraph&) = delete; + + // ---- 声明阶段(AddPass 之后不得再声明资源;全部声明完成后 Compile)---- + + // 声明一张图拥有的瞬时纹理(此时不分配任何 GPU 内存) + TextureHandle CreateTexture(std::string name, const TextureDesc& desc); + + // 导入 swapchain 颜色附件(每帧通过 SetImportedImage 绑定物理 image) + TextureHandle ImportSwapchain(std::string name, vk::Format format); + + // 每帧 acquire 后调用,把当帧 swapchain image 绑到导入资源 + void SetImportedImage(TextureHandle h, vk::Image image, vk::ImageView view); + + // ---- Pass 声明 ---- + + class PassBuilder { + public: + PassBuilder& Write(TextureHandle h, ResourceUse use); + PassBuilder& Read(TextureHandle h, ResourceUse use, + vk::ShaderStageFlags stage = vk::ShaderStageFlagBits::eFragment); + PassBuilder& SideEffect(); + + private: + friend class RenderGraph; + PassBuilder(RenderGraph& graph, u32 passIndex) + : m_Graph(graph), m_PassIndex(passIndex) {} + + RenderGraph& m_Graph; + u32 m_PassIndex; + }; + + [[nodiscard]] PassBuilder AddPass(std::string name, ExecuteFn exec); + + // ---- 编译与执行 ---- + + // 编译:死 pass 剔除 → 拓扑排序 → 生命周期 → barrier 预算 → 物理分配。 + // 目标尺寸变化时调用 SetTargetExtent 会自动重编译并轮换物理资源。 + void Compile(); + + void SetTargetExtent(vk::Extent2D extent); + vk::Extent2D GetTargetExtent() const { return m_TargetExtent; } + + // 每帧执行:按编译好的顺序录命令。要求 cmd 处于 recording 状态。 + void Execute(const FrameContext& frame); + + // ---- 统计 ---- + + u32 GetPassCount() const; + u32 GetActivePassCount() const; + u32 GetTextureCount() const; + u32 GetBarrierCount() const; + u32 GetPooledImageCount() const; + + private: + friend struct RenderGraphContext; + + // 编译子步骤 + void ResolveUsage(TextureEntry& entry); + void CullPasses(); + void Toposort(); + void ComputeLifetimes(); + void PrecomputeBarriers(); + void AllocatePhysical(); + void ReleasePhysical(); + void PurgePool(vk::Extent2D target); + PhysicalImage* AcquirePhysical(vk::Format format, vk::Extent2D extent, + vk::ImageUsageFlags usage, + vk::ImageAspectFlagBits aspect); + + TextureEntry& GetEntry(TextureHandle h); + const TextureEntry& GetEntry(TextureHandle h) const; + void CheckHandle(TextureHandle h) const; + void CheckMutable() const; // 声明期校验:Compile 后禁止再改图 + + static vk::ImageLayout UseLayout(ResourceUse use); + static vk::PipelineStageFlags2 UseDstStage(const PassNode::Use& use); + static vk::PipelineStageFlags2 UseSrcStage(ResourceUse use); + static vk::AccessFlags2 UseDstAccess(const PassNode::Use& use); + static vk::AccessFlags2 UseSrcAccess(ResourceUse use); + + Device& m_Device; + MemoryAllocator& m_Allocator; + u32 m_FramesInFlight; + bool m_Compiled = false; + + std::vector m_Textures; + std::vector m_Passes; + u32 m_ActivePassCount = 0; + + vk::Extent2D m_TargetExtent{}; + + // 拓扑排序后的执行序(存 pass 下标) + std::vector m_ExecutionOrder; + + // 物理资源池:key = (format, extent, usage),value = 空闲镜像列表 + struct PoolKey { + u32 format; + u32 width; + u32 height; + u32 usage; + bool operator==(const PoolKey& o) const { + return format == o.format && width == o.width && + height == o.height && usage == o.usage; + } + }; + struct PoolKeyHash { + size_t operator()(const PoolKey& k) const { + size_t h = std::hash{}( + (static_cast(k.format) << 32) ^ k.usage); + h ^= std::hash{}((static_cast(k.width) << 32) ^ k.height) << 1; + return h; + } + }; + std::unordered_map>, PoolKeyHash> m_Pool; + + // Execute 用的 barrier 暂存(成员变量,避免每帧堆分配) + std::vector m_ScratchBarriers; + }; + +} // namespace Engine::RG + +#endif //HELLOVULKAN_RENDERGRAPH_H diff --git a/Engine/include/Engine/Renderer/Camera.h b/Engine/include/Engine/Renderer/Camera.h new file mode 100644 index 0000000..db096c8 --- /dev/null +++ b/Engine/include/Engine/Renderer/Camera.h @@ -0,0 +1,66 @@ +// +// Created by atdunbg on 2026/9/22. +// +// Camera:独立的相机模块。 +// 职责:输入驱动的运动更新 + 视图/投影矩阵计算。 +// 不再依赖具体渲染器——Renderer/RenderGraph 只消费它的矩阵输出。 +// + +#ifndef HELLOVULKAN_CAMERA_H +#define HELLOVULKAN_CAMERA_H + +#include +#include + +namespace Engine { + + class Window; + + class Camera { + public: + // 每帧调用:读取窗口输入,更新位置与朝向 + void Update(Window& window, f32 deltaTime); + + glm::mat4 GetView() const; + + // Vulkan 投影:内部完成 Y 翻转(clip space Y 向下)和 [0,1] 深度 + glm::mat4 GetProjection(f32 aspect, f32 nearZ, f32 farZ) const; + + glm::vec3 GetPosition() const { return m_Position; } + void SetPosition(const glm::vec3& pos) { m_Position = pos; } + + void SetYaw(f32 yaw) { m_Yaw = yaw; } + void SetPitch(f32 pitch) { m_Pitch = pitch; } + f32 GetYaw() const { return m_Yaw; } + f32 GetPitch() const { return m_Pitch; } + + void SetMoveSpeed(f32 speed) { m_MoveSpeed = speed; } + void SetMouseSensitivity(f32 sens) { m_MouseSensitivity = sens; } + + // 鼠标视角默认关闭(需要先做 cursor capture 再打开) + void SetMouseLookEnabled(bool enabled) { m_MouseLookEnabled = enabled; } + bool IsMouseLookEnabled() const { return m_MouseLookEnabled; } + + glm::vec3 GetForward() const { return m_Forward; } + glm::vec3 GetRight() const { return m_Right; } + glm::vec3 GetUp() const { return m_Up; } + + private: + void UpdateBasis(); + + glm::vec3 m_Position{0.0f, 0.0f, 7.0f}; + f32 m_Yaw = -90.0f; // 朝向 -Z + f32 m_Pitch = 0.0f; + f32 m_MoveSpeed = 2.5f; + f32 m_MouseSensitivity = 0.1f; + bool m_MouseLookEnabled = false; + + // 缓存的方向基向量,Update 时刷新,避免每帧三角函数重复计算 + glm::vec3 m_Forward{0.0f, 0.0f, -1.0f}; + glm::vec3 m_Right{1.0f, 0.0f, 0.0f}; + glm::vec3 m_Up{0.0f, 1.0f, 0.0f}; + }; + +} // namespace Engine + +#endif //HELLOVULKAN_CAMERA_H diff --git a/Engine/include/Engine/Renderer/Renderer.h b/Engine/include/Engine/Renderer/Renderer.h index 9172f21..305e29a 100644 --- a/Engine/include/Engine/Renderer/Renderer.h +++ b/Engine/include/Engine/Renderer/Renderer.h @@ -8,14 +8,13 @@ #pragma once #include +#include +#include #include -#include +#include -#include "Engine/Vulkan/Texture.h" #include -#include "Model.h" - namespace Engine { class Window; @@ -24,16 +23,20 @@ namespace Engine { class DescriptorManager; class ShaderManager; class Swapchain; - class CommandManager; class SyncManager; - class Buffer; + class CommandManager; class UniformBuffer; + class Model; - struct Object { - glm::mat4 transform = glm::mat4(1.0f); - Model* model = nullptr; - }; + namespace ECS { class Scene; } + namespace Assets { class AssetManager; } + // Renderer 职责(收敛后): + // 1. 装配底层模块(Device/Swapchain/Sync/Command/Descriptor/Shader) + // 2. 声明并驱动 RenderGraph(pass 结构在这里组装) + // 3. Render(Scene&, AssetManager&):从 ECS 场景读取相机/灯光/渲染体, + // 经资产层解析句柄后绘制 + // 不拥有场景,不加载资产(资产归 AssetManager,磁盘 IO 与这里无关)。 class Renderer { public: Renderer(Window& window); @@ -42,15 +45,21 @@ namespace Engine { Renderer(const Renderer&) = delete; Renderer& operator=(const Renderer&) = delete; - void Render(); + void Render(ECS::Scene& scene, Assets::AssetManager& assets); + + // ---- 组合根(Application)装配其他模块时需要的访问器 ---- + Device& GetDevice() const { return *m_Device; } + MemoryAllocator& GetAllocator() const { return *m_Allocator; } + DescriptorManager& GetDescriptorManager() const { return *m_DescriptorManager; } + vk::DescriptorSetLayout GetTextureSetLayout() const { return m_TextureSetLayout; } private: - void RecordCommands(vk::CommandBuffer cmd, u32 imageIndex); - void TransitionImageLayout(vk::CommandBuffer cmd, vk::Image image, - vk::ImageLayout oldLayout, vk::ImageLayout newLayout); + void BuildGraph(); // 声明 pass 结构 + 首次 Compile + void HandleResize(); Window& m_Window; + // ---- 底层模块(析构序:后声明者先析构,RG 在 Device/Allocator 之前)---- Scope m_Device; Scope m_Allocator; Scope m_DescriptorManager; @@ -58,38 +67,27 @@ namespace Engine { Scope m_Swapchain; Scope m_SyncManager; Scope m_CommandManager; - - Scope m_Model; - std::vector m_Objects; Scope m_UniformBuffer; + Scope m_RenderGraph; + // ---- 管线装配(shader object 方案,无 vk::Pipeline)---- vk::PipelineLayout m_PipelineLayout; - vk::DescriptorSetLayout m_UBOSetLayout; - vk::DescriptorSet m_UBODescriptorSet; vk::DescriptorSetLayout m_TextureSetLayout; + std::vector m_UBODescriptorSets; // 每帧槽位一个 + vk::ShaderEXT m_VertShader{}; + vk::ShaderEXT m_FragShader{}; + // ---- RenderGraph 资源句柄 ---- + RG::TextureHandle m_Backbuffer{}; + RG::TextureHandle m_Depth{}; - vk::ShaderEXT m_VertShader; - vk::ShaderEXT m_FragShader; + // ---- 当帧绑定(Render() 期间有效,供 pass 回调读取)---- + ECS::Scene* m_BoundScene = nullptr; + Assets::AssetManager* m_BoundAssets = nullptr; - vk::Extent2D m_Extent; - vk::Format m_Format; - - static constexpr u32 kMaxFramesInFlight = 2; + vk::Extent2D m_Extent{}; u32 m_CurrentFrame = 0; - - - // 相机 - glm::vec3 m_CameraPos = glm::vec3(0.0f, 0.0f, 7.0f); - f32 m_CameraYaw = -90.0f; // 朝向 -Z - f32 m_CameraPitch = 0.0f; - f32 m_CameraSpeed = 2.5f; - f32 m_MouseSensitivity = 0.1f; - f32 m_DeltaTime = 0.016f; - f32 m_LastFrameTime = 0.0f; - - std::chrono::steady_clock::time_point m_StartTime; }; } // namespace Engine diff --git a/Engine/include/Engine/Vulkan/CommandManager.h b/Engine/include/Engine/Vulkan/CommandManager.h index 794bec1..559bb7a 100644 --- a/Engine/include/Engine/Vulkan/CommandManager.h +++ b/Engine/include/Engine/Vulkan/CommandManager.h @@ -5,32 +5,40 @@ #ifndef HELLOVULKAN_COMMANDMANAGER_H #define HELLOVULKAN_COMMANDMANAGER_H +#include +#include #include +#include 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 m_Buffers; }; } // namespace Engine diff --git a/Engine/include/Engine/Vulkan/Device.h b/Engine/include/Engine/Vulkan/Device.h index cd2c3cc..be40a61 100644 --- a/Engine/include/Engine/Vulkan/Device.h +++ b/Engine/include/Engine/Vulkan/Device.h @@ -34,6 +34,11 @@ public: uint32_t FindMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags props) const; + // 在候选列表中找到第一个满足 tiling+features 的格式,找不到则 FATAL + vk::Format FindSupportedFormat(const std::vector& candidates, + vk::ImageTiling tiling, + vk::FormatFeatureFlags features) const; + // 1.4 特性查询 bool IsDynamicRenderingSupported() const { return m_DynamicRenderingSupported; } bool IsShaderObjectSupported() const { return m_ShaderObjectSupported; } diff --git a/Engine/include/Engine/Vulkan/Swapchain.h b/Engine/include/Engine/Vulkan/Swapchain.h index 87b7461..d468f78 100644 --- a/Engine/include/Engine/Vulkan/Swapchain.h +++ b/Engine/include/Engine/Vulkan/Swapchain.h @@ -33,10 +33,8 @@ namespace Engine const std::vector& GetImages() const { return m_Images; } const std::vector& 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& 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 m_Images; std::vector 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; diff --git a/Engine/include/Engine/Vulkan/UniformBuffer.h b/Engine/include/Engine/Vulkan/UniformBuffer.h index bccbf8b..45034d9 100644 --- a/Engine/include/Engine/Vulkan/UniformBuffer.h +++ b/Engine/include/Engine/Vulkan/UniformBuffer.h @@ -5,33 +5,42 @@ #ifndef HELLOVULKAN_UNIFORMBUFFER_H #define HELLOVULKAN_UNIFORMBUFFER_H - #include +#include #include #include +#include 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 m_Buffer; - u64 m_Size; + std::vector> m_Buffers; + u64 m_Size = 0; }; } // namespace Engine diff --git a/Engine/src/Assets/AssetManager.cpp b/Engine/src/Assets/AssetManager.cpp new file mode 100644 index 0000000..d0de0d2 --- /dev/null +++ b/Engine/src/Assets/AssetManager.cpp @@ -0,0 +1,31 @@ +// +// Created by atdunbg on 2026/9/22. +// + +#include +#include +#include +#include +#include + +namespace Engine::Assets { + + AssetManager::AssetManager(Device& device, MemoryAllocator& allocator, + DescriptorManager& descriptors, + vk::DescriptorSetLayout textureLayout) + : m_Device(device), m_Allocator(allocator), + m_Descriptors(descriptors), m_TextureLayout(textureLayout) + { + ENGINE_INFO("AssetManager created"); + } + + ModelHandle AssetManager::LoadModel(const std::string& path) + { + return m_Models.Load(path, [this](const std::string& p) -> Scope { + // GPU 上传在此发生:资产层调用渲染层接口完成 buffer/image 创建 + return CreateScope( + m_Device, m_Allocator, m_Descriptors, m_TextureLayout, p); + }); + } + +} // namespace Engine::Assets diff --git a/Engine/src/Core/Application.cpp b/Engine/src/Core/Application.cpp new file mode 100644 index 0000000..b45b159 --- /dev/null +++ b/Engine/src/Core/Application.cpp @@ -0,0 +1,115 @@ +// +// Created by atdunbg on 2026/9/22. +// + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Engine { + + Application::Application(std::string title, u32 width, u32 height) + { + m_Window = CreateScope(std::move(title), width, height); + + // 事件通道:GLFW 回调 → 本类 DispatchEvent → 派生类钩子 + m_Window->SetEventCallback( + [this](const Event& e) { DispatchEvent(e); }); + + // 场景(ECS 世界)+ 渲染器 + m_Scene = CreateScope(); + m_Renderer = CreateScope(*m_Window); + + // 资产层:复用渲染器的 GPU 上下文; + // 声明在 Renderer 之后 → 析构先于 Renderer(它引用其子模块) + m_Assets = CreateScope( + m_Renderer->GetDevice(), + m_Renderer->GetAllocator(), + m_Renderer->GetDescriptorManager(), + m_Renderer->GetTextureSetLayout()); + } + + Application::~Application() + { + // 关键:必须在成员析构之前等 GPU 空闲。 + // + // 成员析构序(后声明先析构)是 m_Assets → m_Renderer → m_Scene → m_Window: + // 资产层持有全部 GPU buffer/texture/sampler,会先于 Renderer 析构, + // 而 Renderer 的 waitIdle 在它自己的析构里——那时资产已经销毁了。 + // 若最后一帧命令缓冲仍在 GPU 上执行,就会出现 + // VUID-vkDestroyBuffer-buffer-00922(资源销毁时仍在使用)。 + // 这里显式等待,保证成员按任意顺序析构都是安全的。 + if (m_Renderer) { + m_Renderer->GetDevice().GetDevice().waitIdle(); + } + } + + void Application::Run() + { + ENGINE_INFO("Application starting"); + + m_Running = true; + m_LastFrameTime = std::chrono::steady_clock::now(); + + OnInit(); + + while (m_Running && !m_Window->ShouldClose()) { + m_Window->PollEvents(); + + auto now = std::chrono::steady_clock::now(); + f32 dt = std::chrono::duration(now - m_LastFrameTime).count(); + // 钳制卡顿(断点/窗口拖动导致的超大 dt),避免物理/动画穿模 + m_DeltaTime = std::clamp(dt, 0.0f, 0.25f); + m_LastFrameTime = now; + + OnUpdate(m_DeltaTime); + + // 延迟销毁在渲染前统一 flush,保证渲染遍历时池结构不变 + m_Scene->FlushDestroyQueue(); + + OnRender(); + + ++m_FrameCount; + } + + OnShutdown(); + ENGINE_INFO("Application stopped ({} frames)", m_FrameCount); + } + + void Application::OnRender() + { + m_Renderer->Render(*m_Scene, *m_Assets); + } + + void Application::DispatchEvent(const Event& event) + { + OnEvent(event); + + switch (event.type) { + case EventType::WindowClose: OnWindowClose(event); break; + case EventType::WindowResize: OnWindowResize(event); break; + case EventType::WindowFocus: OnWindowFocus(event); break; + case EventType::WindowLostFocus: OnWindowLostFocus(event); break; + case EventType::KeyPress: OnKeyPress(event); break; + case EventType::KeyRelease: OnKeyRelease(event); break; + case EventType::MouseButtonPress: OnMouseButtonPress(event); break; + case EventType::MouseButtonRelease: OnMouseButtonRelease(event); break; + case EventType::MouseMove: OnMouseMove(event); break; + case EventType::MouseScroll: OnMouseScroll(event); break; + default: break; + } + } + + void Application::OnWindowClose(const Event& event) + { + // 默认行为:直接退出。需要"退出确认"的程序覆写本钩子。 + Close(); + } + +} // namespace Engine diff --git a/Engine/src/Core/Window.cpp b/Engine/src/Core/Window.cpp index 9a19bab..e805e44 100644 --- a/Engine/src/Core/Window.cpp +++ b/Engine/src/Core/Window.cpp @@ -14,6 +14,73 @@ namespace Engine { auto* self = static_cast(glfwGetWindowUserPointer(window)); self->m_FramebufferResized = true; + + if (self->m_EventCallback) { + Event e; + e.type = EventType::WindowResize; + e.width = width; + e.height = height; + self->m_EventCallback(e); + } + } + + void Window::KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) + { + auto* self = static_cast(glfwGetWindowUserPointer(window)); + if (!self->m_EventCallback || key == GLFW_KEY_UNKNOWN) return; + + Event e; + e.key = key; + e.type = (action == GLFW_PRESS) ? EventType::KeyPress + : (action == GLFW_RELEASE) ? EventType::KeyRelease + : EventType::None; // GLFW_REPEAT 暂不透出 + if (e.type != EventType::None) self->m_EventCallback(e); + } + + void Window::MouseButtonCallback(GLFWwindow* window, int button, int action, int mods) + { + auto* self = static_cast(glfwGetWindowUserPointer(window)); + if (!self->m_EventCallback) return; + + Event e; + e.button = button; + e.type = (action == GLFW_PRESS) ? EventType::MouseButtonPress + : EventType::MouseButtonRelease; + self->m_EventCallback(e); + } + + void Window::CursorPosCallback(GLFWwindow* window, double xpos, double ypos) + { + auto* self = static_cast(glfwGetWindowUserPointer(window)); + if (!self->m_EventCallback) return; + + Event e; + e.type = EventType::MouseMove; + e.x = static_cast(xpos); + e.y = static_cast(ypos); + self->m_EventCallback(e); + } + + void Window::ScrollCallback(GLFWwindow* window, double xoffset, double yoffset) + { + auto* self = static_cast(glfwGetWindowUserPointer(window)); + if (!self->m_EventCallback) return; + + Event e; + e.type = EventType::MouseScroll; + e.deltaX = static_cast(xoffset); + e.deltaY = static_cast(yoffset); + self->m_EventCallback(e); + } + + void Window::WindowFocusCallback(GLFWwindow* window, int focused) + { + auto* self = static_cast(glfwGetWindowUserPointer(window)); + if (!self->m_EventCallback) return; + + Event e; + e.type = focused ? EventType::WindowFocus : EventType::WindowLostFocus; + self->m_EventCallback(e); } Window::Window(const std::string& title, u32 width, u32 height) @@ -42,6 +109,11 @@ namespace Engine glfwSetWindowUserPointer(m_Window, this); glfwSetFramebufferSizeCallback(m_Window, FramebufferResizeCallback); + glfwSetKeyCallback(m_Window, KeyCallback); + glfwSetMouseButtonCallback(m_Window, MouseButtonCallback); + glfwSetCursorPosCallback(m_Window, CursorPosCallback); + glfwSetScrollCallback(m_Window, ScrollCallback); + glfwSetWindowFocusCallback(m_Window, WindowFocusCallback); } Window::~Window() diff --git a/Engine/src/ECS/Scene.cpp b/Engine/src/ECS/Scene.cpp new file mode 100644 index 0000000..581a5c4 --- /dev/null +++ b/Engine/src/ECS/Scene.cpp @@ -0,0 +1,62 @@ +// +// Created by atdunbg on 2026/9/22. +// + +#include +#include + +namespace Engine::ECS { + + Entity Scene::CreateEntity(std::string name) + { + Entity e = m_Registry.Create(); + // IdComponent 自动挂载:每个实体出生即有稳定 UUID + m_Registry.Add(e, IdComponent{UUID::New()}); + if (!name.empty()) { + m_Registry.Add(e, NameComponent{std::move(name)}); + } + return e; + } + + Entity Scene::FindEntityByUUID(UUID uuid) + { + if (!uuid.Valid()) return {}; + + Entity found{}; + m_Registry.Each([&](Entity e, IdComponent& id) { + if (!found && id.uuid == uuid) found = e; + }); + return found; + } + + void Scene::DestroyEntity(Entity e) + { + if (!m_Registry.IsAlive(e)) { + ENGINE_FATAL("Scene::DestroyEntity: stale entity ({},{})", + e.index, e.generation); + } + m_PendingDestroy.push_back(e); + } + + void Scene::FlushDestroyQueue() + { + if (m_PendingDestroy.empty()) return; + + for (Entity e : m_PendingDestroy) { + if (m_Registry.IsAlive(e)) { + m_Registry.Destroy(e); + } + } + m_PendingDestroy.clear(); + } + + CameraComponent* Scene::GetPrimaryCamera() + { + CameraComponent* found = nullptr; + m_Registry.Each([&](Entity, CameraComponent& cc) { + if (!found && cc.primary) found = &cc; + }); + return found; + } + +} // namespace Engine::ECS diff --git a/Engine/src/RenderGraph/RenderGraph.cpp b/Engine/src/RenderGraph/RenderGraph.cpp new file mode 100644 index 0000000..e0dcb98 --- /dev/null +++ b/Engine/src/RenderGraph/RenderGraph.cpp @@ -0,0 +1,781 @@ +// +// Created by atdunbg on 2026/9/22. +// + +#include +#include +#include + +#include +#include + +namespace Engine::RG { + + namespace { + bool IsWriteUse(ResourceUse use) + { + switch (use) { + case ResourceUse::ColorAttachment: + case ResourceUse::DepthAttachment: + case ResourceUse::TransferDst: + case ResourceUse::Present: + return true; + case ResourceUse::ShaderRead: + case ResourceUse::TransferSrc: + return false; + } + return false; + } + } // namespace + + // ---------- 构造 / 析构 ---------- + + RenderGraph::RenderGraph(Device& device, MemoryAllocator& allocator, u32 framesInFlight) + : m_Device(device), m_Allocator(allocator), m_FramesInFlight(framesInFlight) + { + } + + RenderGraph::~RenderGraph() + { + ReleasePhysical(); + + const auto& vkDevice = m_Device.GetDevice(); + for (auto& [key, images] : m_Pool) { + for (auto& img : images) { + if (!img->valid) continue; + vkDevice.destroyImageView(img->view); + vkDevice.destroyImage(img->image); + m_Allocator.Free(img->allocation); + } + } + m_Pool.clear(); + } + + // ---------- 资源声明 ---------- + + TextureHandle RenderGraph::CreateTexture(std::string name, const TextureDesc& desc) + { + CheckMutable(); + + TextureHandle h{static_cast(m_Textures.size()), 1}; + + TextureEntry entry; + entry.name = std::move(name); + entry.index = h.index; + entry.desc = desc; + entry.imported = false; + entry.generation = h.generation; + entry.physical.resize(m_FramesInFlight, nullptr); + entry.slots.resize(m_FramesInFlight); + + m_Textures.push_back(std::move(entry)); + return h; + } + + TextureHandle RenderGraph::ImportSwapchain(std::string name, vk::Format format) + { + CheckMutable(); + + TextureHandle h{static_cast(m_Textures.size()), 1}; + + TextureEntry entry; + entry.name = std::move(name); + entry.index = h.index; + entry.desc.format = format; + entry.imported = true; + entry.generation = h.generation; + entry.physical.resize(m_FramesInFlight, nullptr); + entry.slots.resize(m_FramesInFlight); + + m_Textures.push_back(std::move(entry)); + return h; + } + + void RenderGraph::SetImportedImage(TextureHandle h, vk::Image image, vk::ImageView view) + { + auto& entry = GetEntry(h); + if (!entry.imported) { + ENGINE_FATAL("RenderGraph: SetImportedImage on non-imported resource '{}'", entry.name); + } + entry.importedImage = image; + entry.importedView = view; + } + + // ---------- Pass 声明 ---------- + + RenderGraph::PassBuilder RenderGraph::AddPass(std::string name, ExecuteFn exec) + { + CheckMutable(); + + PassNode node; + node.name = std::move(name); + node.exec = std::move(exec); + + m_Passes.push_back(std::move(node)); + return PassBuilder(*this, static_cast(m_Passes.size() - 1)); + } + + RenderGraph::PassBuilder& RenderGraph::PassBuilder::Write(TextureHandle h, ResourceUse use) + { + m_Graph.CheckMutable(); + m_Graph.m_Passes[m_PassIndex].uses.push_back({h.index, use, {}}); + return *this; + } + + RenderGraph::PassBuilder& RenderGraph::PassBuilder::Read(TextureHandle h, ResourceUse use, + vk::ShaderStageFlags stage) + { + m_Graph.CheckMutable(); + m_Graph.m_Passes[m_PassIndex].uses.push_back({h.index, use, stage}); + return *this; + } + + RenderGraph::PassBuilder& RenderGraph::PassBuilder::SideEffect() + { + m_Graph.CheckMutable(); + m_Graph.m_Passes[m_PassIndex].sideEffect = true; + return *this; + } + + // ---------- 编译 ---------- + + void RenderGraph::Compile() + { + if (m_TargetExtent.width == 0 || m_TargetExtent.height == 0) { + ENGINE_FATAL("RenderGraph: Compile before SetTargetExtent"); + } + + ReleasePhysical(); + + // 清理上一次编译的中间结果 + for (auto& entry : m_Textures) { + entry.usage = {}; + entry.firstPass = -1; + entry.lastPass = -1; + entry.chainLayout = vk::ImageLayout::eUndefined; + entry.chainStage = {}; + entry.chainAccess = {}; + entry.chainWasWrite = false; + } + for (auto& pass : m_Passes) { + pass.culled = false; + pass.barriers.clear(); + } + + for (auto& entry : m_Textures) ResolveUsage(entry); + + CullPasses(); + Toposort(); + ComputeLifetimes(); + PrecomputeBarriers(); + AllocatePhysical(); + + m_Compiled = true; + m_ActivePassCount = static_cast( + std::count_if(m_Passes.begin(), m_Passes.end(), + [](const PassNode& p) { return !p.culled; })); + + ENGINE_INFO("RenderGraph compiled: {} passes ({} active), {} textures, {} barriers, {} gpu images", + m_Passes.size(), m_ActivePassCount, m_Textures.size(), + GetBarrierCount(), GetPooledImageCount()); + } + + void RenderGraph::SetTargetExtent(vk::Extent2D extent) + { + if (extent == m_TargetExtent) return; + + // 尺寸变化:调用方必须保证 GPU 空闲(通常在 swapchain recreate 的 waitIdle 之后) + ReleasePhysical(); + PurgePool(extent); + + // 物理镜像全部换新,槽位状态清零 + for (auto& entry : m_Textures) { + for (auto& slot : entry.slots) slot.used = false; + } + + m_TargetExtent = extent; + Compile(); + } + + void RenderGraph::ResolveUsage(TextureEntry& entry) + { + if (entry.imported) return; + + vk::ImageUsageFlags usage{}; + for (const auto& pass : m_Passes) { + for (const auto& use : pass.uses) { + if (use.resIndex != entry.index) continue; + switch (use.use) { + case ResourceUse::ColorAttachment: + case ResourceUse::Present: + usage |= vk::ImageUsageFlagBits::eColorAttachment; + break; + case ResourceUse::DepthAttachment: + usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment; + entry.aspect = vk::ImageAspectFlagBits::eDepth; + break; + case ResourceUse::ShaderRead: + usage |= vk::ImageUsageFlagBits::eSampled; + break; + case ResourceUse::TransferSrc: + usage |= vk::ImageUsageFlagBits::eTransferSrc; + break; + case ResourceUse::TransferDst: + usage |= vk::ImageUsageFlagBits::eTransferDst; + break; + } + } + } + entry.usage = usage; + } + + void RenderGraph::CullPasses() + { + // 引用计数剔除:一个 pass 存活当且仅当 + // a) 声明了 sideEffect,或 + // b) 它读写某个导入资源(对外可见),或 + // c) 它写出的某个资源被某个存活 pass 读取。 + // 迭代到不动点(最多 pass 数轮)。 + + std::vector kept(m_Passes.size(), false); + std::vector readByKept(m_Textures.size(), false); + + bool changed = true; + while (changed) { + changed = false; + for (u32 i = 0; i < m_Passes.size(); ++i) { + if (kept[i]) continue; + const auto& pass = m_Passes[i]; + + bool keep = pass.sideEffect; + for (const auto& use : pass.uses) { + if (IsWriteUse(use.use) && readByKept[use.resIndex]) { + keep = true; + } else if (m_Textures[use.resIndex].imported) { + keep = true; + } + } + if (!keep) continue; + + kept[i] = true; + changed = true; + for (const auto& use : pass.uses) { + if (!IsWriteUse(use.use)) readByKept[use.resIndex] = true; + } + } + } + + for (u32 i = 0; i < m_Passes.size(); ++i) { + m_Passes[i].culled = !kept[i]; + } + } + + void RenderGraph::Toposort() + { + // writer → reader 建边(去重),Kahn 拓扑排序。 + // 声明顺序通常已是拓扑序,但引擎级实现必须显式校验, + // 避免上层重构 pass 顺序时引入环。 + + std::vector> writers(m_Textures.size()); + for (u32 i = 0; i < m_Passes.size(); ++i) { + if (m_Passes[i].culled) continue; + for (const auto& use : m_Passes[i].uses) { + if (IsWriteUse(use.use)) { + writers[use.resIndex].push_back(i); + } + } + } + + std::vector> adj(m_Passes.size()); + std::vector indegree(m_Passes.size(), 0); + + for (u32 q = 0; q < m_Passes.size(); ++q) { + if (m_Passes[q].culled) continue; + std::set seenWriters; + for (const auto& use : m_Passes[q].uses) { + if (IsWriteUse(use.use)) continue; + for (u32 p : writers[use.resIndex]) { + if (p == q || m_Passes[p].culled) continue; + if (seenWriters.insert(p).second) { + adj[p].insert(q); + ++indegree[q]; + } + } + } + } + + m_ExecutionOrder.clear(); + m_ExecutionOrder.reserve(m_Passes.size()); + + // 同层按声明序出队,保证确定性 + std::set ready; + for (u32 i = 0; i < m_Passes.size(); ++i) { + if (!m_Passes[i].culled && indegree[i] == 0) ready.insert(i); + } + + while (!ready.empty()) { + u32 p = *ready.begin(); + ready.erase(ready.begin()); + m_ExecutionOrder.push_back(p); + + for (u32 q : adj[p]) { + if (--indegree[q] == 0) ready.insert(q); + } + } + + u32 activeCount = 0; + for (const auto& pass : m_Passes) { + if (!pass.culled) ++activeCount; + } + + if (m_ExecutionOrder.size() != activeCount) { + ENGINE_FATAL("RenderGraph: cycle detected in pass declarations"); + } + } + + void RenderGraph::ComputeLifetimes() + { + for (u32 pos = 0; pos < m_ExecutionOrder.size(); ++pos) { + const u32 passIdx = m_ExecutionOrder[pos]; + for (const auto& use : m_Passes[passIdx].uses) { + auto& entry = m_Textures[use.resIndex]; + if (entry.firstPass < 0) entry.firstPass = static_cast(pos); + entry.lastPass = static_cast(pos); + } + } + } + + void RenderGraph::PrecomputeBarriers() + { + // 两阶段: + // Pass A:按执行序推演每个资源的状态链,得到稳态(resting)。 + // Pass B:真正生成 barrier。 + // + // entry barrier(资源在该 pass 首次被使用)有两个变体: + // first — 该物理实例的槽位尚未用过:old = Undefined(无历史依赖) + // steady — 槽位已用过:old = resting(上一轮执行结束时的状态)。 + // 同一槽位两次进入图之间有 CPU fence 全等保,状态链闭合。 + // imported 资源(swapchain)acquire 后 layout 语义上总是 Undefined,恒用 first。 + // + // 非 entry:layout 变化或存在写读/读写/写写依赖时插入(read-read 无害跳过)。 + + // ---- Pass A:状态链推演 ---- + for (const u32 passIdx : m_ExecutionOrder) { + for (const auto& use : m_Passes[passIdx].uses) { + auto& entry = m_Textures[use.resIndex]; + entry.chainLayout = UseLayout(use.use); + entry.chainStage = UseDstStage(use); + entry.chainAccess = UseDstAccess(use); + entry.chainWasWrite = IsWriteUse(use.use); + } + } + + for (auto& entry : m_Textures) { + if (entry.imported || entry.firstPass < 0) continue; + entry.restingLayout = entry.chainLayout; + entry.restingStage = entry.chainStage; + entry.restingAccess = entry.chainAccess; + } + + // ---- Pass B:barrier 生成 ---- + for (auto& entry : m_Textures) { + entry.chainLayout = vk::ImageLayout::eUndefined; + entry.chainStage = {}; + entry.chainAccess = {}; + entry.chainWasWrite = false; + } + + for (u32 pos = 0; pos < m_ExecutionOrder.size(); ++pos) { + auto& pass = m_Passes[m_ExecutionOrder[pos]]; + + for (const auto& use : pass.uses) { + auto& entry = m_Textures[use.resIndex]; + + const vk::ImageLayout desiredLayout = UseLayout(use.use); + const vk::PipelineStageFlags2 dstStage = UseDstStage(use); + const vk::AccessFlags2 dstAccess = UseDstAccess(use); + const bool curIsWrite = IsWriteUse(use.use); + + auto makeBarrier = [&](vk::ImageLayout oldLayout, + vk::PipelineStageFlags2 srcStage, + vk::AccessFlags2 srcAccess) { + vk::ImageMemoryBarrier2 b; + b.setSrcStageMask(srcStage) + .setSrcAccessMask(srcAccess) + .setDstStageMask(dstStage) + .setDstAccessMask(dstAccess) + .setOldLayout(oldLayout) + .setNewLayout(desiredLayout) + // image 句柄由 AllocatePhysical/每帧导入回填 + .setSubresourceRange({entry.aspect, 0, 1, 0, 1}); + return b; + }; + + if (entry.firstPass == static_cast(pos)) { + // entry barrier:两个变体 + vk::ImageMemoryBarrier2 first = makeBarrier( + vk::ImageLayout::eUndefined, + vk::PipelineStageFlagBits2::eNone, + vk::AccessFlagBits2::eNone); + + vk::ImageMemoryBarrier2 steady; + if (entry.imported) { + // swapchain:每帧 acquire 后都是 Undefined,两变体相同 + steady = first; + } else { + steady = makeBarrier(entry.restingLayout, + entry.restingStage, + entry.restingAccess); + } + + pass.barriers.push_back({use.resIndex, steady, first}); + entry.chainLayout = desiredLayout; + entry.chainStage = dstStage; + entry.chainAccess = dstAccess; + entry.chainWasWrite = curIsWrite; + continue; + } + + // 非 entry:layout 变化,或存在写读/读写/写写依赖时插入 + bool need = entry.chainLayout != desiredLayout || + entry.chainWasWrite || curIsWrite; + if (!need) continue; + + vk::ImageMemoryBarrier2 b = makeBarrier( + entry.chainLayout, entry.chainStage, entry.chainAccess); + + pass.barriers.push_back({use.resIndex, b, b}); + entry.chainLayout = desiredLayout; + entry.chainStage = dstStage; + entry.chainAccess = dstAccess; + entry.chainWasWrite = curIsWrite; + } + } + } + + void RenderGraph::AllocatePhysical() + { + for (auto& entry : m_Textures) { + if (entry.imported || entry.firstPass < 0) continue; + + vk::Extent2D extent{ + entry.desc.width ? entry.desc.width : m_TargetExtent.width, + entry.desc.height ? entry.desc.height : m_TargetExtent.height + }; + + for (u32 slot = 0; slot < m_FramesInFlight; ++slot) { + entry.physical[slot] = AcquirePhysical( + entry.desc.format, extent, entry.usage, entry.aspect); + } + // barrier 的 image 句柄在 Execute 期按帧槽位绑定(见 Execute) + } + } + + void RenderGraph::ReleasePhysical() + { + for (auto& entry : m_Textures) { + if (entry.imported) continue; + for (u32 slot = 0; slot < entry.physical.size(); ++slot) { + auto* img = entry.physical[slot]; + if (!img) continue; + + PoolKey key{ + static_cast(img->format), + img->extent.width, img->extent.height, + static_cast(img->usage) + }; + m_Pool[key].push_back(Scope(img)); + entry.physical[slot] = nullptr; + } + } + } + + void RenderGraph::PurgePool(vk::Extent2D target) + { + const auto& vkDevice = m_Device.GetDevice(); + + for (auto it = m_Pool.begin(); it != m_Pool.end();) { + if (it->first.width != target.width || it->first.height != target.height) { + for (auto& img : it->second) { + if (!img->valid) continue; + vkDevice.destroyImageView(img->view); + vkDevice.destroyImage(img->image); + m_Allocator.Free(img->allocation); + } + it = m_Pool.erase(it); + } else { + ++it; + } + } + } + + PhysicalImage* RenderGraph::AcquirePhysical(vk::Format format, vk::Extent2D extent, + vk::ImageUsageFlags usage, + vk::ImageAspectFlagBits aspect) + { + PoolKey key{static_cast(format), extent.width, extent.height, + static_cast(usage)}; + + // 池中取空闲镜像 + auto it = m_Pool.find(key); + if (it != m_Pool.end() && !it->second.empty()) { + PhysicalImage* img = it->second.back().release(); + it->second.pop_back(); + return img; + } + + // 新建:所有权归 TextureEntry.physical,Release 时归还池 + auto img = CreateScope(); + img->extent = extent; + img->format = format; + img->usage = usage; + img->valid = true; + + const auto& vkDevice = m_Device.GetDevice(); + + vk::ImageCreateInfo imageInfo; + imageInfo.setImageType(vk::ImageType::e2D) + .setFormat(format) + .setExtent({extent.width, extent.height, 1}) + .setMipLevels(1) + .setArrayLayers(1) + .setSamples(vk::SampleCountFlagBits::e1) + .setTiling(vk::ImageTiling::eOptimal) + .setUsage(usage) + .setSharingMode(vk::SharingMode::eExclusive) + .setInitialLayout(vk::ImageLayout::eUndefined); + + img->image = vkDevice.createImage(imageInfo); + + auto reqs = vkDevice.getImageMemoryRequirements(img->image); + img->allocation = m_Allocator.Allocate(reqs, vk::MemoryPropertyFlagBits::eDeviceLocal); + vkDevice.bindImageMemory(img->image, img->allocation.Memory, img->allocation.Offset); + + vk::ImageViewCreateInfo viewInfo; + viewInfo.setImage(img->image) + .setViewType(vk::ImageViewType::e2D) + .setFormat(format) + .setSubresourceRange({aspect, 0, 1, 0, 1}); + + img->view = vkDevice.createImageView(viewInfo); + + return img.release(); + } + + // ---------- 执行 ---------- + + void RenderGraph::Execute(const FrameContext& frame) + { + for (const u32 passIdx : m_ExecutionOrder) { + auto& pass = m_Passes[passIdx]; + if (pass.culled) continue; + + // barrier:按槽位状态挑选变体,批量提交 + m_ScratchBarriers.clear(); + for (const auto& bp : pass.barriers) { + auto& entry = m_Textures[bp.resIndex]; + + bool useFirst; + if (entry.imported) { + useFirst = true; // 恒为 Undefined 起点 + } else { + auto& slot = entry.slots[frame.frameSlot]; + useFirst = !slot.used; + slot.used = true; + } + + vk::ImageMemoryBarrier2 b = useFirst ? bp.first : bp.steady; + // image 句柄在执行期绑定:imported 用当帧 acquire 的 image, + // created 用当前帧槽位的物理镜像(每个槽位各有一份) + b.setImage(entry.imported + ? entry.importedImage + : entry.physical[frame.frameSlot]->image); + m_ScratchBarriers.push_back(b); + } + + if (!m_ScratchBarriers.empty()) { + vk::DependencyInfo dep; + dep.setImageMemoryBarriers(m_ScratchBarriers); + frame.cmd.pipelineBarrier2(dep); + } + + if (pass.exec) { + RenderGraphContext ctx; + ctx.cmd = frame.cmd; + ctx.frame = frame; + ctx.extent = m_TargetExtent; + ctx.graph = this; + pass.exec(ctx); + } + } + } + + // ---------- RenderGraphContext ---------- + + vk::ImageView RenderGraphContext::View(TextureHandle h) const + { + const auto& entry = graph->GetEntry(h); + if (entry.imported) return entry.importedView; + return entry.physical[frame.frameSlot]->view; + } + + vk::Image RenderGraphContext::Image(TextureHandle h) const + { + const auto& entry = graph->GetEntry(h); + if (entry.imported) return entry.importedImage; + return entry.physical[frame.frameSlot]->image; + } + + // ---------- 工具 / 统计 ---------- + + TextureEntry& RenderGraph::GetEntry(TextureHandle h) + { + CheckHandle(h); + return m_Textures[h.index]; + } + + const TextureEntry& RenderGraph::GetEntry(TextureHandle h) const + { + CheckHandle(h); + return m_Textures[h.index]; + } + + void RenderGraph::CheckHandle(TextureHandle h) const + { + if (!h.Valid() || h.index >= m_Textures.size() || + m_Textures[h.index].generation != h.generation) { + ENGINE_FATAL("RenderGraph: invalid texture handle (index={}, gen={})", + h.index, h.generation); + } + } + + void RenderGraph::CheckMutable() const + { + // 图结构(资源/pass/use)只在声明期可变;Compile 后 barrier 已预算, + // 静默接受修改会产生错误同步,直接拒绝。 + if (m_Compiled) { + ENGINE_FATAL("RenderGraph: graph is compiled; structural changes are not allowed"); + } + } + + vk::ImageLayout RenderGraph::UseLayout(ResourceUse use) + { + switch (use) { + case ResourceUse::ColorAttachment: return vk::ImageLayout::eColorAttachmentOptimal; + case ResourceUse::DepthAttachment: return vk::ImageLayout::eDepthAttachmentOptimal; + case ResourceUse::ShaderRead: return vk::ImageLayout::eShaderReadOnlyOptimal; + case ResourceUse::TransferSrc: return vk::ImageLayout::eTransferSrcOptimal; + case ResourceUse::TransferDst: return vk::ImageLayout::eTransferDstOptimal; + case ResourceUse::Present: return vk::ImageLayout::ePresentSrcKHR; + } + return vk::ImageLayout::eUndefined; + } + + vk::PipelineStageFlags2 RenderGraph::UseDstStage(const PassNode::Use& use) + { + switch (use.use) { + case ResourceUse::ColorAttachment: + return vk::PipelineStageFlagBits2::eColorAttachmentOutput; + case ResourceUse::DepthAttachment: + return vk::PipelineStageFlagBits2::eEarlyFragmentTests | + vk::PipelineStageFlagBits2::eLateFragmentTests; + case ResourceUse::ShaderRead: + // ShaderStageFlags 与 PipelineStageFlags2 位值兼容,需显式转换 + return vk::PipelineStageFlags2( + static_cast( + static_cast(use.readStage))); + case ResourceUse::TransferSrc: + case ResourceUse::TransferDst: + return vk::PipelineStageFlagBits2::eTransfer; + case ResourceUse::Present: + return vk::PipelineStageFlagBits2::eNone; + } + return vk::PipelineStageFlagBits2::eNone; + } + + vk::PipelineStageFlags2 RenderGraph::UseSrcStage(ResourceUse use) + { + switch (use) { + case ResourceUse::ColorAttachment: + case ResourceUse::Present: + return vk::PipelineStageFlagBits2::eColorAttachmentOutput; + case ResourceUse::DepthAttachment: + return vk::PipelineStageFlagBits2::eEarlyFragmentTests | + vk::PipelineStageFlagBits2::eLateFragmentTests; + case ResourceUse::ShaderRead: + return vk::PipelineStageFlagBits2::eFragmentShader; + case ResourceUse::TransferSrc: + case ResourceUse::TransferDst: + return vk::PipelineStageFlagBits2::eTransfer; + } + return vk::PipelineStageFlagBits2::eNone; + } + + vk::AccessFlags2 RenderGraph::UseDstAccess(const PassNode::Use& use) + { + switch (use.use) { + case ResourceUse::ColorAttachment: + return vk::AccessFlagBits2::eColorAttachmentWrite; + case ResourceUse::DepthAttachment: + return vk::AccessFlagBits2::eDepthStencilAttachmentWrite; + case ResourceUse::ShaderRead: + return vk::AccessFlagBits2::eShaderRead; + case ResourceUse::TransferSrc: + return vk::AccessFlagBits2::eTransferRead; + case ResourceUse::TransferDst: + return vk::AccessFlagBits2::eTransferWrite; + case ResourceUse::Present: + // layout-only 转换(交给 presentation engine),无访问掩码 + return vk::AccessFlagBits2::eNone; + } + return vk::AccessFlagBits2::eNone; + } + + vk::AccessFlags2 RenderGraph::UseSrcAccess(ResourceUse use) + { + switch (use) { + case ResourceUse::ColorAttachment: + case ResourceUse::Present: + return vk::AccessFlagBits2::eColorAttachmentWrite; + case ResourceUse::DepthAttachment: + return vk::AccessFlagBits2::eDepthStencilAttachmentWrite; + case ResourceUse::ShaderRead: + return vk::AccessFlagBits2::eShaderRead; + case ResourceUse::TransferSrc: + return vk::AccessFlagBits2::eTransferRead; + case ResourceUse::TransferDst: + return vk::AccessFlagBits2::eTransferWrite; + } + return vk::AccessFlagBits2::eNone; + } + + u32 RenderGraph::GetPassCount() const { return static_cast(m_Passes.size()); } + u32 RenderGraph::GetActivePassCount() const { return m_ActivePassCount; } + u32 RenderGraph::GetTextureCount() const { return static_cast(m_Textures.size()); } + + u32 RenderGraph::GetBarrierCount() const + { + u32 total = 0; + for (const auto& pass : m_Passes) { + total += static_cast(pass.barriers.size()); + } + return total; + } + + u32 RenderGraph::GetPooledImageCount() const + { + // 池中空闲 + 租借中(挂在资源上的物理镜像) + u32 total = 0; + for (const auto& [key, images] : m_Pool) { + total += static_cast(images.size()); + } + for (const auto& entry : m_Textures) { + if (entry.imported) continue; + for (auto* img : entry.physical) { + if (img) ++total; + } + } + return total; + } + +} // namespace Engine::RG diff --git a/Engine/src/Renderer/Camera.cpp b/Engine/src/Renderer/Camera.cpp new file mode 100644 index 0000000..12c0c20 --- /dev/null +++ b/Engine/src/Renderer/Camera.cpp @@ -0,0 +1,64 @@ +// +// Created by atdunbg on 2026/9/22. +// + +#include +#include + +#include + +#include "GLFW/glfw3.h" + +namespace Engine { + + void Camera::Update(Window& window, f32 deltaTime) + { + // 鼠标旋转(需显式启用,配合 cursor capture 使用) + if (m_MouseLookEnabled) + { + f32 mouseDX = 0, mouseDY = 0; + window.GetMouseDelta(mouseDX, mouseDY); + + if (mouseDX != 0 || mouseDY != 0) + { + m_Yaw += mouseDX * m_MouseSensitivity; + m_Pitch -= mouseDY * m_MouseSensitivity; + m_Pitch = glm::clamp(m_Pitch, -89.0f, 89.0f); + } + } + + UpdateBasis(); + + f32 speed = m_MoveSpeed * deltaTime; + if (window.IsKeyPressed(GLFW_KEY_W)) m_Position += m_Forward * speed; + if (window.IsKeyPressed(GLFW_KEY_S)) m_Position -= m_Forward * speed; + if (window.IsKeyPressed(GLFW_KEY_A)) m_Position -= m_Right * speed; + if (window.IsKeyPressed(GLFW_KEY_D)) m_Position += m_Right * speed; + if (window.IsKeyPressed(GLFW_KEY_SPACE)) m_Position.y += speed; + if (window.IsKeyPressed(GLFW_KEY_LEFT_SHIFT)) m_Position.y -= speed; + } + + void Camera::UpdateBasis() + { + m_Forward.x = cosf(glm::radians(m_Yaw)) * cosf(glm::radians(m_Pitch)); + m_Forward.y = sinf(glm::radians(m_Pitch)); + m_Forward.z = sinf(glm::radians(m_Yaw)) * cosf(glm::radians(m_Pitch)); + m_Forward = glm::normalize(m_Forward); + + m_Right = glm::normalize(glm::cross(m_Forward, glm::vec3(0, 1, 0))); + m_Up = glm::normalize(glm::cross(m_Right, m_Forward)); + } + + glm::mat4 Camera::GetView() const + { + return glm::lookAt(m_Position, m_Position + m_Forward, m_Up); + } + + glm::mat4 Camera::GetProjection(f32 aspect, f32 nearZ, f32 farZ) const + { + auto proj = glm::perspective(glm::radians(45.0f), aspect, nearZ, farZ); + proj[1][1] *= -1.0f; // Vulkan Y 翻转 + return proj; + } + +} // namespace Engine diff --git a/Engine/src/Renderer/Renderer.cpp b/Engine/src/Renderer/Renderer.cpp index 3d6168f..373079c 100644 --- a/Engine/src/Renderer/Renderer.cpp +++ b/Engine/src/Renderer/Renderer.cpp @@ -4,6 +4,11 @@ #include #include +#include +#include +#include +#include +#include #include #include #include @@ -20,26 +25,30 @@ #include #include -#include - -#include "Engine/Core/Stats.h" -#include "GLFW/glfw3.h" namespace Engine { + using namespace ECS; // TransformComponent / Entity 等直接使用 - struct Light { - glm::vec4 direction; - glm::vec4 color; // rgb = 颜色,a = 强度 - }; + namespace + { + struct Light { + glm::vec4 direction; // xyz = 方向,w 未用 + glm::vec4 color; // rgb = 颜色,a = 强度 + }; - struct UniformBufferObject { - glm::mat4 view; - glm::mat4 proj; - glm::vec4 ambientColor; - Light lights[4]; - }; + struct UniformBufferObject { + glm::mat4 view; + glm::mat4 proj; + glm::vec4 ambientColor; + Light lights[4]; + }; + constexpr u32 kUboBinding = 0; + constexpr u32 kUBOSetIndex = 0; + constexpr u32 kTextureSetIndex = 1; + constexpr u32 kMaxLights = 4; + } Renderer::Renderer(Window& window) : m_Window(window) @@ -55,7 +64,8 @@ namespace Engine // 4. Set 0: UBO std::vector uboBindings = { - {0, vk::DescriptorType::eUniformBuffer, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment }, + {kUboBinding, vk::DescriptorType::eUniformBuffer, + vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment}, }; m_UBOSetLayout = m_DescriptorManager->CreateLayout(uboBindings); @@ -66,277 +76,350 @@ namespace Engine m_TextureSetLayout = m_DescriptorManager->CreateLayout(texBindings); // 6. PipelineLayout:两个 set - std::vector layouts = { - m_UBOSetLayout, m_TextureSetLayout - }; m_PipelineLayout = m_DescriptorManager->CreatePipelineLayout( - layouts, sizeof(glm::mat4)); + {m_UBOSetLayout, m_TextureSetLayout}, sizeof(glm::mat4)); - // 7. ShaderManager + // 7. ShaderManager(VK_EXT_shader_object) m_ShaderManager = CreateScope(*m_Device); std::string shaderDir = "shaders/"; auto vert = m_ShaderManager->CreateFromFile( shaderDir + "simple_shader.vert.spv", vk::ShaderStageFlagBits::eVertex, - layouts, sizeof(glm::mat4)); + {m_UBOSetLayout, m_TextureSetLayout}, sizeof(glm::mat4)); auto frag = m_ShaderManager->CreateFromFile( shaderDir + "simple_shader.frag.spv", vk::ShaderStageFlagBits::eFragment, - layouts, sizeof(glm::mat4)); + {m_UBOSetLayout, m_TextureSetLayout}, sizeof(glm::mat4)); m_VertShader = vert.Handle; m_FragShader = frag.Handle; - if (!m_VertShader || !m_FragShader) - { + if (!m_VertShader || !m_FragShader) { ENGINE_FATAL("Failed to create shader objects"); } - // 8. Swapchain - m_Swapchain = CreateScope(*m_Device, *m_Allocator, window); + // 8. Swapchain(只管呈现;深度图由 RenderGraph 创建) + m_Swapchain = CreateScope(*m_Device, *m_Allocator, m_Window); m_Extent = m_Swapchain->GetExtent(); - m_Format = m_Swapchain->GetFormat(); - // 9. SyncManager - m_SyncManager = CreateScope(*m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount()); + // 9. Sync / Command(per-frame) + m_SyncManager = CreateScope( + *m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount()); + m_CommandManager = CreateScope(*m_Device, kMaxFramesInFlight); - // 10. CommandManager - m_CommandManager = CreateScope(*m_Device); - - // 11. Uniform Buffer + // 10. Per-frame UBO ring + 每槽位的 UBO descriptor set m_UniformBuffer = CreateScope( - *m_Device, *m_Allocator, sizeof(UniformBufferObject)); + *m_Device, *m_Allocator, sizeof(UniformBufferObject), kMaxFramesInFlight); - // 12. UBO DescriptorSet - m_UBODescriptorSet = m_DescriptorManager->AllocateDescriptorSet(m_UBOSetLayout); - m_DescriptorManager->UpdateUniformBuffer( - m_UBODescriptorSet, 0, m_UniformBuffer->Get(), m_UniformBuffer->GetSize()); - - // 13. Model(模型自己加载纹理) - m_Model = CreateScope( - *m_Device, *m_Allocator, *m_DescriptorManager, - m_TextureSetLayout, - "assets/models/backpack/backpack.obj"); - - // 14. 创建几个物体,摆一排 - for (int i = 0; i < 3; ++i) { - Object obj; - obj.model = m_Model.get(); - obj.transform = glm::translate( - glm::mat4(1.0f), - glm::vec3((i - 1) * 1.5f, 0.0f, 0.0f)); - m_Objects.push_back(obj); + m_UBODescriptorSets.resize(kMaxFramesInFlight); + for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { + m_UBODescriptorSets[slot] = + m_DescriptorManager->AllocateDescriptorSet(m_UBOSetLayout); + m_DescriptorManager->UpdateUniformBuffer( + m_UBODescriptorSets[slot], kUboBinding, + m_UniformBuffer->Get(slot), m_UniformBuffer->GetSize()); } - m_StartTime = std::chrono::steady_clock::now(); + // 11. RenderGraph:声明 + 编译(场景在 Render(Scene&) 时按帧绑定) + BuildGraph(); ENGINE_INFO("Renderer initialized"); - } Renderer::~Renderer() { m_Device->GetDevice().waitIdle(); - if (m_VertShader) - { + if (m_VertShader) { m_Device->GetDevice().destroyShaderEXT(m_VertShader); } - if (m_FragShader) - { + if (m_FragShader) { m_Device->GetDevice().destroyShaderEXT(m_FragShader); } m_Device->GetDevice().destroyPipelineLayout(m_PipelineLayout); - m_Device->GetDevice().destroyDescriptorSetLayout(m_UBOSetLayout); m_Device->GetDevice().destroyDescriptorSetLayout(m_TextureSetLayout); } - - void Renderer::Render() + void Renderer::BuildGraph() { + m_RenderGraph = CreateScope( + *m_Device, *m_Allocator, kMaxFramesInFlight); - if (m_Window.WasResized()) - { + // 导入 swapchain 颜色附件 + m_Backbuffer = m_RenderGraph->ImportSwapchain( + "Backbuffer", m_Swapchain->GetFormat()); + + // 深度图:目标尺寸,格式由 Device 能力决定 + const vk::Format depthFormat = m_Device->FindSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, + vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + + m_Depth = m_RenderGraph->CreateTexture( + "MainDepth", RG::TextureDesc{depthFormat}); + + // ---- Forward pass:直接渲染到 swapchain ---- + // 场景数据每帧经 m_BoundScene 从 ECS 读取 + m_RenderGraph->AddPass("Forward", + [this](RG::RenderGraphContext& ctx) { + const vk::CommandBuffer cmd = ctx.cmd; + + vk::ClearValue clearValue; + clearValue.color = vk::ClearColorValue( + std::array{0.1f, 0.1f, 0.1f, 1.0f}); + + vk::RenderingAttachmentInfo colorAttachment; + colorAttachment.setImageView(ctx.View(m_Backbuffer)) + .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) + .setLoadOp(vk::AttachmentLoadOp::eClear) + .setStoreOp(vk::AttachmentStoreOp::eStore) + .setClearValue(clearValue); + + vk::RenderingAttachmentInfo depthAttachment; + depthAttachment.setImageView(ctx.View(m_Depth)) + .setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal) + .setLoadOp(vk::AttachmentLoadOp::eClear) + .setStoreOp(vk::AttachmentStoreOp::eDontCare) + .setClearValue(vk::ClearValue( + vk::ClearDepthStencilValue(1.0f, 0))); + + vk::RenderingInfo renderingInfo; + renderingInfo.setRenderArea({{0, 0}, ctx.extent}) + .setLayerCount(1) + .setColorAttachments(colorAttachment) + .setPDepthAttachment(&depthAttachment); + + cmd.beginRendering(renderingInfo); + + // 绑定 shader object + std::array stages = { + vk::ShaderStageFlagBits::eVertex, + vk::ShaderStageFlagBits::eFragment + }; + std::array shaders = {m_VertShader, m_FragShader}; + cmd.bindShadersEXT(stages, shaders); + + // Set 0:本帧槽位的 UBO + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + m_PipelineLayout, kUBOSetIndex, + m_UBODescriptorSets[ctx.frame.frameSlot], {}); + + // ---- 动态状态 ---- + cmd.setRasterizerDiscardEnable(VK_FALSE); + + cmd.setDepthTestEnable(VK_TRUE); + cmd.setDepthWriteEnable(VK_TRUE); + cmd.setDepthCompareOp(vk::CompareOp::eLess); + cmd.setDepthBoundsTestEnable(VK_FALSE); + + cmd.setStencilTestEnable(VK_FALSE); + cmd.setDepthBiasEnable(VK_FALSE); + cmd.setPolygonModeEXT(vk::PolygonMode::eFill); + cmd.setCullMode(vk::CullModeFlagBits::eNone); + cmd.setFrontFace(vk::FrontFace::eCounterClockwise); + cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList); + cmd.setPrimitiveRestartEnable(VK_FALSE); + + auto binding = Vertex::GetBindingDescription(); + auto attributes = Vertex::GetAttributeDescriptions(); + cmd.setVertexInputEXT(binding, attributes); + + cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1); + vk::SampleMask sampleMask = 0xFFFFFFFF; + cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask); + cmd.setAlphaToCoverageEnableEXT(VK_FALSE); + cmd.setAlphaToOneEnableEXT(VK_FALSE); + + vk::Viewport viewport(0.0f, 0.0f, + static_cast(ctx.extent.width), + static_cast(ctx.extent.height), + 0.0f, 1.0f); + cmd.setViewportWithCount(viewport); + + vk::Rect2D scissor({0, 0}, ctx.extent); + cmd.setScissorWithCount(scissor); + + vk::Bool32 blendEnable = VK_FALSE; + cmd.setColorBlendEnableEXT(0, blendEnable); + + vk::ColorBlendEquationEXT blendEquation; + blendEquation.setSrcColorBlendFactor(vk::BlendFactor::eOne) + .setDstColorBlendFactor(vk::BlendFactor::eZero) + .setColorBlendOp(vk::BlendOp::eAdd) + .setSrcAlphaBlendFactor(vk::BlendFactor::eOne) + .setDstAlphaBlendFactor(vk::BlendFactor::eZero) + .setAlphaBlendOp(vk::BlendOp::eAdd); + cmd.setColorBlendEquationEXT(0, blendEquation); + + vk::ColorComponentFlags colorWriteMask = + vk::ColorComponentFlagBits::eR | + vk::ColorComponentFlagBits::eG | + vk::ColorComponentFlagBits::eB | + vk::ColorComponentFlagBits::eA; + cmd.setColorWriteMaskEXT(0, colorWriteMask); + + // ---- 绘制:遍历 ECS 渲染体 ---- + // Each 以 Transform/MeshRenderer 中较小的池为主表—— + // 大场景下遍历开销与最小池规模成正比 + m_BoundScene->Each( + [&](Entity, TransformComponent& transform, + MeshRendererComponent& meshRenderer) { + // 句柄 → 资产(悬空句柄会被 AssetCache FATAL 拦截) + Model* model = m_BoundAssets->GetModel(meshRenderer.model); + if (!model) return; + + cmd.pushConstants(m_PipelineLayout, + vk::ShaderStageFlagBits::eVertex, + 0, sizeof(glm::mat4), &transform.world); + + for (auto& mesh : model->GetMeshes()) { + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + m_PipelineLayout, kTextureSetIndex, + mesh->GetMaterial()->GetDescriptorSet(), {}); + mesh->Draw(cmd); + } + }); + + cmd.endRendering(); + }) + .Write(m_Backbuffer, RG::ResourceUse::ColorAttachment) + .Write(m_Depth, RG::ResourceUse::DepthAttachment); + + // ---- Present pass:transition 到 ePresentSrcKHR ---- + m_RenderGraph->AddPass("Present", + [](RG::RenderGraphContext&) { + // present 的 layout 转换由 RG 的 barrier 完成, + // 实际 vkQueuePresentKHR 由 Renderer 驱动。 + }) + .Read(m_Backbuffer, RG::ResourceUse::Present); + + m_RenderGraph->SetTargetExtent(m_Extent); // 触发首次 Compile + } + + void Renderer::HandleResize() + { + m_Device->GetDevice().waitIdle(); + + m_Swapchain->Recreate(m_Window); + m_Extent = m_Swapchain->GetExtent(); + + // image 数量可能变化 + m_SyncManager = CreateScope( + *m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount()); + + // RG 重编译:物理镜像按新尺寸轮换 + m_RenderGraph->SetTargetExtent(m_Extent); + } + + void Renderer::Render(ECS::Scene& scene, Assets::AssetManager& assets) + { + m_BoundScene = &scene; + m_BoundAssets = &assets; + + if (m_Window.WasResized()) { m_Window.ResetResizedFlag(); - - m_Device->GetDevice().waitIdle(); - - m_Swapchain->Recreate(m_Window); - m_Extent = m_Swapchain->GetExtent(); - m_SyncManager = CreateScope( - *m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount()); + HandleResize(); } - u32 frameIndex = m_CurrentFrame; + const u32 frameSlot = m_CurrentFrame; const auto& vkDevice = m_Device->GetDevice(); - // 1. 更新 MVP 矩阵 - auto now = std::chrono::steady_clock::now(); - float currentTime = std::chrono::duration(now - m_StartTime).count(); - m_DeltaTime = currentTime - m_LastFrameTime; - m_LastFrameTime = currentTime; - - // 更新每个物体的 transform - for (size_t i = 0; i < m_Objects.size(); ++i) { - float angle = currentTime * glm::radians(60.0f) + i * 0.7f; - - glm::mat4 t = glm::translate( - glm::mat4(1.0f), - glm::vec3((static_cast(i) - 1.0f) * 1.5f, 0.0f, 0.0f)); - glm::mat4 r = glm::rotate(glm::mat4(1.0f), angle, glm::vec3(0.0f, 1.0f, 0.0f)); - - m_Objects[i].transform = t * r; + // 1. UBO:从 ECS 场景组装(primary 相机 + 灯光 + 环境色) + auto* cameraComp = scene.GetPrimaryCamera(); + if (!cameraComp) { + // 无相机:跳过本帧(正常场景必有相机,这里只是安全网) + return; } - - // 鼠标控制方向 - /* - float mouseDX = 0, mouseDY = 0; - m_Window.GetMouseDelta(mouseDX, mouseDY); - if (mouseDX != 0 || mouseDY != 0) - { - m_CameraYaw += mouseDX * m_MouseSensitivity; - m_CameraPitch -= mouseDY * m_MouseSensitivity; - m_CameraPitch = glm::clamp(m_CameraPitch, -89.0f, 89.0f); - } - */ - - // 相机前方向量 - glm::vec3 forward; - forward.x = cosf(glm::radians(m_CameraYaw)) * cos(glm::radians(m_CameraPitch)); - forward.y = sinf(glm::radians(m_CameraPitch)); - forward.z = sinf(glm::radians(m_CameraYaw)) * cos(glm::radians(m_CameraPitch)); - forward = glm::normalize(forward); - - glm::vec3 right = glm::normalize(glm::cross(forward, glm::vec3(0, 1, 0))); - glm::vec3 up = glm::normalize(glm::cross(right, forward)); - - // WASD 移动 - float speed = m_CameraSpeed * m_DeltaTime; - if (m_Window.IsKeyPressed(GLFW_KEY_W)) m_CameraPos += forward * speed; - if (m_Window.IsKeyPressed(GLFW_KEY_S)) m_CameraPos -= forward * speed; - if (m_Window.IsKeyPressed(GLFW_KEY_A)) m_CameraPos -= right * speed; - if (m_Window.IsKeyPressed(GLFW_KEY_D)) m_CameraPos += right * speed; - if (m_Window.IsKeyPressed(GLFW_KEY_SPACE)) m_CameraPos.y += speed; - if (m_Window.IsKeyPressed(GLFW_KEY_LEFT_SHIFT)) m_CameraPos.y -= speed; - - - auto view = glm::lookAt(m_CameraPos, - m_CameraPos + forward, - up); + const Camera& camera = cameraComp->camera; float aspect = static_cast(m_Extent.width) / - static_cast(m_Extent.height); - - auto proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f); - proj[1][1] *= -1.0f; // Vulkan Y 翻转 + static_cast(m_Extent.height); UniformBufferObject ubo{}; - ubo.view = view; - ubo.proj = proj; + ubo.view = camera.GetView(); + ubo.proj = camera.GetProjection(aspect, 0.1f, 100.0f); + ubo.ambientColor = scene.GetSettings().ambientColor; - ubo.ambientColor = glm::vec4(0.15f, 0.15f, 0.2f, 0.0f); + u32 lightCount = 0; + scene.Each([&](Entity, LightComponent& light) { + if (lightCount >= kMaxLights) return; + ubo.lights[lightCount].direction = + glm::length(light.direction) > 0.0f + ? glm::vec4(glm::normalize(light.direction), 0.0f) + : glm::vec4(0.0f); + ubo.lights[lightCount].color = light.color; + ++lightCount; + }); + for (u32 i = lightCount; i < kMaxLights; ++i) { + ubo.lights[i].direction = glm::vec4(0.0f); + ubo.lights[i].color = glm::vec4(0.0f); + } - // 主光:暖色,从左上方照下 - ubo.lights[0].direction = glm::vec4(glm::normalize(glm::vec3(-0.5f, -1.0f, -0.3f)), 0.0f); - ubo.lights[0].color = glm::vec4(1.0f, 0.95f, 0.9f, 1.0f); + // 此时本槽位的 fence 尚未 reset——先等它(保证 GPU 已完成本槽位上一轮), + // 再写 UBO,写-读竞争关闭。 + m_SyncManager->WaitFence(frameSlot); + m_UniformBuffer->Update(frameSlot, &ubo, sizeof(ubo)); + m_SyncManager->ResetFence(frameSlot); - // 补光:冷色,从右下方照上 - ubo.lights[1].direction = glm::vec4(glm::normalize(glm::vec3(0.5f, -0.5f, 0.3f)), 0.0f); - ubo.lights[1].color = glm::vec4(0.3f, 0.5f, 1.0f, 0.8f); - - // 未使用:强度 0 - ubo.lights[2].direction = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); - ubo.lights[2].color = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); - - ubo.lights[3].direction = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); - ubo.lights[3].color = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); - - m_UniformBuffer->Update(&ubo, sizeof(ubo)); - - // 等待这一帧上一次完成 - m_SyncManager->WaitFence(frameIndex); - - // 2. 获取交换链 image + // 2. acquire swapchain image u32 imageIndex = 0; - try - { + try { const auto result = vkDevice.acquireNextImageKHR( m_Swapchain->Get(), std::numeric_limits::max(), - m_SyncManager->GetImageAvailable(frameIndex)); + m_SyncManager->GetImageAvailable(frameSlot)); - if (result.result == vk::Result::eErrorOutOfDateKHR) - { - m_Swapchain->Recreate(m_Window); - return; // ← fence 没被 reset,下帧 wait 立即返回 + if (result.result == vk::Result::eErrorOutOfDateKHR) { + HandleResize(); + return; } imageIndex = result.value; } - catch (const vk::OutOfDateKHRError&) - { - m_Swapchain->Recreate(m_Window); + catch (const vk::OutOfDateKHRError&) { + HandleResize(); return; } - // 3. acquire 成功, reset fence - m_SyncManager->ResetFence(frameIndex); + // 3. 录制:FrameContext + RenderGraph + FrameContext frame; + frame.frameSlot = frameSlot; + frame.imageIndex = imageIndex; + frame.cmd = m_CommandManager->BeginFrame(frameSlot); - // 4. 录制命令 - auto cmd = m_CommandManager->BeginFrame(); + m_RenderGraph->SetImportedImage( + m_Backbuffer, + m_Swapchain->GetImages()[imageIndex], + m_Swapchain->GetImageViews()[imageIndex]); - TransitionImageLayout(cmd, m_Swapchain->GetImages()[imageIndex], - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal); + m_RenderGraph->Execute(frame); - // 深度转换 - vk::ImageMemoryBarrier2 depthBarrier; - depthBarrier.setSrcStageMask(vk::PipelineStageFlagBits2::eEarlyFragmentTests) - .setSrcAccessMask(vk::AccessFlagBits2::eDepthStencilAttachmentWrite) - .setDstStageMask(vk::PipelineStageFlagBits2::eEarlyFragmentTests) - .setDstAccessMask(vk::AccessFlagBits2::eDepthStencilAttachmentWrite) - .setOldLayout(vk::ImageLayout::eUndefined) - .setNewLayout(vk::ImageLayout::eDepthAttachmentOptimal) - .setImage(m_Swapchain->GetDepthImage()) - .setSubresourceRange({ - vk::ImageAspectFlagBits::eDepth, - 0, 1, 0, 1 - }); + m_CommandManager->EndFrame(frameSlot); - vk::DependencyInfo depthDep; - depthDep.setImageMemoryBarriers(depthBarrier); - cmd.pipelineBarrier2(depthDep); - - RecordCommands(cmd, imageIndex); - - TransitionImageLayout(cmd, m_Swapchain->GetImages()[imageIndex], - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR); - - m_CommandManager->EndFrame(); - - // 5. 提交 + // 4. 提交 std::array waitStages = { vk::PipelineStageFlagBits::eColorAttachmentOutput }; std::array waitSemaphores = { - m_SyncManager->GetImageAvailable(frameIndex) + m_SyncManager->GetImageAvailable(frameSlot) }; std::array signalSemaphores = { m_SyncManager->GetRenderFinished(imageIndex) }; vk::SubmitInfo submitInfo; - submitInfo.setCommandBuffers(cmd) + submitInfo.setCommandBuffers(frame.cmd) .setWaitSemaphores(waitSemaphores) .setWaitDstStageMask(waitStages) .setSignalSemaphores(signalSemaphores); - m_Device->GetGraphicsQueue().submit(submitInfo, m_SyncManager->GetFrameFence(frameIndex)); + m_Device->GetGraphicsQueue().submit( + submitInfo, m_SyncManager->GetFrameFence(frameSlot)); - // 6. 呈现 + // 5. 呈现 std::array swapchains = {m_Swapchain->Get()}; std::array presentWait = { m_SyncManager->GetRenderFinished(imageIndex) @@ -347,186 +430,17 @@ namespace Engine .setSwapchains(swapchains) .setWaitSemaphores(presentWait); - try - { + try { auto result = m_Device->GetPresentQueue().presentKHR(presentInfo); - if (result == vk::Result::eSuboptimalKHR) - { - m_Swapchain->Recreate(m_Window); + if (result == vk::Result::eSuboptimalKHR) { + HandleResize(); } } - catch (const vk::OutOfDateKHRError&) - { - m_Swapchain->Recreate(m_Window); + catch (const vk::OutOfDateKHRError&) { + HandleResize(); } - if (m_Window.IsKeyPressed(GLFW_KEY_F1)) - STATS.PrintComparison(); + m_CurrentFrame = (m_CurrentFrame + 1) % kMaxFramesInFlight; } - void Renderer::RecordCommands(vk::CommandBuffer cmd, u32 imageIndex) - { - vk::ClearValue clearValue; - clearValue.color = vk::ClearColorValue(std::array{0.1f, 0.1f, 0.1f, 1.0f}); - - // 颜色附件 - vk::RenderingAttachmentInfo colorAttachment; - colorAttachment.setImageView(m_Swapchain->GetImageViews()[imageIndex]) - .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) - .setLoadOp(vk::AttachmentLoadOp::eClear) - .setStoreOp(vk::AttachmentStoreOp::eStore) - .setClearValue(clearValue); - - // 深度附件 - vk::RenderingAttachmentInfo depthAttachment; - depthAttachment.setImageView(m_Swapchain->GetDepthImageView()) - .setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal) - .setLoadOp(vk::AttachmentLoadOp::eClear) - .setStoreOp(vk::AttachmentStoreOp::eDontCare) - .setClearValue(vk::ClearValue(vk::ClearDepthStencilValue(1.0f, 0))); - - vk::RenderingInfo renderingInfo; - renderingInfo.setRenderArea({{0, 0}, m_Extent}) - .setLayerCount(1) - .setColorAttachments(colorAttachment) - .setPDepthAttachment(&depthAttachment); - - cmd.beginRendering(renderingInfo); - - // 绑定着色器 - std::array stages = { - vk::ShaderStageFlagBits::eVertex, - vk::ShaderStageFlagBits::eFragment - }; - std::array shaders = {m_VertShader, m_FragShader}; - cmd.bindShadersEXT(stages, shaders); - - // 绑定描述符集 - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - m_PipelineLayout, 0, - m_UBODescriptorSet, {}); - - // ===== 动态状态 ===== - cmd.setRasterizerDiscardEnable(VK_FALSE); - - cmd.setDepthTestEnable(VK_TRUE); - cmd.setDepthWriteEnable(VK_TRUE); - cmd.setDepthCompareOp(vk::CompareOp::eLess); - cmd.setDepthBoundsTestEnable(VK_FALSE); - - cmd.setStencilTestEnable(VK_FALSE); - cmd.setDepthBiasEnable(VK_FALSE); - cmd.setPolygonModeEXT(vk::PolygonMode::eFill); - cmd.setCullMode(vk::CullModeFlagBits::eNone); - cmd.setFrontFace(vk::FrontFace::eCounterClockwise); - cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList); - cmd.setPrimitiveRestartEnable(VK_FALSE); - - // 顶点输入 - /* - vk::VertexInputBindingDescription2EXT binding; - binding.setBinding(0) - .setStride(sizeof(f32) * 5) - .setInputRate(vk::VertexInputRate::eVertex) - .setDivisor(1); - - std::array attributes; - - // location 0: position - attributes[0].setLocation(0) - .setBinding(0) - .setFormat(vk::Format::eR32G32B32Sfloat) - .setOffset(0); - - // location 1: uv - attributes[1].setLocation(1) - .setBinding(0) - .setFormat(vk::Format::eR32G32Sfloat) - .setOffset(sizeof(f32) * 3); - */ - - auto binding = Vertex::GetBindingDescription(); - auto attributes = Vertex::GetAttributeDescriptions(); - cmd.setVertexInputEXT(binding, attributes); - - cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1); - vk::SampleMask sampleMask = 0xFFFFFFFF; - cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask); - cmd.setAlphaToCoverageEnableEXT(VK_FALSE); - cmd.setAlphaToOneEnableEXT(VK_FALSE); - - vk::Viewport viewport(0.0f, 0.0f, - static_cast(m_Extent.width), - static_cast(m_Extent.height), - 0.0f, 1.0f); - cmd.setViewportWithCount(viewport); - - vk::Rect2D scissor({0, 0}, m_Extent); - cmd.setScissorWithCount(scissor); - - vk::Bool32 blendEnable = VK_FALSE; - cmd.setColorBlendEnableEXT(0, blendEnable); - - vk::ColorBlendEquationEXT blendEquation; - blendEquation.setSrcColorBlendFactor(vk::BlendFactor::eOne) - .setDstColorBlendFactor(vk::BlendFactor::eZero) - .setColorBlendOp(vk::BlendOp::eAdd) - .setSrcAlphaBlendFactor(vk::BlendFactor::eOne) - .setDstAlphaBlendFactor(vk::BlendFactor::eZero) - .setAlphaBlendOp(vk::BlendOp::eAdd); - cmd.setColorBlendEquationEXT(0, blendEquation); - - vk::ColorComponentFlags colorWriteMask = - vk::ColorComponentFlagBits::eR | - vk::ColorComponentFlagBits::eG | - vk::ColorComponentFlagBits::eB | - vk::ColorComponentFlagBits::eA; - cmd.setColorWriteMaskEXT(0, colorWriteMask); - - - // 绑定顶点缓冲并绘制 - for (auto& obj : m_Objects) { - cmd.pushConstants(m_PipelineLayout, - vk::ShaderStageFlagBits::eVertex, - 0, sizeof(glm::mat4), &obj.transform); - - for (auto& mesh : obj.model->GetMeshes()) { - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - m_PipelineLayout, 1, - mesh->GetMaterial()->GetDescriptorSet(), {}); - mesh->Draw(cmd); - } - } - - /* - cmd.bindVertexBuffers(0, m_VertexBuffer->Get(), {0}); - cmd.bindIndexBuffer(m_IndexBuffer->Get(), 0, vk::IndexType::eUint32); - // cmd.draw(3, 1, 0, 0); - cmd.drawIndexed(36, 1, 0, 0, 0); - */ - - cmd.endRendering(); - } - - void Renderer::TransitionImageLayout(vk::CommandBuffer cmd, vk::Image image, - vk::ImageLayout oldLayout, vk::ImageLayout newLayout) - { - vk::ImageMemoryBarrier2 barrier; - barrier.setSrcStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput) - .setSrcAccessMask(vk::AccessFlagBits2::eColorAttachmentWrite) - .setDstStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput) - .setDstAccessMask(vk::AccessFlagBits2::eColorAttachmentWrite) - .setOldLayout(oldLayout) - .setNewLayout(newLayout) - .setImage(image) - .setSubresourceRange({ - vk::ImageAspectFlagBits::eColor, - 0, 1, 0, 1 - }); - - vk::DependencyInfo dependencyInfo; - dependencyInfo.setImageMemoryBarriers(barrier); - - cmd.pipelineBarrier2(dependencyInfo); - } } // namespace Engine diff --git a/Engine/src/Vulkan/CommandManager.cpp b/Engine/src/Vulkan/CommandManager.cpp index 259f603..910149a 100644 --- a/Engine/src/Vulkan/CommandManager.cpp +++ b/Engine/src/Vulkan/CommandManager.cpp @@ -8,25 +8,26 @@ namespace Engine { - CommandManager::CommandManager(Device& device) + CommandManager::CommandManager(Device& device, u32 frameCount) : m_Device(device) { - // 创建命令池 + // eResetCommandBuffer:允许单独 reset 某个 buffer,而不是整池 reset。 + // 若改为 per-frame pool + 整池 reset,可在帧槽 fence 等待后一次 reset, + // 驱动开销更低——当前规模下单池多 buffer 已足够。 vk::CommandPoolCreateInfo poolInfo; poolInfo.setQueueFamilyIndex(m_Device.GetGraphicsFamily()) .setFlags(vk::CommandPoolCreateFlagBits::eResetCommandBuffer); m_Pool = m_Device.GetDevice().createCommandPool(poolInfo); - // 分配一个命令缓冲 vk::CommandBufferAllocateInfo allocInfo; allocInfo.setCommandPool(m_Pool) .setLevel(vk::CommandBufferLevel::ePrimary) - .setCommandBufferCount(1); + .setCommandBufferCount(frameCount); - m_Buffer = m_Device.GetDevice().allocateCommandBuffers(allocInfo)[0]; + m_Buffers = m_Device.GetDevice().allocateCommandBuffers(allocInfo); - ENGINE_INFO("CommandManager created"); + ENGINE_INFO("CommandManager created: {} frame buffers", frameCount); } CommandManager::~CommandManager() @@ -34,20 +35,21 @@ namespace Engine { m_Device.GetDevice().destroyCommandPool(m_Pool); } - vk::CommandBuffer CommandManager::BeginFrame() + vk::CommandBuffer CommandManager::BeginFrame(u32 frameSlot) { - m_Buffer.reset(); + auto cmd = m_Buffers[frameSlot]; + cmd.reset(); vk::CommandBufferBeginInfo beginInfo; beginInfo.setFlags(vk::CommandBufferUsageFlagBits::eOneTimeSubmit); - m_Buffer.begin(beginInfo); + cmd.begin(beginInfo); - return m_Buffer; + return cmd; } - void CommandManager::EndFrame() + void CommandManager::EndFrame(u32 frameSlot) { - m_Buffer.end(); + m_Buffers[frameSlot].end(); } } // namespace Engine diff --git a/Engine/src/Vulkan/Device.cpp b/Engine/src/Vulkan/Device.cpp index 66dc51d..4be5feb 100644 --- a/Engine/src/Vulkan/Device.cpp +++ b/Engine/src/Vulkan/Device.cpp @@ -310,6 +310,26 @@ uint32_t Device::FindMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags pro return 0; } +vk::Format Device::FindSupportedFormat(const std::vector& candidates, + vk::ImageTiling tiling, + vk::FormatFeatureFlags features) const +{ + for (auto format : candidates) { + auto props = m_PhysicalDevice.getFormatProperties(format); + if (tiling == vk::ImageTiling::eLinear && + (props.linearTilingFeatures & features) == features) { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && + (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + ENGINE_FATAL("No supported format found in candidate list"); + return candidates.empty() ? vk::Format::eUndefined : candidates.front(); +} + // ---------- 验证层检查 ---------- bool Device::CheckValidationLayerSupport() diff --git a/Engine/src/Vulkan/Swapchain.cpp b/Engine/src/Vulkan/Swapchain.cpp index 13b39fe..933bb47 100644 --- a/Engine/src/Vulkan/Swapchain.cpp +++ b/Engine/src/Vulkan/Swapchain.cpp @@ -99,14 +99,10 @@ namespace Engine m_Extent.width, m_Extent.height, m_Images.size(), vk::to_string(m_Format)); - - CreateDepthResources(); } void Swapchain::Destroy() { - DestroyDepthResources(); - const auto& vkDevice = m_Device.GetDevice(); for (auto view : m_ImageViews) @@ -181,78 +177,4 @@ namespace Engine return extent; } - - vk::Format Swapchain::FindDepthFormat() - { - // 优先 D32_SFLOAT,退回 D32_SFLOAT_S8_UINT,再退回 D24_UNORM_S8_UINT - std::array candidates = { - vk::Format::eD32Sfloat, - vk::Format::eD32SfloatS8Uint, - vk::Format::eD24UnormS8Uint - }; - - for (auto format : candidates) - { - auto props = m_Device.GetPhysicalDevice().getFormatProperties(format); - if (props.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment) - { - return format; - } - } - - ENGINE_FATAL("No depth format supported"); - return vk::Format::eD32Sfloat; - } - - void Swapchain::CreateDepthResources() - { - m_DepthFormat = FindDepthFormat(); - - vk::ImageCreateInfo imageInfo; - imageInfo.setImageType(vk::ImageType::e2D) - .setFormat(m_DepthFormat) - .setExtent({m_Extent.width, m_Extent.height, 1}) - .setMipLevels(1) - .setArrayLayers(1) - .setSamples(vk::SampleCountFlagBits::e1) - .setTiling(vk::ImageTiling::eOptimal) - .setUsage(vk::ImageUsageFlagBits::eDepthStencilAttachment) - .setSharingMode(vk::SharingMode::eExclusive) - .setInitialLayout(vk::ImageLayout::eUndefined); - - m_DepthImage = m_Device.GetDevice().createImage(imageInfo); - - auto reqs = m_Device.GetDevice().getImageMemoryRequirements(m_DepthImage); - auto alloc = m_Allocator.Allocate(reqs, vk::MemoryPropertyFlagBits::eDeviceLocal); - - m_DepthMemory = alloc.Memory; - m_Device.GetDevice().bindImageMemory(m_DepthImage, m_DepthMemory, alloc.Offset); - - vk::ImageViewCreateInfo viewInfo; - viewInfo.setImage(m_DepthImage) - .setViewType(vk::ImageViewType::e2D) - .setFormat(m_DepthFormat) - .setSubresourceRange({ - vk::ImageAspectFlagBits::eDepth, - 0, 1, 0, 1 - }); - - m_DepthImageView = m_Device.GetDevice().createImageView(viewInfo); - } - - void Swapchain::DestroyDepthResources() - { - const auto& vkDevice = m_Device.GetDevice(); - if (m_DepthImageView) - { - vkDevice.destroyImageView(m_DepthImageView); - m_DepthImageView = nullptr; - } - if (m_DepthImage) - { - vkDevice.destroyImage(m_DepthImage); - m_DepthImage = nullptr; - } - // 内存由 MemoryAllocator 统一释放 - } } // namespace Engine diff --git a/Engine/src/Vulkan/UniformBuffer.cpp b/Engine/src/Vulkan/UniformBuffer.cpp index 5324a96..e8903ba 100644 --- a/Engine/src/Vulkan/UniformBuffer.cpp +++ b/Engine/src/Vulkan/UniformBuffer.cpp @@ -9,28 +9,26 @@ namespace Engine { - UniformBuffer::UniformBuffer(Device& device, MemoryAllocator& allocator, u64 size) + UniformBuffer::UniformBuffer(Device& device, MemoryAllocator& allocator, + u64 size, u32 frameCount) : m_Size(size) { - // Uniform Buffer 需要 CPU 每帧更新,所以用 HOST_VISIBLE | HOST_COHERENT - m_Buffer = CreateScope( - device, allocator, - size, - vk::BufferUsageFlagBits::eUniformBuffer, - vk::MemoryPropertyFlagBits::eHostVisible | - vk::MemoryPropertyFlagBits::eHostCoherent); + m_Buffers.reserve(frameCount); + for (u32 i = 0; i < frameCount; ++i) { + // Uniform Buffer 需要 CPU 每帧更新,所以用 HOST_VISIBLE | HOST_COHERENT + m_Buffers.push_back(CreateScope( + device, allocator, size, + vk::BufferUsageFlagBits::eUniformBuffer, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent)); + } - ENGINE_INFO("UniformBuffer created: {} bytes", size); + ENGINE_INFO("UniformBuffer created: {} bytes x {} frames", size, frameCount); } - UniformBuffer::~UniformBuffer() + void UniformBuffer::Update(u32 frameSlot, const void* data, u64 size) { - // Buffer 析构时自动销毁 vk::Buffer,内存由 MemoryAllocator 统一释放 + m_Buffers[frameSlot]->Upload(data, size); } - void UniformBuffer::Update(const void* data, u64 size) - { - m_Buffer->Upload(data, size); - } - -} // namespace Engine \ No newline at end of file +} // namespace Engine diff --git a/Lib/entt b/Lib/entt new file mode 160000 index 0000000..85c6bba --- /dev/null +++ b/Lib/entt @@ -0,0 +1 @@ +Subproject commit 85c6bba014049b5de8fad49d25424df2f1f6a8c1