Files
HelloVulkan/Engine/src/Core/Application.cpp
T
atdunbg beba491bcb 阶段性合并:动画重定向、碎裂共享库、第三人称玩法、编辑器面板与 vendored ufbx
- 动画:HumanoidRig 三层架构(standard 标准槽 / dialects 方言映射 / mapper 配对),
  指/趾统一走世界增量两遍式烘焙;AnimationAttach 拆为纯算法 + facade;修正 HumanoidBasis
  行走基朝向与跨骨架去重、根运动通道
- 物理:CharacterController 角色互碰撞 / shapeOffset / 接触事件;PhysicsWorld 接触合成与
  字符冲量判定
- 渲染与材质:Model 多材质按材质拆子 mesh 与贴图路径解析、DDS 直传、GLB 内嵌纹理落盘、
  不透明通道 alpha 固定 1.0、glTF FlipUVs 对齐;SceneSerializer 支持 meshIndex 与材质覆盖
- 脚本:FractureLib 抽为共享库(HostView 桥接)、WallFracture 参数直绑、新增
  ThirdPersonCamera / Follower / WASDMove 玩法;移除 GameDirector / PlayerController / Target
- 编辑器:新增 GamepadPanel、LogPanel(LogRing),改进 Inspector / 材质 / 统计 / 视口面板
- 依赖:vendored ufbx(FBX 5.x/6.x/7.x 全版本解析,替代 assimp 对 FBX 6.1 的拒读)
2026-10-11 22:22:19 +08:00

260 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Created by atdunbg on 2026/9/22.
//
#include <Engine/Core/Application.h>
#include <Engine/Core/Window.h>
#include <Engine/Core/Input.h>
#include <Engine/Core/Log.h>
#include <Engine/Core/LogRing.h>
#include <Engine/Core/Stats.h>
#include <Engine/ECS/Scene.h>
#include <Engine/Physics/PhysicsWorld.h>
#include <Engine/Scripting/ScriptRuntime.h>
#include <Engine/Renderer/Renderer.h>
#include <Engine/Assets/AssetManager.h>
#include <Engine/Vulkan/Device.h>
#include <Engine/ImGui/ImGuiLayer.h>
#include <imgui.h>
#include <tracy/Tracy.hpp>
#include <algorithm>
#include <thread>
namespace Engine {
Application::Application(const std::string& title, u32 width, u32 height)
{
m_Window = CreateScope<Window>(title, width, height);
// 事件通道:GLFW 回调 → 本类 DispatchEvent → 派生类钩子
m_Window->SetEventCallback(
[this](const Event& e) { DispatchEvent(e); });
// 场景(ECS 世界)+ 渲染器
m_Scene = CreateScope<ECS::Scene>();
m_Renderer = CreateScope<Renderer>(*m_Window);
// 资产层:上传服务由渲染层提供(IAssetUploader 实现),
// 资产层不感知 Vulkan 具体类。
// 声明在 Renderer 之后 → 析构先于 Renderer(它引用其子模块)
m_Assets = CreateScope<Assets::AssetManager>(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<ImGuiLayer>(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<PhysicsWorld>(*m_Scene, m_Assets.get());
// 物理调试图元 → 渲染侧(PhysicsDebugPass 每帧上传绘制)
m_Renderer->SetPhysicsDebugSource(&m_Physics->GetDebugFrame());
// 脚本生命周期(对账式,组件增删/改名即时生效);this 注入脚本
// 上下文(Window/Renderer/Physics 门面)
m_Scripts = CreateScope<Scripting::ScriptRuntime>(*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<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;
// 输入动作层采样(单点,整帧一致):仅 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<long long>(
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