feat: 透明窗口支持 + 退出确认流程 + ImGui 总开关

用户自主改进批次:
- 透明窗口:GLFW_TRANSPARENT_FRAMEBUFFER + 窗口 opacity 0.5,
  swapchain composite alpha 按能力自适应(Opaque/Pre/PostMultiplied),
  Forward 清屏 alpha=0,tonemap 透传 HDR alpha——物体实体、背景透明
- 退出确认:Window 挂 WindowCloseCallback 发事件,App 侧 OnWindowClose
  置确认标记,OnRenderOverlay 弹 ImGui 确认框后 Close();
  主循环退出条件改为 m_Running(close 事件驱动)
- ImGui 总开关:` 键切换 ImGuiLayer::SetEnabled,禁用时跳过
  NewFrame/OnRenderOverlay/BuildDrawData 整段
- 帧率控制简化:去掉 LimitFps 开关,非 vsync 时恒按 target 限帧
  (默认 60,可调 ≥24);m_VSync 默认 false(Mailbox 不限速)
- 清理:Model.cpp const 修正,swapchain 日志带 present mode,
  GPU 遥测暂时注释

验证:0 error 0 warning,composite alpha 实测选 PreMultiplied(0x2),
present=Mailbox
This commit is contained in:
2026-09-23 10:57:48 +08:00
parent d8274e8bdc
commit df4871a49b
10 changed files with 174 additions and 95 deletions
+5 -26
View File
@@ -84,7 +84,7 @@ namespace Engine {
OnInit();
while (m_Running && !m_Window->ShouldClose()) {
while (m_Running) {
const auto frameStart = std::chrono::steady_clock::now();
m_Window->PollEvents();
@@ -103,13 +103,10 @@ namespace Engine {
const auto uiStart = std::chrono::steady_clock::now();
// ---- UI 构建段:OnRenderOverlay 只"声明"控件,不直接碰 GPU ----
if (m_ImGuiLayer) {
if (m_ImGuiLayer && m_ImGuiLayer->IsEnabled())
{
m_ImGuiLayer->NewFrame();
}
OnRenderOverlay();
if (m_ImGuiLayer) {
OnRenderOverlay();
m_ImGuiLayer->BuildDrawData();
}
const auto uiEnd = std::chrono::steady_clock::now();
@@ -120,7 +117,7 @@ namespace Engine {
// ---- 帧率控制:vsync 开启时 present 自带节奏;否则按目标限帧 ----
// 策略:先 sleep 到剩 ~2ms,再自旋——精度高且不烧 CPU
if (!m_VSync && m_LimitFps) {
if (!m_VSync) {
const auto deadline = frameStart +
std::chrono::microseconds(static_cast<long long>(
1e6 / m_TargetFps));
@@ -137,23 +134,6 @@ namespace Engine {
}
}
}
// CPU 侧遥测(每 120 帧一行):定位帧耗时的 CPU 归属
const auto frameEnd = std::chrono::steady_clock::now();
static u32 cpuTelemetryCount = 0;
if (++cpuTelemetryCount % 120 == 1) {
const f32 uiMs = std::chrono::duration<f32, std::milli>(
uiEnd - uiStart).count();
const f32 renderMs = std::chrono::duration<f32, std::milli>(
frameEnd - uiEnd).count();
const f32 updateMs = std::chrono::duration<f32, std::milli>(
uiStart - frameStart).count();
const f32 totalMs = std::chrono::duration<f32, std::milli>(
frameEnd - frameStart).count();
ENGINE_INFO("CPU times: update={:.2f}ms ui={:.2f}ms render={:.2f}ms total={:.2f}ms",
updateMs, uiMs, renderMs, totalMs);
}
++m_FrameCount;
}
@@ -182,7 +162,6 @@ namespace Engine {
void Application::OnWindowClose(const Event& event)
{
// 默认行为:直接退出。需要"退出确认"的程序覆写本钩子。
Close();
}
+20 -5
View File
@@ -26,7 +26,7 @@ namespace Engine
void Window::KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
const auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
if (!self->m_EventCallback || key == GLFW_KEY_UNKNOWN) return;
// UI 捕获键盘时,游戏侧不收键盘事件(ImGui 后端链式回调自行处理)
@@ -42,7 +42,7 @@ namespace Engine
void Window::MouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
{
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
const auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
if (!self->m_EventCallback) return;
if (self->m_MouseCapture && self->m_MouseCapture()) return;
@@ -55,7 +55,7 @@ namespace Engine
void Window::CursorPosCallback(GLFWwindow* window, double xpos, double ypos)
{
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
const auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
if (!self->m_EventCallback) return;
if (self->m_MouseCapture && self->m_MouseCapture()) return;
@@ -68,7 +68,7 @@ namespace Engine
void Window::ScrollCallback(GLFWwindow* window, double xoffset, double yoffset)
{
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
const auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
if (!self->m_EventCallback) return;
if (self->m_MouseCapture && self->m_MouseCapture()) return;
@@ -81,7 +81,7 @@ namespace Engine
void Window::WindowFocusCallback(GLFWwindow* window, int focused)
{
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
const auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
if (!self->m_EventCallback) return;
Event e;
@@ -89,6 +89,16 @@ namespace Engine
self->m_EventCallback(e);
}
void Window::WindowCloseCallback(GLFWwindow* window)
{
const auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
if (!self->m_EventCallback) return;
Event e;
e.type = EventType::WindowClose;
self->m_EventCallback(e);
}
Window::Window(const std::string& title, u32 width, u32 height)
: m_Width(width), m_Height(height), m_Title(title)
{
@@ -100,6 +110,8 @@ namespace Engine
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
// glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
// 设置窗口为半透明
m_Window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr);
if (!m_Window)
@@ -108,6 +120,7 @@ namespace Engine
glfwTerminate();
return;
}
glfwSetWindowOpacity(m_Window, 0.5f);
ENGINE_INFO("Window created: {} ({}x{})", title, width, height);
@@ -120,6 +133,8 @@ namespace Engine
glfwSetCursorPosCallback(m_Window, CursorPosCallback);
glfwSetScrollCallback(m_Window, ScrollCallback);
glfwSetWindowFocusCallback(m_Window, WindowFocusCallback);
glfwSetWindowCloseCallback(m_Window, WindowCloseCallback);
}
Window::~Window()
+1 -1
View File
@@ -178,7 +178,7 @@ Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
aiString path;
if (material->GetTexture(aiTextureType_DIFFUSE, 0, &path) == AI_SUCCESS) {
std::filesystem::path modelDir =
const std::filesystem::path modelDir =
std::filesystem::path(m_ModelPath).parent_path();
texturePath = (modelDir / path.C_Str()).string();
}
+3 -1
View File
@@ -475,7 +475,7 @@ namespace Engine
vk::ClearValue clearValue;
clearValue.color = vk::ClearColorValue(
std::array<float, 4>{0.1f, 0.1f, 0.1f, 1.0f});
std::array<float, 4>{0.0f, 0.0f, 0.0f, 0.0f});
vk::RenderingAttachmentInfo colorAttachment;
colorAttachment.setImageView(ctx.View(m_HDRColor))
@@ -850,6 +850,7 @@ namespace Engine
: 0.0f;
}
/*
// 控制台遥测:每 120 次读取(≈60 帧 @2 slot)输出一行
static u32 telemetryCount = 0;
if (++telemetryCount % 120 == 1) {
@@ -861,6 +862,7 @@ namespace Engine
line += " collect=" + std::to_string(m_CollectMs).substr(0, 5) + "ms";
ENGINE_INFO("{}", line);
}
*/
}
void Renderer::Render(ECS::Scene& scene, Assets::AssetManager& assets)
+26 -7
View File
@@ -51,6 +51,21 @@ namespace Engine
}
vk::SwapchainCreateInfoKHR createInfo;
vk::CompositeAlphaFlagBitsKHR compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque;
if (caps.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::ePreMultiplied)
{
compositeAlpha = vk::CompositeAlphaFlagBitsKHR::ePreMultiplied;
}
else if (caps.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::ePostMultiplied)
{
compositeAlpha = vk::CompositeAlphaFlagBitsKHR::ePostMultiplied;
}
ENGINE_INFO("supportedCompositeAlpha = 0x{:x}, chosen = 0x{:x}",
static_cast<u32>(caps.supportedCompositeAlpha),
static_cast<u32>(compositeAlpha));
createInfo.setSurface(surface)
.setMinImageCount(imageCount)
.setImageFormat(surfaceFormat.format)
@@ -60,7 +75,7 @@ namespace Engine
.setImageUsage(vk::ImageUsageFlagBits::eColorAttachment)
.setImageSharingMode(vk::SharingMode::eExclusive)
.setPreTransform(caps.currentTransform)
.setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eOpaque)
.setCompositeAlpha(compositeAlpha)
.setPresentMode(presentMode)
.setClipped(VK_TRUE)
.setOldSwapchain(nullptr);
@@ -95,10 +110,11 @@ namespace Engine
m_ImageViews[i] = vkDevice.createImageView(viewInfo);
}
ENGINE_INFO("Swapchain created: {}x{}, {} images, format={}",
ENGINE_INFO("Swapchain created: {}x{}, {} images, format={}, present={}",
m_Extent.width, m_Extent.height,
m_Images.size(),
vk::to_string(m_Format));
vk::to_string(m_Format),
vk::to_string(presentMode));
}
void Swapchain::Destroy()
@@ -151,11 +167,14 @@ namespace Engine
vk::PresentModeKHR Swapchain::ChoosePresentMode(const std::vector<vk::PresentModeKHR>& modes)
{
// vsync 开:Mailbox(三缓冲垂直同步,低延迟);关:Immediate
//(不限速,可能撕裂)。FIFO 是所有实现必须支持的兜底
// 实测 Wayland/NVIDIA:Mailbox 不阻塞应用循环(compositor 取最新
// 缓冲,应用层计数可远超刷新率);Immediate 常不可用而退到 FIFO。
// 因此映射按"用户预期"定义:
// vsync 开 → FIFO:应用被 pacing 到刷新率(真正的垂直同步)
// vsync 关 → Mailbox:应用循环不限速(Wayland 下无撕裂)
const vk::PresentModeKHR preferred = m_VSync
? vk::PresentModeKHR::eMailbox
: vk::PresentModeKHR::eImmediate;
? vk::PresentModeKHR::eFifo
: vk::PresentModeKHR::eMailbox;
for (const auto& m : modes)
{