// // Created by atdunbg on 2026/9/22. // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Engine { Application::Application(const std::string& title, u32 width, u32 height) { m_Window = CreateScope(title, width, height); // 事件通道:GLFW 回调 → 本类 DispatchEvent → 派生类钩子 m_Window->SetEventCallback( [this](const Event& e) { DispatchEvent(e); }); // 场景(ECS 世界)+ 渲染器 m_Scene = CreateScope(); m_Renderer = CreateScope(*m_Window); // 资产层:上传服务由渲染层提供(IAssetUploader 实现), // 资产层不感知 Vulkan 具体类。 // 声明在 Renderer 之后 → 析构先于 Renderer(它引用其子模块) m_Assets = CreateScope(m_Renderer->GetAssetUploader()); // 统一输入门面装配:脚本/游戏逻辑经 Input 查询键鼠,不直接 // 依赖 Window 后端(GLFW 解耦) Input::Init(*m_Window); // GameFlow →Stopped:脚本终局(Unity 退出 Play 语义——实例与 // 成员状态不跨局存活,下次 Play 由对账重建)。注册在组合根最 // 早处:先于编辑器 Init 的监听执行,EndPlay 跑在场景回滚之前, // OnDestroy 见到的仍是上局实体。m_Scripts 在 Run 里才创建,而 // →Stopped 转换最早发生于 PIE Stop(届时已存在),空指针兜底 m_GameFlow.AddListener([this](GameFlowState, GameFlowState to) { if (to == GameFlowState::Stopped && m_Scripts) { m_Scripts->EndPlay(); } }); } 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(); } // ✅ 进程退出前持久化 GUID 注册表(GetOrCreateGUID 只标 dirty, // 此处统一落盘,避免开发期每次新增都触发全量 yaml 重写) if (m_Assets) { m_Assets->FlushGUIDRegistry(); } if (m_ImGuiLayer) m_ImGuiLayer.reset(); // 注意:这里不能 Logger::Shutdown()——成员析构(Device 销毁等) // 在函数体之后才执行,期间 Vulkan validation 回调仍会打日志, // logger 先亡会导致悬空调用 SIGSEGV。Shutdown 移到 main 的 // app 作用域结束之后(所有成员析构完毕)。 } void Application::EnableImGui() { if (m_ImGuiLayer) { ENGINE_WARN("ImGui layer already enabled, do not enable twice!"); return; } m_ImGuiLayer = CreateScope(GetWindow(), *m_Renderer); m_Renderer->SetImGuiLayer(m_ImGuiLayer.get()); // 输入隔离:ImGui 想要的输入不再透给游戏侧 //(事件回调与轮询接口在 Window 层统一门控) GetWindow().SetInputCapture( [] { return ImGui::GetIO().WantCaptureKeyboard; }, [] { return ImGui::GetIO().WantCaptureMouse; }); } void Application::SetVSync(bool vsync) { if (m_VSync == vsync) return; m_VSync = vsync; m_Renderer->SetVSync(vsync); } void Application::Run() { // Log Panel 开闸:到此为止之前的启动日志(窗口/Vulkan/UBO/ // IBL/网格构建)只落终端与文件——Unity Console 语义,面板只 // 收运行期日志(场景/物理/脚本),启动噪音不进面板 Engine::Debug::LogRing().SetEnabled(true); ENGINE_DEBUG("[APP] 主循环启动(Log Panel 开启;此行为 DEBUG 级," "勾选 Trace 过滤可见青色 DEBUG 徽标)"); ENGINE_INFO("Application starting"); m_Running = true; m_LastFrameTime = std::chrono::steady_clock::now(); tracy::SetThreadName("Main"); // 线程命名:否则 GUI 显示 Unknown OnInit(); // 物理世界(场景实体搭建完后创建) m_Physics = CreateScope(*m_Scene, m_Assets.get()); // 物理调试图元 → 渲染侧(PhysicsDebugPass 每帧上传绘制) m_Renderer->SetPhysicsDebugSource(&m_Physics->GetDebugFrame()); // 脚本生命周期(对账式,组件增删/改名即时生效);this 注入脚本 // 上下文(Window/Renderer/Physics 门面) m_Scripts = CreateScope(*m_Scene, this); while (m_Running) { const auto frameStart = std::chrono::steady_clock::now(); STATS.BeginFrame(); // 帧内资源分配清零(面板帧流失真修正) m_Window->PollEvents(); auto now = std::chrono::steady_clock::now(); 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; // 输入动作层采样(单点,整帧一致):仅 Running 激活—— // Paused/Stopped(编辑器态)动作静默清零,编辑器相机继续走 // 原始 IsKeyDown 通道(自带 UI 捕获门控,两套互不干扰) Input::Poll(m_GameFlow.IsIn(GameFlowState::Running)); // 物理步进(OnUpdate 之前,Unity FixedUpdate→Update 语义): // 动态体姿态写回 Transform 并标脏,UpdateTransforms 统一重算; // 吃 scaled dt → timeScale=0(Paused/Stopped 冻结)时 // accumulator 冻结,SyncScene 对账仍跑(拖静态体照常同步)。 // Loading 态整段跳过(加载中不步进) if (!m_GameFlow.IsIn(GameFlowState::Loading)) { m_Physics->Step(frame.Dt()); // 脚本只在 Running 执行(Unity Play Mode / UE PIE 语义): // 编辑态/暂停态 gameplay 脚本一律不跑——脚本副作用不依赖 // dt(读输入/画调试图元/翻转动画状态),"dt=0 自然停摆" // 不成立,曾致编辑态按 WASD 就画箭头+翻动画状态。实例 // 生命周期 = 一次 PIE 局:→Stopped 由 GameFlow 监听 // EndPlay 统一销毁 if (m_GameFlow.IsIn(GameFlowState::Running)) { m_Scripts->Update(frame.Dt()); } } OnUpdate(frame); // 延迟销毁在渲染前统一 flush,保证渲染遍历时池结构不变 m_Scene->FlushDestroyQueue(); m_Scene->UpdateTransforms(); // UE transform-update 阶段:渲染前保证 world 新鲜 // 帧首同步:窗口 resize / 渲染尺寸(面板跟随)/ 显示模式 / vsync // 统一在 UI 构建"之前"落定——此处可能重建 swapchain 与 RG 目标 // 并重注册 ImGui 场景纹理;若挪到 UI 构建之后,本帧 draw data // 会引用刚被销毁的描述符(validation: Invalid VkDescriptorSet) m_Renderer->BeginFrameSync(); // ---- UI 构建段:OnRenderOverlay 只"声明"控件,不直接碰 GPU ---- if (m_ImGuiLayer && m_ImGuiLayer->IsEnabled()) { const auto uiStart = std::chrono::steady_clock::now(); m_ImGuiLayer->NewFrame(); OnImGuiRender(); m_ImGuiLayer->BuildDrawData(); const auto uiEnd = std::chrono::steady_clock::now(); } // ---- 主渲染(框架行为):RG 顺序 Forward → ImGui(loadOp=Load) → // Present,draw data 在 ImGui pass 里才真正提交 GPU ---- m_Renderer->Render(*m_Scene, *m_Assets, frame.Dt()); // ---- 帧率控制:vsync 开启时 present 自带节奏;否则按目标限帧 ---- // 策略:先 sleep 到剩 ~2ms,再自旋——精度高且不烧 CPU // TargetFps <= 0 表示不限帧(跳过 sleep,避免 1e6/0 除零 UB) if (!m_VSync && m_TargetFps > 0) { const auto deadline = frameStart + std::chrono::microseconds(static_cast( 1e6 / m_TargetFps)); auto now2 = std::chrono::steady_clock::now(); if (now2 < deadline) { const auto remaining = std::chrono::duration_cast< std::chrono::milliseconds>(deadline - now2); if (remaining.count() > 2) { std::this_thread::sleep_for( remaining - std::chrono::milliseconds(2)); } while (std::chrono::steady_clock::now() < deadline) { // 自旋等待最后 ~2ms } } } ++m_FrameCount; FrameMark; // Tracy:帧边界(一帧的时间线切分) } OnShutdown(); ENGINE_INFO("Application stopped ({} frames)", m_FrameCount); } 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