vulkan从小白到专家——VulkanSwapChain
Vulkan没有默认帧缓冲区的概念,在送显上屏之前,需要一个基础结构来持有一些缓冲区。Vulkan引入了交换链的概念。
交换链本质上就是一个(用于送显上屏)图像的等待队列。应用程序获取一个图像,绘制内容,然后返回到队列。队列如何工作,队列中图像如何送显上屏 取决于交换链的配置参数。
交换链的作用就是同步图像的上屏和屏幕的刷新。
0 Surface的校验
验证GPU是否支持surface的显示,vulkan标准内没有窗体surface的概念,外在的因素不可控所以要校验,
// Section1: check
VkBool32 vk_queue_family_supports_present = false;
auto hr = vkGetPhysicalDeviceSurfaceSupportKHR(device.gpuDevice_, device.queueFamilyIndex_, device.surface_, &vk_queue_family_supports_present);
if (hr != VK_SUCCESS || !vk_queue_family_supports_present) {
LOGE("surface not founded...");
exit(1);
}
这个接口第二个参数是queue的索引,多个队列需要循环遍历,手机上一般1个队列有效。
1 Surface Format相关
同样手段,先取长度 再填充vector。
// Query the list of supported surface format and choose one we like
uint32_t formatCount = 0;
hr = vkGetPhysicalDeviceSurfaceFormatsKHR(device.gpuDevice_, device.surface_,
&formatCount, nullptr);
if (hr != VK_SUCCESS || formatCount == 0) {
LOGE("could not get number of physical device formats for device");
exit(1);
}
std::vector<VkSurfaceFormatKHR> formats; formats.resize(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(device.gpuDevice_, device.surface_,
&formatCount, formats);
LOGI("Got %d formats", formatCount);
// 至少必须有RGBA8888啊,
uint32_t chosenFormat;
for (chosenFormat = 0; chosenFormat < formatCount; chosenFormat++) {
if (formats[chosenFormat].format == VK_FORMAT_R8G8B8A8_UNORM) break;
}
assert(chosenFormat < formatCount);
surfaceFormat结构里有两个字段,format和colorSpace:
- 格式主要sRGB最常见的,SCRGB是FP16表示每个通道,R16G16B16A16。RGB也有两种:最普通的UNORM,和非线性的,后者非常少见。
- 颜色空间,依赖于硬件是否支持,最广泛和古老的格式是sRGB,P3(广色域),BT2020,HDR10,杜比,后面几种在HDR渲染中经常遇到,目前旗舰手机都支持hdr,iphone12是第一款集采集、处理、显示的终端设备。
要正常渲染,至少得有一种格式,R8G8B8A8_UNORM,否则就退出。P40Pro手机查询到支持5种格式,
(VkSurfaceFormatKHR) (format = VK_FORMAT_R8G8B8A8_UNORM, colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
(VkSurfaceFormatKHR) (format = VK_FORMAT_R8G8B8A8_SRGB, colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
(VkSurfaceFormatKHR) (format = VK_FORMAT_R5G6B5_UNORM_PACK16, colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
(VkSurfaceFormatKHR) (format = VK_FORMAT_R16G16B16A16_SFLOAT, colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
(VkSurfaceFormatKHR) (format = VK_FORMAT_A2B10G10R10_UNORM_PACK32, colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
几种格式介绍:
- 常规的至少要有RGBA8888,
- 3th:565是压缩格式,
- 5th:支持HDR显示需要的格式至少1010102,
- 4th:要到HDR-vivid,则得用格式12121212,
3 SurfaceCapabilities
VkSurfaceCapabilitiesKHR surfaceCapabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device.gpuDevice_, device.surface_,&surfaceCapabilities);
主要获取交换链的长度,以及渲染目标的尺寸限制。
3.1 窗口尺寸
相关的三个变量,单位都是像素,跟渲染
VkExtent2D currentExtent; 当前窗口的尺寸,可能无效,
VkExtent2D minImageExtent; 最小尺寸
VkExtent2D maxImageExtent; 最大尺寸
// SectionX
VkExtent2D swap_chain_extent;
if (surfaceCapabilities.currentExtent.width == 0xFFFFFFFF)
{
// the surface size is undefined
// swap_chain_extent.width = std::clamp(swap_chain_extent.width, surfaceCapabilities.minImageExtent.width,
// surfaceCapabilities.maxImageExtent.width);
swap_chain_extent.width = std::min(std::max(window_width, surfaceCapabilities.minImageExtent.width),
surfaceCapabilities.maxImageExtent.width);
swap_chain_extent.height = std::min(std::max(window_height, surfaceCapabilities.minImageExtent.height),
surfaceCapabilities.maxImageExtent.height);
}
else {
swap_chain_extent = surfaceCapabilities.currentExtent;
}
手机上查询结果:
surfaceCapabilities = {VkSurfaceCapabilitiesKHR}
minImageCount = {uint32_t} 2
maxImageCount = {uint32_t} 64
currentExtent = {VkExtent2D}
width = {uint32_t} 800
height = {uint32_t} 1657
minImageExtent = {VkExtent2D}
width = {uint32_t} 1
height = {uint32_t} 1
maxImageExtent = {VkExtent2D}
width = {uint32_t} 4096
height = {uint32_t} 4096
maxImageArrayLayers = {uint32_t} 1
supportedTransforms = {VkSurfaceTransformFlagsKHR} 511
currentTransform = {VkSurfaceTransformFlagBitsKHR} VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
supportedCompositeAlpha = {VkCompositeAlphaFlagsKHR} 8
supportedUsageFlags = {VkImageUsageFlags} 159
swap_chain_extent在用于填充交换链的createInfo结构体。
3.2 交换链策略/VkPresentModeKHR
下面来确定呈现模式,也就是交换链池子中的image使用策略:
- 及时模式,不需要等待垂直同步的周期,直接上屏,可能会导致屏幕撕裂tearing,
- 信箱模式,保证新的图像能被及时送显,内部有1个元素的队列,新的到来直接替换旧的。当队列满时,不会阻塞应用程序,而是将已经排队的图像替换为新图像,能够避免撕裂但是会有能耗的问题。
- FIFO模式,队列缓冲,队列的个数未知,实际效果3缓冲性能优于2缓冲。队列满的时候,应用程序必须等待,
最常听说的是双缓冲模式,android 从4.1引入黄油架构,支持垂直同步、tripple buffer等技术,极大地提升了渲染流畅度。
缓冲数的确定:
// set desired number of swap chain images
uint32_t desired_number_of_swap_chain_images = surfaceCapabilities.minImageCount;
if (desired_number_of_swap_chain_images > surfaceCapabilities.maxImageCount) {
desired_number_of_swap_chain_images = surfaceCapabilities.maxImageCount;
}
// get count of present modes
uint32_t present_mode_count = 0;
hr = vkGetPhysicalDeviceSurfacePresentModesKHR(device.gpuDevice_, device.surface_,
&present_mode_count, nullptr);
std::vector<VkPresentModeKHR> present_modes(present_mode_count);
vkGetPhysicalDeviceSurfacePresentModesKHR(device.gpuDevice_, device.surface_,
&present_mode_count, present_modes.data());
// 如果窗口支持vsync,多个buffer的策略定制
std::vector<VkPresentModeKHR> desired_present_modes;
bool support_vsync = true;
if (support_vsync) {
desired_present_modes.push_back(VK_PRESENT_MODE_FIFO_KHR);
desired_present_modes.push_back(VK_PRESENT_MODE_FIFO_RELAXED_KHR);
desired_present_modes.push_back(VK_PRESENT_MODE_MAILBOX_KHR);
} else {
desired_present_modes.push_back(VK_PRESENT_MODE_IMMEDIATE_KHR);
}
int select_present_mode_found = -1;
for (size_t i = 0; i < desired_present_modes.size(); ++i) {
for (auto avaiable : present_modes) {
if (desired_present_modes[i] == avaiable) {
select_present_mode_found = i;
break;
}
if (select_present_mode_found != -1) break;
}
}
select_present_mode_found 用于交换链CreateInfo填充
4 CompositeAlpha
透明度组合定义窗口半透明的效果,alpha通道是否跟其他窗体混合。
//find supported composite alpha format
VkCompositeAlphaFlagBitsKHR composite_alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
std::vector<VkCompositeAlphaFlagBitsKHR> desired_composite_alphas = {
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
};
for (auto flag : desired_composite_alphas) {
if (surfaceCapabilities.supportedCompositeAlpha & flag) {
composite_alpha = flag;
break;
}
}
关于alpha预乘是个大坑:设计给的美术资源,程序化处理以后的纹理,不同平台的shader处理逻辑(unity、android、ios),一定要高度保持一致,否则CPU、GPU侧代码分支会很多,可维护性会很差。
5 preTransform
处理surface旋转的场景,这东西在手机横竖屏切换场景应用有用,原理和效果还没深度研究。
6 code/创建交换链
void CreateSwapChain(uint32_t window_width, uint32_t window_height) {
LOGI("->createSwapChain");
memset(&swapchain, 0, sizeof(swapchain));
// Section1: check
VkBool32 vk_queue_family_supports_present = false;
auto hr = vkGetPhysicalDeviceSurfaceSupportKHR(device.gpuDevice_, device.queueFamilyIndex_, device.surface_, &vk_queue_family_supports_present);
if (hr != VK_SUCCESS || !vk_queue_family_supports_present) {
LOGE("surface not founded...");
exit(1);
}
// **********************************************************
// Get the surface capabilities because:
// - It contains the minimal and max length of the chain, we will need it
// - It's necessary to query the supported surface format (R8G8B8A8 for
// instance ...)
VkSurfaceCapabilitiesKHR surfaceCapabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device.gpuDevice_, device.surface_,
&surfaceCapabilities);
// Section3 获取支持格式列表
// Query the list of supported surface format and choose one we like
uint32_t formatCount = 0;
hr = vkGetPhysicalDeviceSurfaceFormatsKHR(device.gpuDevice_, device.surface_,
&formatCount, nullptr);
if (hr != VK_SUCCESS || formatCount == 0) {
LOGE("could not get number of physical device formats for device");
exit(1);
}
std::vector<VkSurfaceFormatKHR> formats; formats.resize(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(device.gpuDevice_, device.surface_,
&formatCount, formats);
LOGI("Got %d formats", formatCount);
// 至少必须有RGBA8888啊,
uint32_t chosenFormat;
for (chosenFormat = 0; chosenFormat < formatCount; chosenFormat++) {
if (formats[chosenFormat].format == VK_FORMAT_R8G8B8A8_UNORM) break;
}
assert(chosenFormat < formatCount);
// SectionX
VkExtent2D swap_chain_extent;
if (surfaceCapabilities.currentExtent.width == 0xFFFFFFFF)
{
// the surface size is undefined
// swap_chain_extent.width = std::clamp(swap_chain_extent.width, surfaceCapabilities.minImageExtent.width,
// surfaceCapabilities.maxImageExtent.width);
swap_chain_extent.width = std::min(std::max(window_width, surfaceCapabilities.minImageExtent.width),
surfaceCapabilities.maxImageExtent.width);
swap_chain_extent.height = std::min(std::max(window_height, surfaceCapabilities.minImageExtent.height),
surfaceCapabilities.maxImageExtent.height);
}
else {
swap_chain_extent = surfaceCapabilities.currentExtent;
}
// Section4
swapchain.displaySize_ = swap_chain_extent;
swapchain.displayFormat_ = formats[chosenFormat].format;
//find supported composite alpha format
VkCompositeAlphaFlagBitsKHR composite_alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
std::vector<VkCompositeAlphaFlagBitsKHR> desired_composite_alphas = {
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
};
for (auto flag : desired_composite_alphas) {
if (surfaceCapabilities.supportedCompositeAlpha & flag) {
composite_alpha = flag;
break;
}
}
// set desired number of swap chain images
uint32_t desired_number_of_swap_chain_images = surfaceCapabilities.minImageCount;
if (desired_number_of_swap_chain_images > surfaceCapabilities.maxImageCount) {
desired_number_of_swap_chain_images = surfaceCapabilities.maxImageCount;
}
// get count of present modes
uint32_t present_mode_count = 0;
hr = vkGetPhysicalDeviceSurfacePresentModesKHR(device.gpuDevice_, device.surface_,
&present_mode_count, nullptr);
std::vector<VkPresentModeKHR> present_modes(present_mode_count);
vkGetPhysicalDeviceSurfacePresentModesKHR(device.gpuDevice_, device.surface_,
&present_mode_count, present_modes.data());
// 如果窗口支持vsync,多个buffer的策略定制
std::vector<VkPresentModeKHR> desired_present_modes;
bool support_vsync = true;
if (support_vsync) {
desired_present_modes.push_back(VK_PRESENT_MODE_FIFO_KHR);
desired_present_modes.push_back(VK_PRESENT_MODE_FIFO_RELAXED_KHR);
desired_present_modes.push_back(VK_PRESENT_MODE_MAILBOX_KHR);
} else {
desired_present_modes.push_back(VK_PRESENT_MODE_IMMEDIATE_KHR);
}
int select_present_mode_found = -1;
for (size_t i = 0; i < desired_present_modes.size(); ++i) {
for (auto avaiable : present_modes) {
if (desired_present_modes[i] == avaiable) {
select_present_mode_found = i;
break;
}
if (select_present_mode_found != -1) break;
}
}
// **********************************************************
// Create a swap chain (here we choose the minimum available number of surface
// in the chain)
VkSwapchainCreateInfoKHR swapchainCreateInfo{
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.pNext = nullptr,
.surface = device.surface_,
.minImageCount = desired_number_of_swap_chain_images,
.imageFormat = formats[chosenFormat].format,
.imageColorSpace = formats[chosenFormat].colorSpace,
.imageExtent = surfaceCapabilities.currentExtent,
.imageArrayLayers = 1,
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 1,
.pQueueFamilyIndices = &device.queueFamilyIndex_,
.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
// 动态制定呈现模式
.presentMode = select_present_mode_found == -1 ? present_modes[0] :
desired_present_modes[select_present_mode_found],
.clipped = VK_FALSE,
.oldSwapchain = VK_NULL_HANDLE,
};
CALL_VK(vkCreateSwapchainKHR(device.device_, &swapchainCreateInfo, nullptr,
&swapchain.swapchain_));
// Get the length of the created swap chain
CALL_VK(vkGetSwapchainImagesKHR(device.device_, swapchain.swapchain_,
&swapchain.swapchainLength_, nullptr));