From 47dd87997711426207d1f78f1508c5b91dab5ae5 Mon Sep 17 00:00:00 2001 From: atdunbg Date: Fri, 2 Oct 2026 20:09:12 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=80=80=E5=87=BA=20SIGSEGV=20=E4=B8=8E?= =?UTF-8?q?=20VkShaderEXT=20=E6=B3=84=E6=BC=8F=E2=80=94=E2=80=94=E7=94=9F?= =?UTF-8?q?=E5=91=BD=E5=91=A8=E6=9C=9F=E4=B8=89=E5=B1=82=E7=A7=A9=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 根因:~Application 体内 Logger::Shutdown() 先于成员析构执行, Device 销毁期间 Vulkan validation 回调仍打日志 → spdlog 悬空调用 - 修复:app 置于 main 独立作用域,所有成员析构完毕后再 Shutdown - 退出序 = waitIdle(~Application 体内)→ 成员析构(shader 等 Device 子对象销毁)→ Logger::Shutdown - 顺带注释掉窗口全透明(调试期遗留) --- App/main.cpp | 93 ++++++++++++++---------- Engine/include/Engine/Core/Application.h | 16 ++-- Engine/src/Core/Application.cpp | 30 ++++++-- Engine/src/Core/Window.cpp | 2 +- 4 files changed, 89 insertions(+), 52 deletions(-) diff --git a/App/main.cpp b/App/main.cpp index 337311e..78f861c 100644 --- a/App/main.cpp +++ b/App/main.cpp @@ -14,6 +14,7 @@ // #include +#include #include // SetInputCapture(Application.h 只前置声明) #include #include @@ -67,15 +68,22 @@ namespace // Editor 注入:引擎门面 + 沙盒能力回调(环境异步切换等) InitEditor(); - SetEnv(); + // 默认环境(IBL 四件套):经资产层加载(compute 预积分,毫秒级), + // 其余环境在 Stats 面板里按需 lazy 加载后切换 + const auto env = GetAssets().LoadEnvironment(kEnvPaths[0]); + if (env.Valid()) { + GetRenderer().SetEnvironment(GetAssets().GetEnvironment(env)); + } - const auto suntemple = GetAssets().LoadModel("assets/models/demo/asset.fbx"); + /* + const auto suntemple = GetAssets().LoadModel(kTemplePath); auto entityhandle = scene.CreateEntity("SunTemple"); entityhandle.AddComponent(MeshRendererComponent{suntemple}); + */ - scene.CreateEntity(); - scene.CreateEntity(); - scene.CreateEntity(); + 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()) { @@ -84,10 +92,23 @@ namespace 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()) { @@ -177,26 +198,24 @@ namespace */ } - void OnUpdate(f32 deltaTime) override + void OnUpdate(const FrameTime& frameTime) override { - // 暂停时冻结动画时钟(帧历史验证:暂停后 Temporal Diff - // 视图应在 2 帧内整屏变黑;相机移动同样会被 diff 捕捉到) - if (!m_PauseAnim) - { - m_Time += deltaTime; - } - - // 相机(WASD/Space/Shift 移动;位姿读写实体 Transform) + // 相机(WASD/Space/Shift 移动;位姿读写实体 Transform)。 + // 编辑器相机走真实轨:暂停(timeScale=0)时仍可飞行观察 m_Camera.GetComponent() .camera.Update(GetWindow(), m_Camera.GetComponent(), - deltaTime); + frameTime.UnscaledDt()); - // 旋转动画:编辑 Transform 分量后 Compose 重算 world + // 旋转动画:编辑 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, m_Time * 60.0f + static_cast(i) * 40.0f, 0.0f)); + t.SetRotation(glm::vec3( + 0.0f, static_cast(Time::Elapsed()) * 60.0f + + static_cast(i) * 40.0f, 0.0f)); } } @@ -234,15 +253,6 @@ namespace ENGINE_INFO("App: window resized to {}x{}", event.width, event.height); } - void SetEnv(int index = 0) { - // 默认环境(IBL 四件套):经资产层加载(compute 预积分,毫秒级), - // 其余环境在 Stats 面板里按需 lazy 加载后切换 - const auto env = GetAssets().LoadEnvironment(kEnvPaths[index]); - if (env.Valid()) { - GetRenderer().SetEnvironment(GetAssets().GetEnvironment(env)); - } - } - private: // Editor 注入:门面指针 + 反向能力回调(面板 → 沙盒) void InitEditor() @@ -254,7 +264,8 @@ namespace ctx.app = this; ctx.getEnvIndex = [this] { return m_EnvIndex; }; ctx.setEnvIndex = [this](int index) { m_EnvIndex = index; }; - ctx.pauseAnim = &m_PauseAnim; + // 继承原沙盒默认:初始暂停(StatsPanel 的 Pause Simulation 开关恢复) + Time::SetTimeScale(0.0f); m_Editor->Init(ctx); // 输入归属:覆盖 EnableImGui 的全局 WantCapture* 谓词 @@ -275,8 +286,6 @@ namespace EntityHandle m_Ground{}; EntityHandle m_Gun{}; // 骨骼动画验证实体(M1911) std::vector m_Spinners; - f32 m_Time = 0.0f; - bool m_PauseAnim = true; // 冻结动画时钟(Editor 面板可开关) int m_EnvIndex = kDefaultEnv; // ---- 异步模型加载(SunTemple)---- @@ -287,17 +296,25 @@ namespace int main() { - SandboxApp app; + Logger::Initialize(); - try { - app.Run(); - } - catch (const std::exception& e) - { - ENGINE_FATAL("Exception: {}", e.what()); - return -1; - } + 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; } diff --git a/Engine/include/Engine/Core/Application.h b/Engine/include/Engine/Core/Application.h index 01f877e..5a6cb90 100644 --- a/Engine/include/Engine/Core/Application.h +++ b/Engine/include/Engine/Core/Application.h @@ -9,13 +9,14 @@ // 和事件钩子(OnEvent + 类型化事件)定义程序行为; // runtime、editor 等不同程序形态只是不同的派生类。 // -// 主循环固定为:PollEvents → OnUpdate(dt) → [NewFrame → OnRenderOverlay → +// 主循环固定为:PollEvents → OnUpdate(FrameTime) → [NewFrame → OnRenderOverlay → // BuildDrawData] → 主渲染(Renderer.Render)→ 判退出。 // UI 构建与 GPU 提交分离:OnRenderOverlay 只"声明"控件,真正的 draw data // 提交发生在主渲染管线中的 ImGui pass(Forward 之后、Present 之前, // loadOp=Load 叠加)。主渲染是框架行为,派生类不可覆盖也不会丢失; // 框架自身永远不绘制任何 ImGui 控件——UI 内容全部由派生类定义。 -// dt 由框架统一计时(含首帧与卡顿钳制),派生层无需自己管理。 +// 时间由框架单源采集:帧首 Time::BeginFrame 落定 FrameTime(含卡顿钳制), +// 派生层经 OnUpdate(frame) 或 Time 门面只读,不再自己碰 chrono。 // #ifndef HELLOVULKAN_APPLICATION_H @@ -23,6 +24,7 @@ #include #include +#include #include #include @@ -33,6 +35,7 @@ namespace Engine { class Window; class Renderer; class ImGuiLayer; + class PhysicsWorld; namespace ECS { class Scene; } namespace Assets { class AssetManager; } @@ -69,7 +72,7 @@ namespace Engine { 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; } + PhysicsWorld& GetPhysics() const { return *m_Physics; } u64 GetFrameCount() const { return m_FrameCount; } // ImGui相关 @@ -83,8 +86,9 @@ namespace Engine { // 框架装配完成(Window/Renderer 可用),派生类做初始化(建场景等) virtual void OnInit() {} - // 每帧逻辑更新(输入、动画、物理……) - virtual void OnUpdate(f32 deltaTime) {} + // 每帧逻辑更新(输入、动画、物理……)。frame 当帧只读: + // Dt() = scaled(游戏逻辑),UnscaledDt() = 真实(编辑器相机/UI) + virtual void OnUpdate(const FrameTime& frame) {} // 主渲染之后、当帧提交之前的叠加层钩子(ImGui/调试视图/编辑器 UI)。 // 主渲染由框架固定执行;这里只负责"额外的"渲染,不覆写则无叠加层。 @@ -115,12 +119,12 @@ namespace Engine { Scope m_Scene; Scope m_Renderer; Scope m_Assets; + Scope m_Physics; // Jolt 物理(Run 起循环时创建) // 注意成员序:ImGuiLayer 必须后声明(先于 Renderer/Window 析构, // 其后端销毁需要 Device 与 GLFWwindow 存活) Scope m_ImGuiLayer; bool m_Running = false; - f32 m_DeltaTime = 0.0f; u64 m_FrameCount = 0; // 帧率控制 diff --git a/Engine/src/Core/Application.cpp b/Engine/src/Core/Application.cpp index 196a44a..46e347f 100644 --- a/Engine/src/Core/Application.cpp +++ b/Engine/src/Core/Application.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -22,7 +23,6 @@ namespace Engine { Application::Application(const std::string& title, u32 width, u32 height) { - Logger::Initialize(); m_Window = CreateScope(title, width, height); @@ -56,7 +56,10 @@ namespace Engine { if (m_ImGuiLayer) m_ImGuiLayer.reset(); - Logger::Shutdown(); + // 注意:这里不能 Logger::Shutdown()——成员析构(Device 销毁等) + // 在函数体之后才执行,期间 Vulkan validation 回调仍会打日志, + // logger 先亡会导致悬空调用 SIGSEGV。Shutdown 移到 main 的 + // app 作用域结束之后(所有成员析构完毕)。 } void Application::EnableImGui() @@ -94,18 +97,31 @@ namespace Engine { OnInit(); + // 物理世界(场景实体搭建完后创建) + m_Physics = CreateScope(*m_Scene, m_Assets.get()); + // 物理调试图元 → 渲染侧(PhysicsDebugPass 每帧上传绘制) + m_Renderer->SetPhysicsDebugSource(&m_Physics->GetDebugFrame()); + while (m_Running) { const auto frameStart = std::chrono::steady_clock::now(); 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); + const f64 dt = std::chrono::duration(now - m_LastFrameTime).count(); + // 钳制卡顿(断点/窗口拖动导致的超大 dt),避免物理/动画穿模; + // 帧首落定 FrameTime(时间单源),业务当帧只读 + Time::BeginFrame(std::clamp(dt, 0.0, 0.25), m_FrameCount); + const FrameTime& frame = Time::Current(); m_LastFrameTime = now; - OnUpdate(m_DeltaTime); + // 物理步进(OnUpdate 之前,Unity FixedUpdate→Update 语义): + // 动态体姿态写回 Transform 并标脏,UpdateTransforms 统一重算; + // 吃 scaled dt → timeScale=0(编辑器暂停)时 accumulator 冻结, + // SyncScene 对账仍跑(拖静态体照常同步) + m_Physics->Step(frame.Dt()); + + OnUpdate(frame); // 延迟销毁在渲染前统一 flush,保证渲染遍历时池结构不变 m_Scene->FlushDestroyQueue(); @@ -130,7 +146,7 @@ namespace Engine { // ---- 主渲染(框架行为):RG 顺序 Forward → ImGui(loadOp=Load) → // Present,draw data 在 ImGui pass 里才真正提交 GPU ---- - m_Renderer->Render(*m_Scene, *m_Assets, m_DeltaTime); + m_Renderer->Render(*m_Scene, *m_Assets, frame.Dt()); // ---- 帧率控制:vsync 开启时 present 自带节奏;否则按目标限帧 ---- // 策略:先 sleep 到剩 ~2ms,再自旋——精度高且不烧 CPU diff --git a/Engine/src/Core/Window.cpp b/Engine/src/Core/Window.cpp index 626ac7c..66007a6 100644 --- a/Engine/src/Core/Window.cpp +++ b/Engine/src/Core/Window.cpp @@ -127,7 +127,7 @@ namespace Engine glfwTerminate(); return; } - glfwSetWindowOpacity(m_Window, 0.0f); + // glfwSetWindowOpacity(m_Window, 0.0f); ENGINE_INFO("Window created: {} ({}x{})", title, width, height);