feat: 帧率控制——手动限帧 + VSync 开关
- Application 帧率控制 API:SetLimitFps/SetTargetFps/SetVSync。 限帧目标最低 24fps(防止过低刷新导致交互卡死),无上限; vsync 开启时 present 自带节奏,手动限帧不生效 - 限帧策略:sleep 到剩 ~2ms 后自旋,精度高且不烧 CPU - Swapchain 加 VSync 开关:ChoosePresentMode 按开关选 Mailbox(低延迟垂直同步)/ Immediate(不限速可撕裂), FIFO 兜底;切换经 SetVSync → 重建 swapchain(复用 HandleResize) - Scene Debug 面板新增 Frame 区块(vsync/limit/目标滑条) - Run 循环新增 CPU 归属遥测(update/ui/render/total,每 120 帧)
This commit is contained in:
+35
-10
@@ -24,7 +24,6 @@
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include "Engine/ImGui/ImGuiLayer.h"
|
||||
|
||||
using namespace Engine;
|
||||
using namespace Engine::ECS;
|
||||
@@ -66,17 +65,21 @@ protected:
|
||||
camera.camera.SetPosition(glm::vec3(0.0f, 0.0f, 7.0f));
|
||||
|
||||
// 背包实体 × 3(悬浮在地面上方)
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
auto e = scene.CreateEntity("Backpack" + std::to_string(i));
|
||||
auto size = 10;
|
||||
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<TransformComponent>()
|
||||
.SetPosition(glm::vec3((i - 1) * 1.5f, 0.0f, 0.0f))
|
||||
.SetScale(glm::vec3(0.5f))
|
||||
.Compose();
|
||||
e.AddComponent<TransformComponent>()
|
||||
.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>(MeshRendererComponent{backpack});
|
||||
e.AddComponent<MeshRendererComponent>(MeshRendererComponent{backpack});
|
||||
|
||||
m_Spinners.push_back(e);
|
||||
m_Spinners.push_back(e);
|
||||
}
|
||||
}
|
||||
|
||||
// 灯光实体(第一个灯 = 阴影光源)
|
||||
@@ -102,7 +105,6 @@ protected:
|
||||
// 旋转动画:编辑 Transform 分量后 Compose 重算 world
|
||||
for (size_t i = 0; i < m_Spinners.size(); ++i) {
|
||||
auto& t = m_Spinners[i].GetComponent<TransformComponent>();
|
||||
t.position = glm::vec3((static_cast<f32>(i) - 1.0f) * 1.5f, 0.0f, 0.0f);
|
||||
t.rotation.y = m_Time * 60.0f + static_cast<f32>(i) * 40.0f;
|
||||
t.scale = glm::vec3(0.5f);
|
||||
t.Compose();
|
||||
@@ -163,6 +165,29 @@ protected:
|
||||
ImGui::Text(" %-12s %.3f ms", names[i].c_str(), times[i]);
|
||||
}
|
||||
|
||||
// 帧率控制
|
||||
auto& app = *this;
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Frame");
|
||||
bool vsync = app.GetVSync();
|
||||
if (ImGui::Checkbox("VSync", &vsync)) {
|
||||
app.SetVSync(vsync);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("fps: %.1f frame: %llu",
|
||||
1.0f / GetDeltaTime(),
|
||||
|
||||
@@ -54,6 +54,16 @@ namespace Engine {
|
||||
// 主渲染(ImGui pass 把 draw data 叠加到场景上)。
|
||||
void EnableImGui();
|
||||
|
||||
// ---- 帧率控制 ----
|
||||
// vsync 开启时 present 自带节奏,手动限帧不生效;
|
||||
// 限帧目标最低 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; }
|
||||
|
||||
// ---- 供派生类访问的模块 ----
|
||||
Window& GetWindow() const { return *m_Window; }
|
||||
Renderer& GetRenderer() const { return *m_Renderer; }
|
||||
@@ -108,6 +118,11 @@ namespace Engine {
|
||||
f32 m_DeltaTime = 0.0f;
|
||||
u64 m_FrameCount = 0;
|
||||
|
||||
// 帧率控制
|
||||
bool m_VSync = true; // 垂直同步(present 自带节奏)
|
||||
bool m_LimitFps = false; // 手动限帧开关(vsync 开启时不生效)
|
||||
f32 m_TargetFps = 60.0f; // 限帧目标(≥24)
|
||||
|
||||
std::chrono::steady_clock::time_point m_LastFrameTime;
|
||||
};
|
||||
|
||||
|
||||
@@ -174,6 +174,10 @@ namespace Engine {
|
||||
const std::vector<std::string>& GetPassNames() const { return m_PassNames; }
|
||||
const std::vector<f32>& GetPassGpuMs() const { return m_PassGpuMs; }
|
||||
|
||||
// 垂直同步开关(Mailbox ↔ Immediate):切换会重建 swapchain,
|
||||
// 只应在帧 UI 构建段调用(Render 之前)
|
||||
void SetVSync(bool vsync);
|
||||
|
||||
private:
|
||||
|
||||
vk::Extent2D m_Extent{};
|
||||
|
||||
@@ -28,6 +28,11 @@ namespace Engine
|
||||
vk::SwapchainKHR Get() const { return m_Swapchain; }
|
||||
vk::Format GetFormat() const { return m_Format; }
|
||||
vk::Extent2D GetExtent() const { return m_Extent; }
|
||||
|
||||
// 垂直同步开关:影响 present mode(Mailbox ↔ Immediate),
|
||||
// 切换后需 Recreate 才生效
|
||||
void SetVSync(bool vsync) { m_VSync = vsync; }
|
||||
bool GetVSync() const { return m_VSync; }
|
||||
uint32_t GetImageCount() const { return static_cast<u32>(m_Images.size()); }
|
||||
|
||||
const std::vector<vk::Image>& GetImages() const { return m_Images; }
|
||||
@@ -56,6 +61,7 @@ namespace Engine
|
||||
|
||||
vk::Format m_Format;
|
||||
vk::Extent2D m_Extent;
|
||||
bool m_VSync = true;
|
||||
};
|
||||
} // namespace Engine
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <imgui.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
namespace Engine {
|
||||
|
||||
@@ -67,6 +68,13 @@ namespace Engine {
|
||||
[] { return ImGui::GetIO().WantCaptureMouse; });
|
||||
}
|
||||
|
||||
void Application::SetVSync(bool vsync)
|
||||
{
|
||||
if (m_VSync == vsync) return;
|
||||
m_VSync = vsync;
|
||||
m_Renderer->SetVSync(vsync);
|
||||
}
|
||||
|
||||
void Application::Run()
|
||||
{
|
||||
ENGINE_INFO("Application starting");
|
||||
@@ -110,6 +118,26 @@ namespace Engine {
|
||||
// Present,draw data 在 ImGui pass 里才真正提交 GPU ----
|
||||
m_Renderer->Render(*m_Scene, *m_Assets);
|
||||
|
||||
// ---- 帧率控制:vsync 开启时 present 自带节奏;否则按目标限帧 ----
|
||||
// 策略:先 sleep 到剩 ~2ms,再自旋——精度高且不烧 CPU
|
||||
if (!m_VSync && m_LimitFps) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CPU 侧遥测(每 120 帧一行):定位帧耗时的 CPU 归属
|
||||
const auto frameEnd = std::chrono::steady_clock::now();
|
||||
static u32 cpuTelemetryCount = 0;
|
||||
|
||||
@@ -754,6 +754,13 @@ namespace Engine
|
||||
m_RenderGraph->SetTargetExtent(m_Extent); // 触发首次 Compile
|
||||
}
|
||||
|
||||
void Renderer::SetVSync(bool vsync)
|
||||
{
|
||||
if (m_Swapchain->GetVSync() == vsync) return;
|
||||
m_Swapchain->SetVSync(vsync);
|
||||
HandleResize(); // present mode 变化需要重建 swapchain
|
||||
}
|
||||
|
||||
void Renderer::HandleResize()
|
||||
{
|
||||
m_Device->GetDevice().waitIdle();
|
||||
|
||||
@@ -151,9 +151,15 @@ namespace Engine
|
||||
|
||||
vk::PresentModeKHR Swapchain::ChoosePresentMode(const std::vector<vk::PresentModeKHR>& modes)
|
||||
{
|
||||
// vsync 开:Mailbox(三缓冲垂直同步,低延迟);关:Immediate
|
||||
//(不限速,可能撕裂)。FIFO 是所有实现必须支持的兜底
|
||||
const vk::PresentModeKHR preferred = m_VSync
|
||||
? vk::PresentModeKHR::eMailbox
|
||||
: vk::PresentModeKHR::eImmediate;
|
||||
|
||||
for (const auto& m : modes)
|
||||
{
|
||||
if (m == vk::PresentModeKHR::eMailbox)
|
||||
if (m == preferred)
|
||||
{
|
||||
return m;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user