diff --git a/App/main.cpp b/App/main.cpp index fbe181d..6535b10 100644 --- a/App/main.cpp +++ b/App/main.cpp @@ -54,8 +54,8 @@ protected: m_Ground = scene.CreateEntity("Ground"); m_Ground.AddComponent() - .SetPosition(glm::vec3(0.0f, -0.5f, 0.0f)) - .Compose(); + .SetPosition(glm::vec3(0.0f, -4.5f, 0.0f)) + .Compose(); m_Ground.AddComponent(MeshRendererComponent{ground}); // 相机实体(primary = 当前渲染视角) @@ -65,16 +65,17 @@ protected: camera.camera.SetPosition(glm::vec3(0.0f, 0.0f, 7.0f)); // 背包实体 × 3(悬浮在地面上方) - auto size = 10; - for (int i = 0; i < size; ++i) { - for (int j = 0 ; j < size; j ++) + auto size = 1; + for (int i = 0; i < size; ++i) + { + for (int j = 0; j < size; j++) { auto e = scene.CreateEntity("Backpack" + std::to_string(i)); e.AddComponent() - .SetPosition(glm::vec3((i - size * 0.5) * 1.5f, 0.0f, (j - size * 0.5) * 1.5f)) - .SetScale(glm::vec3(0.5f)) - .Compose(); + .SetPosition(glm::vec3((i - size * 0.5) * 1.5f, 0.0f, (j - size * 0.5) * 1.5f)) + .SetScale(glm::vec3(0.5f)) + .Compose(); e.AddComponent(MeshRendererComponent{backpack}); @@ -85,13 +86,17 @@ protected: // 灯光实体(第一个灯 = 阴影光源) m_KeyLight = scene.CreateEntity("KeyLight"); m_KeyLight.AddComponent( - LightComponent{glm::vec3(-0.5f, -0.6f, -0.35f), - glm::vec4(1.0f, 0.95f, 0.9f, 1.0f)}); + LightComponent{ + glm::vec3(-0.5f, -0.6f, -0.35f), + glm::vec4(1.0f, 0.95f, 0.9f, 1.0f) + }); scene.CreateEntity("FillLight") - .AddComponent( - LightComponent{glm::vec3(0.5f, -0.5f, 0.3f), - glm::vec4(0.3f, 0.5f, 1.0f, 0.8f)}); + .AddComponent( + LightComponent{ + glm::vec3(0.5f, -0.5f, 0.3f), + glm::vec4(0.3f, 0.5f, 1.0f, 0.8f) + }); } void OnUpdate(f32 deltaTime) override @@ -100,10 +105,11 @@ protected: // 相机(WASD/Space/Shift 移动) m_Camera.GetComponent() - .camera.Update(GetWindow(), deltaTime); + .camera.Update(GetWindow(), deltaTime); // 旋转动画:编辑 Transform 分量后 Compose 重算 world - for (size_t i = 0; i < m_Spinners.size(); ++i) { + for (size_t i = 0; i < m_Spinners.size(); ++i) + { auto& t = m_Spinners[i].GetComponent(); t.rotation.y = m_Time * 60.0f + static_cast(i) * 40.0f; t.scale = glm::vec3(0.5f); @@ -117,18 +123,18 @@ protected: void OnRenderOverlay() override { - if (!m_OverlayVisible) return; - ImGui::ShowDemoWindow(); ImGui::Begin("Scene Debug"); // 阴影光源方向:拖动滑条实时看影子变化 - if (auto* light = m_KeyLight.TryGetComponent()) { + if (auto* light = m_KeyLight.TryGetComponent()) + { ImGui::Text("Key Light (shadow caster)"); ImGui::SliderFloat3("direction", &light->direction.x, -1.0f, 1.0f); - if (ImGui::Button("reset")) { + if (ImGui::Button("reset")) + { light->direction = glm::vec3(-0.5f, -0.6f, -0.35f); } } @@ -138,21 +144,25 @@ protected: ImGui::Separator(); ImGui::Text("Tonemap"); f32 exposure = renderer.GetExposure(); - if (ImGui::SliderFloat("exposure", &exposure, 0.1f, 4.0f)) { + if (ImGui::SliderFloat("exposure", &exposure, 0.1f, 4.0f)) + { renderer.SetExposure(exposure); } bool aces = renderer.GetUseACES(); - if (ImGui::Checkbox("ACES", &aces)) { + if (ImGui::Checkbox("ACES", &aces)) + { renderer.SetUseACES(aces); } ImGui::Text("Bloom"); f32 threshold = renderer.GetBloomThreshold(); - if (ImGui::SliderFloat("threshold", &threshold, 0.0f, 2.0f)) { + if (ImGui::SliderFloat("threshold", &threshold, 0.0f, 2.0f)) + { renderer.SetBloomThreshold(threshold); } f32 strength = renderer.GetBloomStrength(); - if (ImGui::SliderFloat("strength", &strength, 0.0f, 2.0f)) { + if (ImGui::SliderFloat("strength", &strength, 0.0f, 2.0f)) + { renderer.SetBloomStrength(strength); } @@ -161,7 +171,8 @@ protected: ImGui::Text("GPU Timings"); const auto& names = renderer.GetPassNames(); const auto& times = renderer.GetPassGpuMs(); - for (size_t i = 0; i < names.size() && i < times.size(); ++i) { + for (size_t i = 0; i < names.size() && i < times.size(); ++i) + { ImGui::Text(" %-12s %.3f ms", names[i].c_str(), times[i]); } @@ -170,21 +181,21 @@ protected: ImGui::Separator(); ImGui::Text("Frame"); bool vsync = app.GetVSync(); - if (ImGui::Checkbox("VSync", &vsync)) { + if (ImGui::Checkbox("VSync", &vsync)) + { app.SetVSync(vsync); + if (!vsync) + { + const f32 target = app.GetTargetFps(); + app.SetTargetFps(target); + } } - bool limit = app.GetLimitFps(); - if (ImGui::Checkbox("Limit FPS", &limit)) { - app.SetLimitFps(limit); - } - if (limit) { - if (vsync) { - ImGui::TextDisabled("(vsync 开启时限帧无效)"); - } else { - f32 target = app.GetTargetFps(); - if (ImGui::SliderFloat("target fps", &target, 24.0f, 360.0f, "%.0f")) { - app.SetTargetFps(target); - } + if (!vsync) + { + f32 target = app.GetTargetFps(); + if (ImGui::SliderFloat("target fps", &target, 24.0f, 360.0f, "%.0f")) + { + app.SetTargetFps(target); } } @@ -194,32 +205,76 @@ protected: static_cast(GetFrameCount())); ImGui::End(); + + + // 自定义重载事件: 重载关闭事件模态框 + { + if (m_ShowQuitConfirm) + { + ImGui::OpenPopup("Should quit?"); + m_ShowQuitConfirm = false; + } + + ImGui::SetNextWindowPos( + ImGui::GetMainViewport()->GetCenter(), + ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + + if (ImGui::BeginPopupModal("Should quit?", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::TextUnformatted("Quit the application?"); + ImGui::Separator(); + if (ImGui::Button("Yes", ImVec2(120, 0))) + { + m_QuitConfirmed = true; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("No", ImVec2(120, 0))) + { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + if (m_QuitConfirmed) + { + m_QuitConfirmed = false; + Close(); + } + } + } + + void OnWindowClose(const Event& event) override + { + m_ShowQuitConfirm = true; } void OnKeyPress(const Event& event) override { - if (event.key == GLFW_KEY_GRAVE_ACCENT) { - m_OverlayVisible = !m_OverlayVisible; // ` 反折叠加层 + if (event.key == GLFW_KEY_GRAVE_ACCENT) + { + SetImGuiEnabled(!GetImGuiEnabled()); } - if (event.key == GLFW_KEY_F1) { + if (event.key == GLFW_KEY_F1) + { STATS.PrintComparison(); } - if (event.key == GLFW_KEY_ESCAPE) { - Close(); - } } + void OnWindowResize(const Event& event) override { ENGINE_INFO("App: window resized to {}x{}", event.width, event.height); } private: - EntityHandle m_Camera{}; // Scene::CreateEntity 之前为空 + bool m_ShowQuitConfirm = false; + bool m_QuitConfirmed = false; + EntityHandle m_Camera{}; // Scene::CreateEntity 之前为空 EntityHandle m_Ground{}; EntityHandle m_KeyLight{}; std::vector m_Spinners; - bool m_OverlayVisible = true; f32 m_Time = 0.0f; }; @@ -227,10 +282,12 @@ int main() { SandboxApp app; - try { + try + { app.Run(); } - catch (const std::exception& e) { + catch (const std::exception& e) + { ENGINE_FATAL("Exception: {}", e.what()); return -1; } diff --git a/Engine/include/Engine/Core/Application.h b/Engine/include/Engine/Core/Application.h index 5f9a80d..d644870 100644 --- a/Engine/include/Engine/Core/Application.h +++ b/Engine/include/Engine/Core/Application.h @@ -26,6 +26,8 @@ #include #include +#include "Engine/ImGui/ImGuiLayer.h" + namespace Engine { class Window; @@ -59,8 +61,6 @@ namespace Engine { // 限帧目标最低 24fps(防止过低刷新导致交互卡死),无上限 void SetVSync(bool vsync); bool GetVSync() const { return m_VSync; } - void SetLimitFps(bool on) { m_LimitFps = on; } - bool GetLimitFps() const { return m_LimitFps; } void SetTargetFps(f32 fps) { m_TargetFps = std::max(24.0f, fps); } f32 GetTargetFps() const { return m_TargetFps; } @@ -72,6 +72,11 @@ namespace Engine { f32 GetDeltaTime() const { return m_DeltaTime; } u64 GetFrameCount() const { return m_FrameCount; } + // ImGui相关 + void SetImGuiEnabled(const bool enable) { if (m_ImGuiLayer) m_ImGuiLayer->SetEnabled(enable); } + // 理论上ImGuiLayer不应该会空,暂时静默处理 + bool GetImGuiEnabled() const { return m_ImGuiLayer ? m_ImGuiLayer->IsEnabled() : false; } + protected: // ---- 生命周期钩子 ---- @@ -119,8 +124,7 @@ namespace Engine { u64 m_FrameCount = 0; // 帧率控制 - bool m_VSync = true; // 垂直同步(present 自带节奏) - bool m_LimitFps = false; // 手动限帧开关(vsync 开启时不生效) + bool m_VSync = false; // 垂直同步(present 自带节奏) f32 m_TargetFps = 60.0f; // 限帧目标(≥24) std::chrono::steady_clock::time_point m_LastFrameTime; diff --git a/Engine/include/Engine/Core/Window.h b/Engine/include/Engine/Core/Window.h index c97b62d..cb958a6 100644 --- a/Engine/include/Engine/Core/Window.h +++ b/Engine/include/Engine/Core/Window.h @@ -87,6 +87,8 @@ namespace Engine { static void CursorPosCallback(GLFWwindow* window, double xpos, double ypos); static void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset); static void WindowFocusCallback(GLFWwindow* window, int focused); + + static void WindowCloseCallback(GLFWwindow* window); }; } // namespace Engine diff --git a/Engine/include/Engine/Vulkan/Swapchain.h b/Engine/include/Engine/Vulkan/Swapchain.h index daeb6e6..1b94137 100644 --- a/Engine/include/Engine/Vulkan/Swapchain.h +++ b/Engine/include/Engine/Vulkan/Swapchain.h @@ -61,7 +61,7 @@ namespace Engine vk::Format m_Format; vk::Extent2D m_Extent; - bool m_VSync = true; + bool m_VSync = false; }; } // namespace Engine diff --git a/Engine/src/Core/Application.cpp b/Engine/src/Core/Application.cpp index 6ac666f..44a7481 100644 --- a/Engine/src/Core/Application.cpp +++ b/Engine/src/Core/Application.cpp @@ -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( 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( - uiEnd - uiStart).count(); - const f32 renderMs = std::chrono::duration( - frameEnd - uiEnd).count(); - const f32 updateMs = std::chrono::duration( - uiStart - frameStart).count(); - const f32 totalMs = std::chrono::duration( - 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(); } diff --git a/Engine/src/Core/Window.cpp b/Engine/src/Core/Window.cpp index 9515dc7..849815c 100644 --- a/Engine/src/Core/Window.cpp +++ b/Engine/src/Core/Window.cpp @@ -26,7 +26,7 @@ namespace Engine void Window::KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { - auto* self = static_cast(glfwGetWindowUserPointer(window)); + const auto* self = static_cast(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(glfwGetWindowUserPointer(window)); + const auto* self = static_cast(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(glfwGetWindowUserPointer(window)); + const auto* self = static_cast(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(glfwGetWindowUserPointer(window)); + const auto* self = static_cast(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(glfwGetWindowUserPointer(window)); + const auto* self = static_cast(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(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() diff --git a/Engine/src/Renderer/Model.cpp b/Engine/src/Renderer/Model.cpp index 4585085..f9227ef 100644 --- a/Engine/src/Renderer/Model.cpp +++ b/Engine/src/Renderer/Model.cpp @@ -178,7 +178,7 @@ Scope 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(); } diff --git a/Engine/src/Renderer/Renderer.cpp b/Engine/src/Renderer/Renderer.cpp index ce57579..ae171c4 100644 --- a/Engine/src/Renderer/Renderer.cpp +++ b/Engine/src/Renderer/Renderer.cpp @@ -475,7 +475,7 @@ namespace Engine vk::ClearValue clearValue; clearValue.color = vk::ClearColorValue( - std::array{0.1f, 0.1f, 0.1f, 1.0f}); + std::array{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) diff --git a/Engine/src/Vulkan/Swapchain.cpp b/Engine/src/Vulkan/Swapchain.cpp index e4639b1..e91378f 100644 --- a/Engine/src/Vulkan/Swapchain.cpp +++ b/Engine/src/Vulkan/Swapchain.cpp @@ -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(caps.supportedCompositeAlpha), + static_cast(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& 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) { diff --git a/shaders/tonemap.frag b/shaders/tonemap.frag index 3eac200..6fcb462 100644 --- a/shaders/tonemap.frag +++ b/shaders/tonemap.frag @@ -25,8 +25,9 @@ vec3 acesFilm(vec3 x) { void main() { // bloom 在 HDR 域叠加(半分辨率模糊图线性采样上采样) - vec3 hdr = texture(hdrImage, fragUV).rgb - + texture(bloomImage, fragUV).rgb * pc.bloomStrength; + vec4 hdrTex = texture(hdrImage, fragUV); + vec3 hdr = hdrTex.rgb + texture(bloomImage, fragUV).rgb * pc.bloomStrength; + float alpha = hdrTex.a; hdr *= pc.exposure; @@ -34,5 +35,5 @@ void main() { ? acesFilm(hdr) : clamp(hdr, 0.0, 1.0); - outColor = vec4(pow(mapped, vec3(1.0 / 2.2)), 1.0); + outColor = vec4(pow(mapped, vec3(1.0 / 2.2)), alpha); }