- 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 帧)
191 lines
6.1 KiB
C++
191 lines
6.1 KiB
C++
//
|
|
// Created by atdunbg on 2026/9/20.
|
|
//
|
|
|
|
#include <Engine/Vulkan/Swapchain.h>
|
|
#include <Engine/Vulkan/Device.h>
|
|
#include <Engine/Core/Window.h>
|
|
#include <Engine/Core/Log.h>
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
|
|
#include "Engine/Vulkan/MemoryAllocator.h"
|
|
|
|
namespace Engine
|
|
{
|
|
Swapchain::Swapchain(Device& device, MemoryAllocator& allocator, Window& window)
|
|
: m_Device(device), m_Allocator(allocator)
|
|
{
|
|
Create(window);
|
|
}
|
|
|
|
Swapchain::~Swapchain()
|
|
{
|
|
Destroy();
|
|
}
|
|
|
|
void Swapchain::Create(Window& window)
|
|
{
|
|
const auto& vkDevice = m_Device.GetDevice();
|
|
auto surface = m_Device.GetSurface();
|
|
auto physicalDevice = m_Device.GetPhysicalDevice();
|
|
|
|
// 查询支持
|
|
auto caps = physicalDevice.getSurfaceCapabilitiesKHR(surface);
|
|
auto formats = physicalDevice.getSurfaceFormatsKHR(surface);
|
|
auto modes = physicalDevice.getSurfacePresentModesKHR(surface);
|
|
|
|
auto surfaceFormat = ChooseSurfaceFormat(formats);
|
|
auto presentMode = ChoosePresentMode(modes);
|
|
auto extent = ChooseExtent(caps, window);
|
|
|
|
m_Format = surfaceFormat.format;
|
|
m_Extent = extent;
|
|
|
|
// 交换链 image 数量
|
|
uint32_t imageCount = caps.minImageCount + 1;
|
|
if (caps.maxImageCount > 0 && imageCount > caps.maxImageCount)
|
|
{
|
|
imageCount = caps.maxImageCount;
|
|
}
|
|
|
|
vk::SwapchainCreateInfoKHR createInfo;
|
|
createInfo.setSurface(surface)
|
|
.setMinImageCount(imageCount)
|
|
.setImageFormat(surfaceFormat.format)
|
|
.setImageColorSpace(surfaceFormat.colorSpace)
|
|
.setImageExtent(extent)
|
|
.setImageArrayLayers(1)
|
|
.setImageUsage(vk::ImageUsageFlagBits::eColorAttachment)
|
|
.setImageSharingMode(vk::SharingMode::eExclusive)
|
|
.setPreTransform(caps.currentTransform)
|
|
.setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eOpaque)
|
|
.setPresentMode(presentMode)
|
|
.setClipped(VK_TRUE)
|
|
.setOldSwapchain(nullptr);
|
|
|
|
// graphics 和 present 队列族不同时,用 concurrent
|
|
uint32_t indices[] = {m_Device.GetGraphicsFamily(), m_Device.GetPresentFamily()};
|
|
if (indices[0] != indices[1])
|
|
{
|
|
createInfo.setImageSharingMode(vk::SharingMode::eConcurrent)
|
|
.setQueueFamilyIndices(indices);
|
|
}
|
|
|
|
m_Swapchain = vkDevice.createSwapchainKHR(createInfo);
|
|
|
|
// 拿 image
|
|
m_Images = vkDevice.getSwapchainImagesKHR(m_Swapchain);
|
|
|
|
// 创建 image view
|
|
m_ImageViews.resize(m_Images.size());
|
|
for (size_t i = 0; i < m_Images.size(); ++i)
|
|
{
|
|
vk::ImageViewCreateInfo viewInfo;
|
|
viewInfo.setImage(m_Images[i])
|
|
.setViewType(vk::ImageViewType::e2D)
|
|
.setFormat(m_Format)
|
|
.setSubresourceRange({
|
|
vk::ImageAspectFlagBits::eColor,
|
|
0, 1,
|
|
0, 1
|
|
});
|
|
|
|
m_ImageViews[i] = vkDevice.createImageView(viewInfo);
|
|
}
|
|
|
|
ENGINE_INFO("Swapchain created: {}x{}, {} images, format={}",
|
|
m_Extent.width, m_Extent.height,
|
|
m_Images.size(),
|
|
vk::to_string(m_Format));
|
|
}
|
|
|
|
void Swapchain::Destroy()
|
|
{
|
|
const auto& vkDevice = m_Device.GetDevice();
|
|
|
|
for (auto view : m_ImageViews)
|
|
{
|
|
vkDevice.destroyImageView(view);
|
|
}
|
|
m_ImageViews.clear();
|
|
m_Images.clear();
|
|
|
|
if (m_Swapchain)
|
|
{
|
|
vkDevice.destroySwapchainKHR(m_Swapchain);
|
|
m_Swapchain = nullptr;
|
|
}
|
|
}
|
|
|
|
void Swapchain::Recreate(Window& window)
|
|
{
|
|
const auto& vkDevice = m_Device.GetDevice();
|
|
vkDevice.waitIdle();
|
|
|
|
Destroy();
|
|
Create(window);
|
|
|
|
ENGINE_INFO("Swapchain recreated: {}x{}", m_Extent.width, m_Extent.height);
|
|
}
|
|
|
|
// ---------- 选择函数 ----------
|
|
|
|
vk::SurfaceFormatKHR Swapchain::ChooseSurfaceFormat(const std::vector<vk::SurfaceFormatKHR>& formats)
|
|
{
|
|
// 用 Unorm 而非 Srgb:当前管线(场景光照 + ImGui)都是把
|
|
// "显示空间"颜色直接写进 framebuffer,若用 Srgb 附件硬件会再
|
|
// 做一次 linear→sRGB 提亮 → 整体泛白(ImGui 原生蓝 #4296FA
|
|
// 会被洗成 #8CCBFC)。等上 HDR/tonemap 管线后再切回 Srgb。
|
|
for (const auto& f : formats)
|
|
{
|
|
if (f.format == vk::Format::eB8G8R8A8Unorm &&
|
|
f.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear)
|
|
{
|
|
return f;
|
|
}
|
|
}
|
|
return formats[0];
|
|
}
|
|
|
|
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 == preferred)
|
|
{
|
|
return m;
|
|
}
|
|
}
|
|
return vk::PresentModeKHR::eFifo;
|
|
}
|
|
|
|
vk::Extent2D Swapchain::ChooseExtent(const vk::SurfaceCapabilitiesKHR& caps, Window& window)
|
|
{
|
|
if (caps.currentExtent.width != std::numeric_limits<uint32_t>::max())
|
|
{
|
|
return caps.currentExtent;
|
|
}
|
|
|
|
int fbWidth = 0, fbHeight = 0;
|
|
window.GetFramebufferSize(fbWidth, fbHeight);
|
|
|
|
vk::Extent2D extent = {
|
|
static_cast<uint32_t>(fbWidth),
|
|
static_cast<uint32_t>(fbHeight)
|
|
};
|
|
|
|
extent.width = std::clamp(extent.width, caps.minImageExtent.width, caps.maxImageExtent.width);
|
|
extent.height = std::clamp(extent.height, caps.minImageExtent.height, caps.maxImageExtent.height);
|
|
|
|
return extent;
|
|
}
|
|
} // namespace Engine
|