fix: 退出 SIGSEGV 与 VkShaderEXT 泄漏——生命周期三层秩序

- 根因:~Application 体内 Logger::Shutdown() 先于成员析构执行,
  Device 销毁期间 Vulkan validation 回调仍打日志 → spdlog 悬空调用
- 修复:app 置于 main 独立作用域,所有成员析构完毕后再 Shutdown
- 退出序 = waitIdle(~Application 体内)→ 成员析构(shader 等
  Device 子对象销毁)→ Logger::Shutdown
- 顺带注释掉窗口全透明(调试期遗留)
This commit is contained in:
2026-10-02 20:09:12 +08:00
parent 1449bea86d
commit 47dd879977
4 changed files with 89 additions and 52 deletions
+10 -6
View File
@@ -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 <Engine/Core/Types.h>
#include <Engine/Core/Event.h>
#include <Engine/Core/Time.h>
#include <chrono>
#include <string>
@@ -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<ECS::Scene> m_Scene;
Scope<Renderer> m_Renderer;
Scope<Assets::AssetManager> m_Assets;
Scope<PhysicsWorld> m_Physics; // Jolt 物理(Run 起循环时创建)
// 注意成员序:ImGuiLayer 必须后声明(先于 Renderer/Window 析构,
// 其后端销毁需要 Device 与 GLFWwindow 存活)
Scope<ImGuiLayer> m_ImGuiLayer;
bool m_Running = false;
f32 m_DeltaTime = 0.0f;
u64 m_FrameCount = 0;
// 帧率控制
+23 -7
View File
@@ -6,6 +6,7 @@
#include <Engine/Core/Window.h>
#include <Engine/Core/Log.h>
#include <Engine/ECS/Scene.h>
#include <Engine/Physics/PhysicsWorld.h>
#include <Engine/Renderer/Renderer.h>
#include <Engine/Assets/AssetManager.h>
#include <Engine/Vulkan/Device.h>
@@ -22,7 +23,6 @@ namespace Engine {
Application::Application(const std::string& title, u32 width, u32 height)
{
Logger::Initialize();
m_Window = CreateScope<Window>(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<PhysicsWorld>(*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<f32>(now - m_LastFrameTime).count();
// 钳制卡顿(断点/窗口拖动导致的超大 dt),避免物理/动画穿模
m_DeltaTime = std::clamp(dt, 0.0f, 0.25f);
const f64 dt = std::chrono::duration<f64>(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
+1 -1
View File
@@ -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);