diff --git a/App/main.cpp b/App/main.cpp index 78f861c..8af314e 100644 --- a/App/main.cpp +++ b/App/main.cpp @@ -1,320 +1,314 @@ -// -// Created by atdunbg on 2026/9/20. -// -// SandboxApp:第一个基于 Application 框架的程序。 -// -// main 的职责边界: -// - 定义 Application 派生类并运行它 -// - "程序逻辑":场景搭建(实体/组件)、逐帧更新、事件响应、 -// 异步环境加载 -// - UI 全部委托 App/editor/ 的 docking Editor(本文件不再出现 -// 任何 ImGui 控件) -// -// 窗口、渲染器、事件泵、帧计时、场景容器全部由框架管理。 -// - -#include -#include -#include // SetInputCapture(Application.h 只前置声明) -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include "Editor.h" -#include "EditorContext.h" // kEnvPaths / kDefaultEnv(与 StatsPanel 共用) - -using namespace Engine; -using namespace Engine::ECS; - -namespace -{ - constexpr const char* kTemplePath = "assets/models/SunTemple/SunTemple.fbx"; - - class SandboxApp final : public Application - { - public: - SandboxApp() - : Application("HelloVulkan", 800, 600) - { - } - - ~SandboxApp() override - { - } - - protected: - // ---- 生命周期 ---- - - void OnInit() override - { - Scene& scene = GetScene(); - Renderer& renderer = GetRenderer(); - renderer.SetDisplayMode(kDisplayViewport); - - - // ImGui 叠加层(框架只做管线桥接;UI 内容 = docking Editor) - EnableImGui(); - - // Editor 注入:引擎门面 + 沙盒能力回调(环境异步切换等) - InitEditor(); - - // 默认环境(IBL 四件套):经资产层加载(compute 预积分,毫秒级), - // 其余环境在 Stats 面板里按需 lazy 加载后切换 - const auto env = GetAssets().LoadEnvironment(kEnvPaths[0]); - if (env.Valid()) { - GetRenderer().SetEnvironment(GetAssets().GetEnvironment(env)); - } - - /* - const auto suntemple = GetAssets().LoadModel(kTemplePath); - auto entityhandle = scene.CreateEntity("SunTemple"); - entityhandle.AddComponent(MeshRendererComponent{suntemple}); - */ - - scene.CreateEntity("test entity"); - scene.CreateEntity("test entity"); - scene.CreateEntity("test entity"); - - const auto gun = GetAssets().LoadModel("assets/models/m1911/M1911.fbx"); - if (gun.Valid()) { - m_Gun = scene.CreateEntity("M1911"); - m_Gun.AddComponent(MeshRendererComponent{gun}); - m_Gun.AddComponent(); - auto& gt = m_Gun.GetComponent(); - gt.SetPosition(glm::vec3(0.0f, 1.2f, -6.0f)); - // 物理验证:Dynamic 刚体(默认 Box halfExtents 0.5), - // 出生位置悬空 → 应下落至地面静止(Transform 由物理写回) - m_Gun.AddComponent(); - } else { - ENGINE_WARN("m1911 model not loaded, animation test skipped"); - } - - // 地面:程序化纯色平面(半边长 15,中心 y=-0.5)+ 静态盒刚体 - const auto ground = GetAssets().CreatePlane(15.0f, 140, 140, 145); - m_Ground = scene.CreateEntity("Ground"); - m_Ground.GetComponent() - .SetPosition(glm::vec3(0.0f, -0.5f, 0.0f)); // dirty → 帧循环统一 Compose - m_Ground.AddComponent(MeshRendererComponent{ground}); - auto& groundRb = m_Ground.AddComponent(); - groundRb.type = RigidBodyComponent::BodyType::Static; - groundRb.boxHalfExtents = glm::vec3(15.0f, 0.1f, 15.0f); // 平面顶点烘焙 ±15,不能用默认 0.5 - - /* - const auto gun = GetAssets().LoadModel("assets/models/m1911/M1911.fbx"); - if (gun.Valid()) { - m_Gun = scene.CreateEntity("M1911"); - m_Gun.AddComponent(MeshRendererComponent{gun}); - m_Gun.AddComponent(); - auto& gt = m_Gun.GetComponent(); - gt.SetPosition(glm::vec3(0.0f, 1.2f, -6.0f)); - } else { - ENGINE_WARN("m1911 model not loaded, animation test skipped"); - } - - int countSize = 0; - if (countSize) { - // 模型资产(PBR 验证场景:backpack 为旧式贴图资产, - // 退化为电介质因子路径——albedo+normal,metallic=0/roughness=0.5) - const auto model = GetAssets().LoadModel("assets/models/backpack/backpack.obj"); - // const auto model = GetAssets().LoadModel("assets/models/pocket_watch_4k.gltf/pocket_watch_4k.gltf"); - - for (int i = 0; i < countSize; i ++) - { - for (int j = 0; j < countSize; j ++) - { - auto m = scene.CreateEntity(); - m.AddComponent(MeshRendererComponent{model}); - m.GetComponent().SetPosition(glm::vec3{(i - countSize * 0.5) * 2, 0, (j - countSize * 0.5) * 2}); - m_Spinners.push_back(m); - } - } - } - // 地面:程序化纯色平面(半边长 15,中心 y=-2) - const auto ground = GetAssets().CreatePlane(15.0f, 140, 140, 145); - m_Ground = scene.CreateEntity("Ground"); - m_Ground.GetComponent() - .SetPosition(glm::vec3(0.0f, -0.5f, 0.0f)); // dirty → 帧循环统一 Compose - m_Ground.AddComponent(MeshRendererComponent{ground}); - */ - - // 相机实体(primary = 当前渲染视角;位姿存实体 Transform) - m_Camera = scene.CreateEntity("MainCamera"); - auto& camera = m_Camera.AddComponent(); - camera.primary = true; - m_Camera.GetComponent() - .SetPosition(glm::vec3(0.0f, 12.0f, 22.0f)); - m_Camera.GetComponent() - .SetRotation(glm::vec3(-25.0f, 0.0f, 0.0f)); - - // 灯光实体(第一个方向光 = 阴影光源) - m_KeyLight = scene.CreateEntity("KeyLight"); - m_KeyLight.AddComponent( - LightComponent{ - .type = LightComponent::Type::Directional, - .direction = glm::vec3(-0.5f, -0.6f, -0.35f), - .color = glm::vec4(1.0f, 0.95f, 0.9f, 5.0f), - .castShadow = true, - }); - - /* - scene.CreateEntity("FillLight") - .AddComponent( - LightComponent{ - .type = LightComponent::Type::Directional, - .direction = glm::vec3(0.5f, -0.5f, 0.3f), - .color = glm::vec4(0.3f, 0.5f, 1.0f, 1.5f) - }); - */ - - /* - // 点光源(位置 = 实体 Transform,出生即有;无阴影,距离衰减) - auto fire = scene.CreateEntity("FirePitLight"); - fire.GetComponent().SetPosition(glm::vec3(2.0f, 1.5f, 1.0f)); - fire.AddComponent( - LightComponent{ - .type = LightComponent::Type::Point, - .color = glm::vec4(1.0f, 0.6f, 0.3f, 1.0f), // 暖橙 - .radius = 8.0f, - }); - - auto cool = scene.CreateEntity("CoolFillLight"); - cool.GetComponent().SetPosition(glm::vec3(-2.5f, 2.0f, -2.0f)); - cool.AddComponent( - LightComponent{ - .type = LightComponent::Type::Point, - .color = glm::vec4(0.3f, 0.5f, 1.0f, 1.2f), // 冷蓝 - .radius = 10.0f, - }); - */ - } - - void OnUpdate(const FrameTime& frameTime) override - { - // 相机(WASD/Space/Shift 移动;位姿读写实体 Transform)。 - // 编辑器相机走真实轨:暂停(timeScale=0)时仍可飞行观察 - m_Camera.GetComponent() - .camera.Update(GetWindow(), - m_Camera.GetComponent(), - frameTime.UnscaledDt()); - - // 旋转动画:编辑 Transform 分量后 Compose 重算 world。 - // 动画时钟 = Time::Elapsed()(scaled 累计,暂停自动冻结—— - // 帧历史验证:暂停后 Temporal Diff 视图 2 帧内整屏变黑) - for (size_t i = 0; i < m_Spinners.size(); ++i) - { - auto& t = m_Spinners[i].GetComponent(); - t.SetRotation(glm::vec3( - 0.0f, static_cast(Time::Elapsed()) * 60.0f + - static_cast(i) * 40.0f, 0.0f)); - } - } - - // ---- UI(全部委托 docking Editor;本文件零 ImGui 控件)---- - - void OnImGuiRender() override - { - m_Editor->Draw(); - } - - // ---- 事件 ---- - - void OnWindowClose(const Event& event) override - { - // 退出确认走 Editor 的模态框(ImGui 必须可见才有 UI) - SetImGuiEnabled(true); - m_Editor->RequestQuit(); - } - - void OnKeyPress(const Event& event) override - { - if (event.key == GLFW_KEY_GRAVE_ACCENT) - { - SetImGuiEnabled(!GetImGuiEnabled()); - } - if (event.key == GLFW_KEY_F1) - { - STATS.PrintComparison(); - } - } - - - void OnWindowResize(const Event& event) override - { - ENGINE_INFO("App: window resized to {}x{}", event.width, event.height); - } - - private: - // Editor 注入:门面指针 + 反向能力回调(面板 → 沙盒) - void InitEditor() - { - EditorContext ctx; - ctx.renderer = &GetRenderer(); - ctx.scene = &GetScene(); - ctx.assets = &GetAssets(); - ctx.app = this; - ctx.getEnvIndex = [this] { return m_EnvIndex; }; - ctx.setEnvIndex = [this](int index) { m_EnvIndex = index; }; - // 继承原沙盒默认:初始暂停(StatsPanel 的 Pause Simulation 开关恢复) - Time::SetTimeScale(0.0f); - m_Editor->Init(ctx); - - // 输入归属:覆盖 EnableImGui 的全局 WantCapture* 谓词 - //(全屏 dock host 让它恒真,输入全死)。改为 Editor 按帧 - // 判定——Viewport hover/focus 拥有输入(Hazel/UE 语义); - // UI 隐藏(反引号)时 Draw 不再执行,直接全放行 - GetWindow().SetInputCapture( - [this] { return GetImGuiEnabled() && m_Editor->BlockingKeyboard(); }, - [this] { return GetImGuiEnabled() && m_Editor->BlockingMouse(); }); - - } - - private: - std::unique_ptr m_Editor = std::make_unique(); - - EntityHandle m_Camera{}; // Scene::CreateEntity 之前为空 - EntityHandle m_KeyLight{}; - EntityHandle m_Ground{}; - EntityHandle m_Gun{}; // 骨骼动画验证实体(M1911) - std::vector m_Spinners; - int m_EnvIndex = kDefaultEnv; - - // ---- 异步模型加载(SunTemple)---- - AsyncTask m_TempleTask; - f32 m_TempleScale = 1.0f; // cm 尺度资产 → 米(解析后应用) - }; -} - -int main() -{ - Logger::Initialize(); - - { - SandboxApp app; - - try - { - app.Run(); - } - catch (const std::exception& e) - { - ENGINE_FATAL("Exception: {}", e.what()); - Logger::Shutdown(); - return -1; - } - } // app 析构:成员(Device/Renderer/Window…)全部销毁完毕 - - // 日志必须最后关:Device 析构期间 Vulkan validation 回调仍在 - // 打日志(销毁资源时报 VUID),logger 先亡 = 悬空调用 SIGSEGV - Logger::Shutdown(); - return 0; -} +// +// Created by atdunbg on 2026/9/20. +// +// SandboxApp:第一个基于 Application 框架的程序。 +// +// main 的职责边界: +// - 定义 Application 派生类并运行它 +// - "程序逻辑":场景搭建(实体/组件)、逐帧更新、事件响应、 +// 异步环境加载 +// - UI 全部委托 App/editor/ 的 docking Editor(本文件不再出现 +// 任何 ImGui 控件) +// +// 窗口、渲染器、事件泵、帧计时、场景容器全部由框架管理。 +// + +#include +#include +#include // SetInputCapture(Application.h 只前置声明) +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "Editor.h" +#include "EditorContext.h" // kEnvPaths / kDefaultEnv(与 StatsPanel 共用) + +using namespace Engine; +using namespace Engine::ECS; + +namespace +{ + constexpr const char* kTemplePath = "assets/models/SunTemple/SunTemple.fbx"; + + class SandboxApp final : public Application + { + public: + SandboxApp() + : Application("HelloVulkan", 1920, 1080) + { + } + + ~SandboxApp() override + { + } + + protected: + // ---- 生命周期 ---- + + void OnInit() override + { + Scene& scene = GetScene(); + Renderer& renderer = GetRenderer(); + renderer.SetDisplayMode(kDisplayViewport); + + + // ImGui 叠加层(框架只做管线桥接;UI 内容 = docking Editor) + EnableImGui(); + + // Editor 注入:引擎门面 + 沙盒能力回调(环境异步切换等) + InitEditor(); + + // 默认环境(IBL 四件套):经资产层加载(compute 预积分,毫秒级), + // 其余环境在 Stats 面板里按需 lazy 加载后切换 + const auto env = GetAssets().LoadEnvironment(kEnvPaths[0]); + if (env.Valid()) { + GetRenderer().SetEnvironment(GetAssets().GetEnvironment(env)); + } + + /* + const auto suntemple = GetAssets().LoadModel(kTemplePath); + auto entityhandle = scene.CreateEntity("SunTemple"); + entityhandle.AddComponent(MeshRendererComponent{suntemple}); + */ + + scene.CreateEntity("test entity"); + scene.CreateEntity("test entity"); + scene.CreateEntity("test entity"); + + const auto gun = GetAssets().LoadModel("assets/models/m1911/M1911.fbx"); + if (gun.Valid()) { + m_Gun = scene.CreateEntity("M1911"); + m_Gun.AddComponent(MeshRendererComponent{gun}); + m_Gun.AddComponent(); + auto& gt = m_Gun.GetComponent(); + gt.SetPosition(glm::vec3(0.0f, 1.2f, -6.0f)); + // 物理验证:Dynamic 刚体(默认 Box halfExtents 0.5), + // 出生位置悬空 → 应下落至地面静止(Transform 由物理写回) + m_Gun.AddComponent(); + } else { + ENGINE_WARN("m1911 model not loaded, animation test skipped"); + } + + // 地面:程序化纯色平面(半边长 15,中心 y=-0.5)+ 静态盒刚体 + const auto ground = GetAssets().CreatePlane(15.0f, 140, 140, 145); + m_Ground = scene.CreateEntity("Ground"); + m_Ground.GetComponent() + .SetPosition(glm::vec3(0.0f, -0.5f, 0.0f)); // dirty → 帧循环统一 Compose + m_Ground.AddComponent(MeshRendererComponent{ground}); + auto& groundRb = m_Ground.AddComponent(); + groundRb.type = RigidBodyComponent::BodyType::Static; + groundRb.boxHalfExtents = glm::vec3(15.0f, 0.1f, 15.0f); // 平面顶点烘焙 ±15,不能用默认 0.5 + + /* + const auto gun = GetAssets().LoadModel("assets/models/m1911/M1911.fbx"); + if (gun.Valid()) { + m_Gun = scene.CreateEntity("M1911"); + m_Gun.AddComponent(MeshRendererComponent{gun}); + m_Gun.AddComponent(); + auto& gt = m_Gun.GetComponent(); + gt.SetPosition(glm::vec3(0.0f, 1.2f, -6.0f)); + } else { + ENGINE_WARN("m1911 model not loaded, animation test skipped"); + } + + int countSize = 0; + if (countSize) { + // 模型资产(PBR 验证场景:backpack 为旧式贴图资产, + // 退化为电介质因子路径——albedo+normal,metallic=0/roughness=0.5) + const auto model = GetAssets().LoadModel("assets/models/backpack/backpack.obj"); + // const auto model = GetAssets().LoadModel("assets/models/pocket_watch_4k.gltf/pocket_watch_4k.gltf"); + + for (int i = 0; i < countSize; i ++) + { + for (int j = 0; j < countSize; j ++) + { + auto m = scene.CreateEntity(); + m.AddComponent(MeshRendererComponent{model}); + m.GetComponent().SetPosition(glm::vec3{(i - countSize * 0.5) * 2, 0, (j - countSize * 0.5) * 2}); + m_Spinners.push_back(m); + } + } + } + // 地面:程序化纯色平面(半边长 15,中心 y=-2) + const auto ground = GetAssets().CreatePlane(15.0f, 140, 140, 145); + m_Ground = scene.CreateEntity("Ground"); + m_Ground.GetComponent() + .SetPosition(glm::vec3(0.0f, -0.5f, 0.0f)); // dirty → 帧循环统一 Compose + m_Ground.AddComponent(MeshRendererComponent{ground}); + */ + + // 相机实体(primary = 当前渲染视角;位姿存实体 Transform) + m_Camera = scene.CreateEntity("MainCamera"); + auto& camera = m_Camera.AddComponent(); + camera.primary = true; + m_Camera.GetComponent() + .SetPosition(glm::vec3(0.0f, 12.0f, 22.0f)); + m_Camera.GetComponent() + .SetRotation(glm::vec3(-25.0f, 0.0f, 0.0f)); + + // 灯光实体(第一个方向光 = 阴影光源) + m_KeyLight = scene.CreateEntity("KeyLight"); + m_KeyLight.AddComponent( + LightComponent{ + .type = LightComponent::Type::Directional, + .direction = glm::vec3(-0.5f, -0.6f, -0.35f), + .color = glm::vec4(1.0f, 0.95f, 0.9f, 5.0f), + .castShadow = true, + }); + + /* + scene.CreateEntity("FillLight") + .AddComponent( + LightComponent{ + .type = LightComponent::Type::Directional, + .direction = glm::vec3(0.5f, -0.5f, 0.3f), + .color = glm::vec4(0.3f, 0.5f, 1.0f, 1.5f) + }); + */ + + /* + // 点光源(位置 = 实体 Transform,出生即有;无阴影,距离衰减) + auto fire = scene.CreateEntity("FirePitLight"); + fire.GetComponent().SetPosition(glm::vec3(2.0f, 1.5f, 1.0f)); + fire.AddComponent( + LightComponent{ + .type = LightComponent::Type::Point, + .color = glm::vec4(1.0f, 0.6f, 0.3f, 1.0f), // 暖橙 + .radius = 8.0f, + }); + + auto cool = scene.CreateEntity("CoolFillLight"); + cool.GetComponent().SetPosition(glm::vec3(-2.5f, 2.0f, -2.0f)); + cool.AddComponent( + LightComponent{ + .type = LightComponent::Type::Point, + .color = glm::vec4(0.3f, 0.5f, 1.0f, 1.2f), // 冷蓝 + .radius = 10.0f, + }); + */ + } + + void OnUpdate(const FrameTime& frameTime) override + { + // 相机(WASD/Space/Shift 移动;位姿读写实体 Transform)。 + // 编辑器相机走真实轨:暂停(timeScale=0)时仍可飞行观察 + m_Camera.GetComponent() + .camera.Update(GetWindow(), + m_Camera.GetComponent(), + frameTime.UnscaledDt()); + + // 旋转动画:编辑 Transform 分量后 Compose 重算 world。 + // 动画时钟 = Time::Elapsed()(scaled 累计,暂停自动冻结—— + // 帧历史验证:暂停后 Temporal Diff 视图 2 帧内整屏变黑) + for (size_t i = 0; i < m_Spinners.size(); ++i) + { + auto& t = m_Spinners[i].GetComponent(); + t.SetRotation(glm::vec3( + 0.0f, static_cast(Time::Elapsed()) * 60.0f + + static_cast(i) * 40.0f, 0.0f)); + } + } + + // ---- UI(全部委托 docking Editor;本文件零 ImGui 控件)---- + + void OnImGuiRender() override + { + m_Editor->Draw(); + } + + // ---- 事件 ---- + + void OnWindowClose(const Event& event) override + { + // 退出确认走 Editor 的模态框(ImGui 必须可见才有 UI) + SetImGuiEnabled(true); + m_Editor->RequestQuit(); + } + + void OnKeyPress(const Event& event) override + { + if (event.key == GLFW_KEY_GRAVE_ACCENT) + { + SetImGuiEnabled(!GetImGuiEnabled()); + } + if (event.key == GLFW_KEY_F1) + { + STATS.PrintComparison(); + } + } + + + void OnWindowResize(const Event& event) override + { + ENGINE_INFO("App: window resized to {}x{}", event.width, event.height); + } + + private: + // Editor 注入:门面指针 + 反向能力回调(面板 → 沙盒) + void InitEditor() + { + EditorContext ctx; + ctx.renderer = &GetRenderer(); + ctx.scene = &GetScene(); + ctx.assets = &GetAssets(); + ctx.app = this; + ctx.getEnvIndex = [this] { return m_EnvIndex; }; + ctx.setEnvIndex = [this](int index) { m_EnvIndex = index; }; + // 继承原沙盒默认:初始暂停(StatsPanel 的 Pause Simulation 开关恢复) + Time::SetTimeScale(0.0f); + m_Editor->Init(ctx); + + // 输入归属:覆盖 EnableImGui 的全局 WantCapture* 谓词 + //(全屏 dock host 让它恒真,输入全死)。改为 Editor 按帧 + // 判定——Viewport hover/focus 拥有输入(Hazel/UE 语义); + // UI 隐藏(反引号)时 Draw 不再执行,直接全放行 + GetWindow().SetInputCapture( + [this] { return GetImGuiEnabled() && m_Editor->BlockingKeyboard(); }, + [this] { return GetImGuiEnabled() && m_Editor->BlockingMouse(); }); + + } + + private: + std::unique_ptr m_Editor = std::make_unique(); + + EntityHandle m_Camera{}; // Scene::CreateEntity 之前为空 + EntityHandle m_KeyLight{}; + EntityHandle m_Ground{}; + EntityHandle m_Gun{}; // 骨骼动画验证实体(M1911) + std::vector m_Spinners; + int m_EnvIndex = kDefaultEnv; + }; +} + +int main() +{ + Logger::Initialize(); + + { + SandboxApp app; + + try + { + app.Run(); + } + catch (const std::exception& e) + { + ENGINE_FATAL("Exception: {}", e.what()); + Logger::Shutdown(); + return -1; + } + } // app 析构:成员(Device/Renderer/Window…)全部销毁完毕 + + Logger::Shutdown(); + return 0; +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 9579b06..ec56aa7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,69 +1,77 @@ -cmake_minimum_required(VERSION 3.20) -project(HelloVulkan CXX) - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -set(CMAKE_BINARY_DIR ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) - -# 第三方库 -add_subdirectory(Lib/glfw) -add_subdirectory(Lib/glm) -add_subdirectory(Lib/spdlog) -add_subdirectory(Lib/stb) -# meshoptimizer(submodule,LOD 网格简化;demo/gltfpack 默认关闭,install 不需要) -set(MESHOPT_INSTALL OFF CACHE BOOL "" FORCE) -add_subdirectory(Lib/meshoptimizer) - -# ---- Jolt Physics(submodule v5.6,刚体/软体/角色/载具模拟)---- -# 顶层 CMake 在 Build/ 子目录;只编 jolt 静态库,示例/测试目标默认全关 -set(JPH_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) -set(INTERPROCEDURAL_OPTIMIZATION OFF CACHE BOOL "" FORCE) # LTO 拖慢开发迭代 -set(OVERRIDE_CXX_FLAGS OFF CACHE BOOL "" FORCE) # 不动全局优化参数 -set(FLOATING_POINT_EXCEPTIONS_ENABLED OFF CACHE BOOL "" FORCE) # NaN 进物理不炸,仅报警 -# JPH_USE_CPU_COMPUTE 默认 ON,需要 DXC 把 Shaders/*.hlsl 编成 .spv(本机无 -# 工具链);我们不使用 Jolt 的 GPU compute 路径,连同后端 shader 一并关掉 -set(JPH_USE_CPU_COMPUTE OFF CACHE BOOL "" FORCE) -set(JPH_USE_DX12 OFF CACHE BOOL "" FORCE) -set(JPH_USE_VK OFF CACHE BOOL "" FORCE) -set(JPH_USE_MTL OFF CACHE BOOL "" FORCE) -add_subdirectory(Lib/JoltPhysics/Build) - -# ---- Assimp(vendored submodule,模型导入;只编译需要的 importer) ---- -set(BUILD_SHARED_LIBS OFF) # 静态链接 -set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "" FORCE) -set(ASSIMP_BUILD_ASSIMP_TOOLS OFF CACHE BOOL "" FORCE) # 不编 assimp_cmd 等工具 -set(ASSIMP_INSTALL OFF CACHE BOOL "" FORCE) -set(ASSIMP_WARNINGS_AS_ERRORS OFF CACHE BOOL "" FORCE) # assimp 源码警告多 -set(ASSIMP_INJECT_DEBUG_POSTFIX OFF CACHE BOOL "" FORCE) -set(ASSIMP_NO_EXPORT ON CACHE BOOL "" FORCE) # 彻底移除导出代码 -set(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT OFF CACHE BOOL "" FORCE) # 全关,按需开 -set(ASSIMP_BUILD_DRACO OFF CACHE BOOL "" FORCE) -# 用 assimp 自带 contrib zlib(而非系统 zlib)。原因:系统 zlib 会使 assimp 走 -# pkg-config 找系统 minizip,而 Arch 的 minizip 头文件在 /usr/include/minizip/ -# 子目录,与 assimp 的 #include 不符 → ZipArchiveIOSystem 编译失败。 -# 自带 zlib 后 ASSIMP_BUILD_MINIZIP=1,跳过 pkg-config,直接用 contrib/unzip。 -set(ASSIMP_BUILD_ZLIB ON CACHE BOOL "" FORCE) -# 清除首次 configure 残留的 UNZIP_FOUND 缓存(INTERNAL=1 会持续生效, -# 使 assimp 误判系统 minizip 可用而不把 contrib/unzip 加进 include 路径)。 -set(UNZIP_FOUND OFF CACHE INTERNAL "" FORCE) -# 需要的导入格式(按需增减,每关一个省编译时间): -set(ASSIMP_BUILD_OBJ_IMPORTER ON CACHE BOOL "" FORCE) -set(ASSIMP_BUILD_GLTF_IMPORTER ON CACHE BOOL "" FORCE) # glTF1+2 同一选项 -set(ASSIMP_BUILD_FBX_IMPORTER ON CACHE BOOL "" FORCE) -set(ASSIMP_BUILD_STL_IMPORTER ON CACHE BOOL "" FORCE) -set(ASSIMP_BUILD_PLY_IMPORTER ON CACHE BOOL "" FORCE) -add_subdirectory(Lib/assimp) - - - -# Vulkan -find_package(Vulkan REQUIRED) - -# Engine 和 App -add_subdirectory(Engine) -add_subdirectory(App) - -file(GLOB ASSETS "assets") -file(COPY ${ASSETS} DESTINATION ${CMAKE_BINARY_DIR}) +cmake_minimum_required(VERSION 3.20) +project(HelloVulkan CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_BINARY_DIR ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + +# 第三方库 +add_subdirectory(Lib/glfw) +add_subdirectory(Lib/glm) +add_subdirectory(Lib/spdlog) +add_subdirectory(Lib/stb) +# meshoptimizer(submodule,LOD 网格简化;demo/gltfpack 默认关闭,install 不需要) +set(MESHOPT_INSTALL OFF CACHE BOOL "" FORCE) +add_subdirectory(Lib/meshoptimizer) + +# ---- Jolt Physics(submodule v5.6,刚体/软体/角色/载具模拟)---- +if(MSVC) + set(USE_STATIC_MSVC_RUNTIME_LIBRARY OFF CACHE BOOL "" FORCE) +endif () +# 顶层 CMake 在 Build/ 子目录;只编 jolt 静态库,示例/测试目标默认全关 +set(JPH_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(INTERPROCEDURAL_OPTIMIZATION OFF CACHE BOOL "" FORCE) # LTO 拖慢开发迭代 +set(OVERRIDE_CXX_FLAGS OFF CACHE BOOL "" FORCE) # 不动全局优化参数 +set(FLOATING_POINT_EXCEPTIONS_ENABLED OFF CACHE BOOL "" FORCE) # NaN 进物理不炸,仅报警 +# JPH_USE_CPU_COMPUTE 默认 ON,需要 DXC 把 Shaders/*.hlsl 编成 .spv(本机无 +# 工具链);我们不使用 Jolt 的 GPU compute 路径,连同后端 shader 一并关掉 +set(JPH_USE_CPU_COMPUTE OFF CACHE BOOL "" FORCE) +set(JPH_USE_DX12 OFF CACHE BOOL "" FORCE) +set(JPH_USE_VK OFF CACHE BOOL "" FORCE) +set(JPH_USE_MTL OFF CACHE BOOL "" FORCE) +add_subdirectory(Lib/JoltPhysics/Build) + +# ---- Assimp(vendored submodule,模型导入;只编译需要的 importer) ---- +set(BUILD_SHARED_LIBS OFF) # 静态链接 +set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(ASSIMP_BUILD_ASSIMP_TOOLS OFF CACHE BOOL "" FORCE) # 不编 assimp_cmd 等工具 +set(ASSIMP_INSTALL OFF CACHE BOOL "" FORCE) +set(ASSIMP_WARNINGS_AS_ERRORS OFF CACHE BOOL "" FORCE) # assimp 源码警告多 +set(ASSIMP_INJECT_DEBUG_POSTFIX OFF CACHE BOOL "" FORCE) +set(ASSIMP_NO_EXPORT ON CACHE BOOL "" FORCE) # 彻底移除导出代码 +set(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT OFF CACHE BOOL "" FORCE) # 全关,按需开 +set(ASSIMP_BUILD_DRACO OFF CACHE BOOL "" FORCE) +# 用 assimp 自带 contrib zlib(而非系统 zlib)。原因:系统 zlib 会使 assimp 走 +# pkg-config 找系统 minizip,而 Arch 的 minizip 头文件在 /usr/include/minizip/ +# 子目录,与 assimp 的 #include 不符 → ZipArchiveIOSystem 编译失败。 +# 自带 zlib 后 ASSIMP_BUILD_MINIZIP=1,跳过 pkg-config,直接用 contrib/unzip。 +set(ASSIMP_BUILD_ZLIB ON CACHE BOOL "" FORCE) +# 清除首次 configure 残留的 UNZIP_FOUND 缓存(INTERNAL=1 会持续生效, +# 使 assimp 误判系统 minizip 可用而不把 contrib/unzip 加进 include 路径)。 +set(UNZIP_FOUND OFF CACHE INTERNAL "" FORCE) +# 需要的导入格式(按需增减,每关一个省编译时间): +set(ASSIMP_BUILD_OBJ_IMPORTER ON CACHE BOOL "" FORCE) +set(ASSIMP_BUILD_GLTF_IMPORTER ON CACHE BOOL "" FORCE) # glTF1+2 同一选项 +set(ASSIMP_BUILD_FBX_IMPORTER ON CACHE BOOL "" FORCE) +set(ASSIMP_BUILD_STL_IMPORTER ON CACHE BOOL "" FORCE) +set(ASSIMP_BUILD_PLY_IMPORTER ON CACHE BOOL "" FORCE) +add_subdirectory(Lib/assimp) + + + +# Vulkan +find_package(Vulkan REQUIRED) + + +if(MINGW) + set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -Wl,-Bdynamic -lucrt") +endif() + +# Engine 和 App +add_subdirectory(Engine) +add_subdirectory(App) + +file(GLOB ASSETS "assets") +file(COPY ${ASSETS} DESTINATION ${CMAKE_BINARY_DIR}) diff --git a/Engine/CMakeLists.txt b/Engine/CMakeLists.txt index 6c6ee2d..7d08390 100644 --- a/Engine/CMakeLists.txt +++ b/Engine/CMakeLists.txt @@ -1,60 +1,69 @@ -# ============ 第三方库目标(Engine 的依赖) ============ - -# ImGui(docking 分支,vendored submodule) -set(IMGUI_DIR ${CMAKE_SOURCE_DIR}/Lib/imgui) -add_library(imgui STATIC - ${IMGUI_DIR}/imgui.cpp - ${IMGUI_DIR}/imgui_draw.cpp - ${IMGUI_DIR}/imgui_tables.cpp - ${IMGUI_DIR}/imgui_widgets.cpp - # ${IMGUI_DIR}/imgui_demo.cpp # ShowDemoWindow 的实现(调试期保留) - ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp - ${IMGUI_DIR}/backends/imgui_impl_vulkan.cpp -) -target_include_directories(imgui PUBLIC - ${IMGUI_DIR} - ${IMGUI_DIR}/backends) -# 两个 backend 头文件分别依赖 GLFW / Vulkan 头 -target_link_libraries(imgui PUBLIC glfw Vulkan::Vulkan) - -# Tracy(CPU/GPU 帧分析) -add_library(tracy STATIC ${CMAKE_SOURCE_DIR}/Lib/tracy/public/TracyClient.cpp) -target_include_directories(tracy PUBLIC ${CMAKE_SOURCE_DIR}/Lib/tracy/public) -target_compile_definitions(tracy PUBLIC - TRACY_ENABLE - # 按需剖析:GPU context 宣告(GpuNewContext)与连接绑定——GUI 永远晚于 - # 程序启动连接,没有这个宏宣告消息被丢弃,GPU 时间线永远空白 - TRACY_ON_DEMAND) -find_package(Threads REQUIRED) -target_link_libraries(tracy PUBLIC Threads::Threads) - -# ============ Engine ============ - -file(GLOB_RECURSE ENGINE_SOURCES CONFIGURE_DEPENDS src/*.cpp) -add_library(Engine STATIC ${ENGINE_SOURCES}) - -# PhysicsDebugDraw 派生 Jolt::DebugRendererSimple:Jolt 以 -fno-rtti 编译 -#(有 key function 的基类 typeinfo 符号不存在),该 TU 必须同步关闭 RTTI, -# 否则派生类 typeinfo 引用悬空符号(链接期 undefined reference) -set_source_files_properties(src/Physics/PhysicsDebugDraw.cpp PROPERTIES - COMPILE_OPTIONS "-fno-rtti") - -target_include_directories(Engine PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/include - # header-only 库:纯 include 路径 - ${CMAKE_SOURCE_DIR}/Lib/entt/src - ${CMAKE_SOURCE_DIR}/Lib/VulkanMemoryAllocator/include) - -target_link_libraries(Engine PUBLIC - glfw - glm::glm - Vulkan::Vulkan - spdlog::spdlog - stb - assimp::assimp - meshoptimizer - Jolt::Jolt - imgui - tracy) - -target_compile_definitions(Engine PRIVATE VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1) +# ============ 第三方库目标(Engine 的依赖) ============ + +# ImGui(docking 分支,vendored submodule) +set(IMGUI_DIR ${CMAKE_SOURCE_DIR}/Lib/imgui) +add_library(imgui STATIC + ${IMGUI_DIR}/imgui.cpp + ${IMGUI_DIR}/imgui_draw.cpp + ${IMGUI_DIR}/imgui_tables.cpp + ${IMGUI_DIR}/imgui_widgets.cpp + # ${IMGUI_DIR}/imgui_demo.cpp # ShowDemoWindow 的实现(调试期保留) + ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp + ${IMGUI_DIR}/backends/imgui_impl_vulkan.cpp +) +target_include_directories(imgui PUBLIC + ${IMGUI_DIR} + ${IMGUI_DIR}/backends) +# 两个 backend 头文件分别依赖 GLFW / Vulkan 头 +target_link_libraries(imgui PUBLIC glfw Vulkan::Vulkan) + +# Tracy(CPU/GPU 帧分析) +add_library(tracy STATIC ${CMAKE_SOURCE_DIR}/Lib/tracy/public/TracyClient.cpp) +target_include_directories(tracy PUBLIC ${CMAKE_SOURCE_DIR}/Lib/tracy/public) +target_compile_definitions(tracy PUBLIC + TRACY_ENABLE + # 按需剖析:GPU context 宣告(GpuNewContext)与连接绑定——GUI 永远晚于 + # 程序启动连接,没有这个宏宣告消息被丢弃,GPU 时间线永远空白 + TRACY_ON_DEMAND) +find_package(Threads REQUIRED) +if(WIN32) + target_link_libraries(tracy PUBLIC + Threads::Threads + ws2_32 + dbghelp + secur32) +else() + target_link_libraries(tracy PUBLIC Threads::Threads) +endif() + + +# ============ Engine ============ + +file(GLOB_RECURSE ENGINE_SOURCES CONFIGURE_DEPENDS src/*.cpp) +add_library(Engine STATIC ${ENGINE_SOURCES}) + +# PhysicsDebugDraw 派生 Jolt::DebugRendererSimple:Jolt 以 -fno-rtti 编译 +#(有 key function 的基类 typeinfo 符号不存在),该 TU 必须同步关闭 RTTI, +# 否则派生类 typeinfo 引用悬空符号(链接期 undefined reference) +set_source_files_properties(src/Physics/PhysicsDebugDraw.cpp PROPERTIES + COMPILE_OPTIONS "-fno-rtti") + +target_include_directories(Engine PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + # header-only 库:纯 include 路径 + ${CMAKE_SOURCE_DIR}/Lib/entt/src + ${CMAKE_SOURCE_DIR}/Lib/VulkanMemoryAllocator/include) + +target_link_libraries(Engine PUBLIC + glfw + glm::glm + Vulkan::Vulkan + spdlog::spdlog + stb + assimp::assimp + meshoptimizer + Jolt::Jolt + imgui + tracy) + +target_compile_definitions(Engine PRIVATE VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1) diff --git a/Engine/src/ImGui/ImGuiLayer.cpp b/Engine/src/ImGui/ImGuiLayer.cpp index 62beb84..2cb9ecb 100644 --- a/Engine/src/ImGui/ImGuiLayer.cpp +++ b/Engine/src/ImGui/ImGuiLayer.cpp @@ -1,154 +1,154 @@ -// -// Created by atdunbg on 2026/9/22. -// - -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace Engine { - - namespace { - void CheckVkResult(VkResult err) - { - if (err != VK_SUCCESS) { - ENGINE_ERROR("ImGui Vulkan backend error: VkResult={}", static_cast(err)); - } - } - } - - ImGuiLayer::ImGuiLayer(Window& window, Renderer& renderer) - : m_Device(renderer.GetDevice()) - { - Device& device = renderer.GetDevice(); - - // 1. 描述符池(ImGui 自用:字体贴图 + 用户贴图) - std::array poolSizes = {{ - { vk::DescriptorType::eSampler, 1000 }, - { vk::DescriptorType::eCombinedImageSampler, 1000 }, - { vk::DescriptorType::eSampledImage, 1000 }, - { vk::DescriptorType::eStorageImage, 1000 }, - { vk::DescriptorType::eUniformTexelBuffer, 1000 }, - { vk::DescriptorType::eStorageTexelBuffer, 1000 }, - { vk::DescriptorType::eUniformBuffer, 1000 }, - { vk::DescriptorType::eStorageBuffer, 1000 }, - { vk::DescriptorType::eUniformBufferDynamic, 1000 }, - { vk::DescriptorType::eStorageBufferDynamic, 1000 }, - { vk::DescriptorType::eInputAttachment, 1000 }, - }}; - vk::DescriptorPoolCreateInfo poolInfo; - poolInfo.setFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet) - .setMaxSets(1000) - .setPoolSizes(poolSizes); - m_Pool = device.GetDevice().createDescriptorPool(poolInfo); - - // 2. ImGui 上下文 + 平台/渲染后端 - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGui::StyleColorsDark(); // 深色主题(ImGui 默认是灰白 Classic) - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // docking 分支 - io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // 多视口 - - ImGui_ImplGlfw_InitForVulkan(window.GetHandle(), true); - - ImGui_ImplVulkan_InitInfo info = {}; - info.ApiVersion = VK_API_VERSION_1_4; - info.Instance = device.GetInstance(); - info.PhysicalDevice = device.GetPhysicalDevice(); - info.Device = device.GetDevice(); - info.QueueFamily = device.GetGraphicsFamily(); - info.Queue = device.GetGraphicsQueue(); - info.DescriptorPool = m_Pool; - info.MinImageCount = 2; // kMaxFramesInFlight - info.ImageCount = renderer.GetSwapchainImageCount(); - info.CheckVkResultFn = CheckVkResult; - - // 与引擎共存的关键:不用 RenderPass,走 dynamic rendering, - // 颜色格式 = swapchain 格式(引擎无 vk::Pipeline,全动态状态) - info.UseDynamicRendering = true; - const vk::Format colorFormat = renderer.GetSwapchainFormat(); - - VkPipelineRenderingCreateInfoKHR pri{}; - pri.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR; - pri.colorAttachmentCount = 1; - pri.pColorAttachmentFormats = - reinterpret_cast(&colorFormat); - info.PipelineInfoMain.PipelineRenderingCreateInfo = pri; - // 多视口子窗口同格式 - info.PipelineInfoForViewports.PipelineRenderingCreateInfo = pri; - - ImGui_ImplVulkan_Init(&info); - - ENGINE_INFO("ImGuiLayer initialized (docking + viewports, dynamic rendering)"); - } - - ImGuiLayer::~ImGuiLayer() - { - // 后端销毁自己的管线/字体资源前先等 GPU 空闲(层先于 Renderer 析构) - m_Device.GetDevice().waitIdle(); - - // 场景纹理描述符先归还(后端 shutdown 前必须完成) - for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { - UpdateSceneTexture(slot, vk::ImageView{}); - } - - ImGui_ImplVulkan_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); - - m_Device.GetDevice().destroyDescriptorPool(m_Pool); - - ENGINE_INFO("ImGuiLayer shut down"); - } - - void ImGuiLayer::UpdateSceneTexture(u32 slot, vk::ImageView view) - { - if (slot >= kMaxFramesInFlight) return; - - // 旧描述符先归还(池带 eFreeDescriptorSet;resize 重建时 GPU 已 - // waitIdle,无在飞引用) - if (m_SceneTextures[slot]) { - ImGui_ImplVulkan_RemoveTexture( - (VkDescriptorSet)(intptr_t)m_SceneTextures[slot]); - m_SceneTextures[slot] = ImTextureID_Invalid; - } - if (view) { - // 后端自有采样器(linear):只需提供 view 与采样期 layout—— - // RG 已保证 ImGuiPass 执行期处于 eShaderReadOnlyOptimal - m_SceneTextures[slot] = (ImTextureID)(intptr_t) - ImGui_ImplVulkan_AddTexture( - (VkImageView)view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - } - } - - void ImGuiLayer::NewFrame() - { - ImGui_ImplVulkan_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - } - - void ImGuiLayer::BuildDrawData() - { - ImGui::Render(); - } - - void ImGuiLayer::RenderDrawData(vk::CommandBuffer cmd) - { - if (!m_Enabled) return; - - ImDrawData* drawData = ImGui::GetDrawData(); - if (drawData && drawData->CmdListsCount > 0) { - ImGui_ImplVulkan_RenderDrawData(drawData, cmd); - } - } - -} // namespace Engine +// +// Created by atdunbg on 2026/9/22. +// + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Engine { + + namespace { + void CheckVkResult(VkResult err) + { + if (err != VK_SUCCESS) { + ENGINE_ERROR("ImGui Vulkan backend error: VkResult={}", static_cast(err)); + } + } + } + + ImGuiLayer::ImGuiLayer(Window& window, Renderer& renderer) + : m_Device(renderer.GetDevice()) + { + Device& device = renderer.GetDevice(); + + // 1. 描述符池(ImGui 自用:字体贴图 + 用户贴图) + std::array poolSizes = {{ + { vk::DescriptorType::eSampler, 1000 }, + { vk::DescriptorType::eCombinedImageSampler, 1000 }, + { vk::DescriptorType::eSampledImage, 1000 }, + { vk::DescriptorType::eStorageImage, 1000 }, + { vk::DescriptorType::eUniformTexelBuffer, 1000 }, + { vk::DescriptorType::eStorageTexelBuffer, 1000 }, + { vk::DescriptorType::eUniformBuffer, 1000 }, + { vk::DescriptorType::eStorageBuffer, 1000 }, + { vk::DescriptorType::eUniformBufferDynamic, 1000 }, + { vk::DescriptorType::eStorageBufferDynamic, 1000 }, + { vk::DescriptorType::eInputAttachment, 1000 }, + }}; + vk::DescriptorPoolCreateInfo poolInfo; + poolInfo.setFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet) + .setMaxSets(1000) + .setPoolSizes(poolSizes); + m_Pool = device.GetDevice().createDescriptorPool(poolInfo); + + // 2. ImGui 上下文 + 平台/渲染后端 + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGui::StyleColorsDark(); // 深色主题(ImGui 默认是灰白 Classic) + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // docking 分支 + // io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // 多视口 + + ImGui_ImplGlfw_InitForVulkan(window.GetHandle(), true); + + ImGui_ImplVulkan_InitInfo info = {}; + info.ApiVersion = VK_API_VERSION_1_4; + info.Instance = device.GetInstance(); + info.PhysicalDevice = device.GetPhysicalDevice(); + info.Device = device.GetDevice(); + info.QueueFamily = device.GetGraphicsFamily(); + info.Queue = device.GetGraphicsQueue(); + info.DescriptorPool = m_Pool; + info.MinImageCount = 2; // kMaxFramesInFlight + info.ImageCount = renderer.GetSwapchainImageCount(); + info.CheckVkResultFn = CheckVkResult; + + // 与引擎共存的关键:不用 RenderPass,走 dynamic rendering, + // 颜色格式 = swapchain 格式(引擎无 vk::Pipeline,全动态状态) + info.UseDynamicRendering = true; + const vk::Format colorFormat = renderer.GetSwapchainFormat(); + + VkPipelineRenderingCreateInfoKHR pri{}; + pri.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR; + pri.colorAttachmentCount = 1; + pri.pColorAttachmentFormats = + reinterpret_cast(&colorFormat); + info.PipelineInfoMain.PipelineRenderingCreateInfo = pri; + // 多视口子窗口同格式 + info.PipelineInfoForViewports.PipelineRenderingCreateInfo = pri; + + ImGui_ImplVulkan_Init(&info); + + ENGINE_INFO("ImGuiLayer initialized (docking + viewports, dynamic rendering)"); + } + + ImGuiLayer::~ImGuiLayer() + { + // 后端销毁自己的管线/字体资源前先等 GPU 空闲(层先于 Renderer 析构) + m_Device.GetDevice().waitIdle(); + + // 场景纹理描述符先归还(后端 shutdown 前必须完成) + for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { + UpdateSceneTexture(slot, vk::ImageView{}); + } + + ImGui_ImplVulkan_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + m_Device.GetDevice().destroyDescriptorPool(m_Pool); + + ENGINE_INFO("ImGuiLayer shut down"); + } + + void ImGuiLayer::UpdateSceneTexture(u32 slot, vk::ImageView view) + { + if (slot >= kMaxFramesInFlight) return; + + // 旧描述符先归还(池带 eFreeDescriptorSet;resize 重建时 GPU 已 + // waitIdle,无在飞引用) + if (m_SceneTextures[slot]) { + ImGui_ImplVulkan_RemoveTexture( + (VkDescriptorSet)(intptr_t)m_SceneTextures[slot]); + m_SceneTextures[slot] = ImTextureID_Invalid; + } + if (view) { + // 后端自有采样器(linear):只需提供 view 与采样期 layout—— + // RG 已保证 ImGuiPass 执行期处于 eShaderReadOnlyOptimal + m_SceneTextures[slot] = (ImTextureID)(intptr_t) + ImGui_ImplVulkan_AddTexture( + (VkImageView)view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } + } + + void ImGuiLayer::NewFrame() + { + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + } + + void ImGuiLayer::BuildDrawData() + { + ImGui::Render(); + } + + void ImGuiLayer::RenderDrawData(vk::CommandBuffer cmd) + { + if (!m_Enabled) return; + + ImDrawData* drawData = ImGui::GetDrawData(); + if (drawData && drawData->CmdListsCount > 0) { + ImGui_ImplVulkan_RenderDrawData(drawData, cmd); + } + } + +} // namespace Engine