Image Presentation

[TOC]

内容

  • 创建一个激活WSI扩展的Vulkan Instance
  • 创建一个presentation surface
  • 选择一个支持已给surface的queue family
  • 创建一个有WSI扩展的logical device
  • 选择一个期望的presentation mode
  • 获得presentation surface的策略
  • 设置swapchain images的大小
  • 选择一个期望的swapchain iamges的方案
  • 选择swapchain images交换方法
  • 选择swapchain images格式
  • 创建swapchain
  • 获得swapchain images的handles
  • 创建一个R8G8B8A8格式和mailbox显示模式的swapchain
  • 请求一个swapchain image
  • 销毁swapchain
  • 销毁presentation surface

介绍

Vulkan API本身由于跨平台考虑,本身不带有显示生成的图像到窗口的接口,但一组扩展接口(Windowing System Integration(WSI))支持了这种操作.每个支持Vulkan的操作系统有它自己的扩展.

最重要的扩展是允许我们创建一个swapchain.swapchain是一组images,能展示(显示)给用户.

步骤

instance with WSI extensions

WSI extension分为Instance 和device levels.

第一步是创建激活了对应扩展运行创建presentation surface的Vulkan Instance

Instance-level extensions负责管理、创建、销毁一个presentation surface.它是一个软件的窗口的(跨平台的)representation.通过它,我们能检查是否能绘制窗口(显示图片、一个queue family的额外属性),能知道它的参数,它支持什么(如果向垂直同步激活或关闭).

1
2
3
4
5
6
7
8
9
10
11
12
desired_extensions.emplace_back( VK_KHR_SURFACE_EXTENSION_NAME );//所有os都支持,用于管理、删除khr
desired_extensions.emplace_back(
#ifdef VK_USE_PLATFORM_WIN32_KHR
VK_KHR_WIN32_SURFACE_EXTENSION_NAME //windows
#elif defined VK_USE_PLATFORM_XCB_KHR
VK_KHR_XCB_SURFACE_EXTENSION_NAME //linux xcb
#elif defined VK_USE_PLATFORM_XLIB_KHR
VK_KHR_XLIB_SURFACE_EXTENSION_NAME //linux xlib
#endif
);
return CreateVulkanInstance( desired_extensions, application_name, instance
);

创建presentation surface

presentation显示软件的窗口,允许我们获取窗口的参数(比如尺寸,支持的颜色格式,请求的images数量,或者显示模式).

前提:windows已经创建

1
2
3
4
5
6
7
8
9
10
11
12
struct WindowParameters {
#ifdef VK_USE_PLATFORM_WIN32_KHR
HINSTANCE HInstance;
HWND HWnd;
#elif defined VK_USE_PLATFORM_XLIB_KHR
Display * Dpy;
Window Window;
#elif defined VK_USE_PLATFORM_XCB_KHR
xcb_connection_t * Connection;
xcb_window_t Window;
#endif
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#ifdef VK_USE_PLATFORM_WIN32_KHR
VkWin32SurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
nullptr,
0,
window_parameters.HInstance,
window_parameters.HWnd
};
VkResult result = vkCreateWin32SurfaceKHR( instance, &surface_create_info,
nullptr, &presentation_surface );
#elif defined VK_USE_PLATFORM_XLIB_KHR
VkXlibSurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
nullptr,
0,
window_parameters.Dpy,
window_parameters.Window
};
VkResult result = vkCreateXlibSurfaceKHR( instance, &surface_create_info,
nullptr, &presentation_surface );
#elif defined VK_USE_PLATFORM_XCB_KHR
VkXcbSurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR,
nullptr,
0,
window_parameters.Connection,
window_parameters.Window
};
VkResult result = vkCreateXcbSurfaceKHR( instance, &surface_create_info,
nullptr, &presentation_surface );
#endif

选择支持给定表面显示的queue family

显示图像是通过提交特色的command到device的queue实现的.所以要求对应的queue支持.

目前queue family可能支持的特性有:Image presentation,graphics,compute,transfer,sparse operations.

1
2
3
4
5
6
7
8
9
10
11
12
for( uint32_t index = 0; index <
static_cast<uint32_t>(queue_families.size()); ++index ) {
VkBool32 presentation_supported = VK_FALSE;
VkResult result = vkGetPhysicalDeviceSurfaceSupportKHR( physical_device,
index, presentation_surface, &presentation_supported );
if( (VK_SUCCESS == result) &&
(VK_TRUE == presentation_supported) ) {
queue_family_index = index;
return true;
}
}
return false;

通过vkGetPhysicalDeviceSurfaceSupportKHR接口进行检查

logical device with WSI extensions

一个device-level WSI扩展允许创建一个swapchain.这是一组被presentation engine管理的images.

VK_KHR_swapchain

一个swapchain,列举了image format,images 数量(双缓存或三缓存),presentation mode(v-sync 激活/关闭).伴随着swapchain创建的images被presentation engine所有和管理.需要使用时,需要请求,绘制,归还到presentation engine.

1
2
3
desired_extensions.emplace_back( VK_KHR_SWAPCHAIN_EXTENSION_NAME );
return CreateLogicalDevice( physical_device, queue_infos,
desired_extensions, desired_features, logical_device );

选择期望的presentation mode

vulkan的swapchain最重要的特性是将图像显示倒屏幕,也是swap’chain的设计目的.

有四种模式.

最简单的是IMMEDIATE模式.会有屏幕撕裂现象

<<<<<<< HEAD
<<<<<<< HEAD
Vulkan API实现都必须支持的是FIFO模式.

FIFO RELAXED是FIFO的简单变体.不同之处是RELAXED模式,只有当图像显示足够快,比刷新率还快时才会在空白期显示图像到屏幕上.如果应用程序显示了一个图像,并且从上次显示到现在所花费的时间大于两个空白周期之间的刷新时间(FIFO queue为空),图像立即显示.因此如果足够快,这里不会有撕裂,但如果绘制得比屏幕刷新慢,会出现撕裂.这个mode与OpenGL的EXT_swap_control_tear扩展类似.

最后一种为mailbox模式.它可以被看做是三重缓冲.有一个只包含一个元素的队列.一个image在这个队列里等待在空白期同步显示(v-sync激活)显示.但当app显示一张image时,新的一张新的image会替换掉队列里的.所以presentation engine总是显示最新的,没有屏幕撕裂.

检查可用modes.vkGetPhysicalDeviceSurfacePresentModesKHR.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
uint32_t present_modes_count = 0;
VkResult result = VK_SUCCESS;
result = vkGetPhysicalDeviceSurfacePresentModesKHR( physical_device,
presentation_surface, &present_modes_count, nullptr );
if( (VK_SUCCESS != result) ||
(0 == present_modes_count) ) {
std::cout << "Could not get the number of supported present modes." <<
std::endl;
return false;
}
std::vector<VkPresentModeKHR> present_modes( present_modes_count );
result = vkGetPhysicalDeviceSurfacePresentModesKHR( physical_device,
presentation_surface, &present_modes_count, &present_modes[0] );
if( (VK_SUCCESS != result) ||
(0 == present_modes_count) ) {
std::cout << "Could not enumerate present modes." << std::endl;
return false;
}

获得了支持的所有modes后,选择一个期望的.如果不支持则选择默认的FIFO(总被支持).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for( auto & current_present_mode : present_modes ) {
if( current_present_mode == desired_present_mode ) {
present_mode = desired_present_mode;
return true;
}
}
std::cout << "Desired present mode is not supported. Selecting default FIFO
mode." << std::endl;
for( auto & current_present_mode : present_modes ) {
if( current_present_mode == VK_PRESENT_MODE_FIFO_KHR ) {
present_mode = VK_PRESENT_MODE_FIFO_KHR;
return true;
}
}

获得一个presentation surface的功能

当创建一个swapchain时,不能选择我们想要的值而是指定参数.必须提供被presentation surface支持的限制范围内的值.因此为了正确创建swapchain,我们需要获得surface的功能.

1
2
3
4
5
6
7
8
VkResult result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
physical_device, presentation_surface, &surface_capabilities );
if( VK_SUCCESS != result ) {
std::cout << "Could not get the capabilities of a presentation surface."
<< std::endl;
return false;
}
return true;

VkSurfaceCapabilitiesKHR

  • swapchain images的最小和最大允许数量
  • 表面的最小、最大、当前范围
  • 支持图像转换(在显示前应用),其实就是是否进行sRGB转换,可能在不同平台支持特性不同,也就是要注意是否要自己进行转换
  • image layers最大数量
  • 支持的usages
  • 支持曲面的alpha值(图像的alpha组件该如何影响应用程序的窗口桌面合成)的支持的组件列表

内容包括

  • 创建一个presentation surface
  • select swapchain images的个数
  • choose swapchain images的尺寸
  • select swapchain chains的期望的usage
  • select swapchain images的transformation
  • select swapchain images的format
  • 创建一个swapchain

select swapchain iamges的数量

当app想向swapchain image里渲染时,必须向prsentation engine请求它.app可以请求多张images,不限制一次请求一张.但可用的images(presentation engine 没在使用的)数量与presentation mode,app当前状态(渲染、显示的历史),images数量(创建swapchain时指定(最小))有关.

相关结构

VkSurfaceCapabilitiesKHR

.minImageCount,一般将minImageCount.+1作为请求数量

.maxImageCount,如果>0则对能创建的images最大数量有限制,就需要修正请求的images 数量了

伴随swapchain创经济的images主要用作显示目的.但它们也表示引擎正常工作.知道它被替换,app不能使用它(image).images立即替换现实中的image或者在队列里等待替换它(v-sync)–基于选择的mode.app只能请求处于unused 状态的image.(可以请求所有unused状态的images).但同时,需要present至少一张image,否则请求操作会死锁.

未使用的映像的数量主要取决于表示模式和使用swapchain创建的images的总数.因此,我们想要创建的图像的数量应该根据我们想要实现的呈现场景(应用程序想同时拥有多少图像)和所选的当前模式来选择.

请求最小数量的Images:

1
2
3
4
5
6
number_of_images = surface_capabilities.minImageCount + 1;
if( (surface_capabilities.maxImageCount > 0) &&
(number_of_images > surface_capabilities.maxImageCount) ) {
number_of_images = surface_capabilities.maxImageCount;
}
return true;

Vulkan API实现都必须支持的是FIFO模式.

选择需求需要的images数量

choose swapchain images size *

通常要适合window大小,支持的dimensions再presentation surface的属性里有.但有的操作系统,iamges的size决定了最终window的大小.

同时也要记住去检查swapchain images的适合的dimensions.

相关结构

VkSurfaceCapabilitiesKHR

VkExtent2D

检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//如果surface_capabilities.currentExtent.width==-1
//则image的size决定windows的size
if( 0xFFFFFFFF == surface_capabilities.currentExtent.width )
{
//这种情况就根据surface 属性调节image size
size_of_images = { 640, 480 };
//范围检查
if( size_of_images.width < surface_capabilities.minImageExtent.width )
{
size_of_images.width = surface_capabilities.minImageExtent.width;
}
else if( size_of_images.width >
surface_capabilities.maxImageExtent.width )
{
size_of_images.width = surface_capabilities.maxImageExtent.width;
}

if( size_of_images.height < surface_capabilities.minImageExtent.height )
{
size_of_images.height = surface_capabilities.minImageExtent.height;
}
else if( size_of_images.height > surface_capabilities.maxImageExtent.height )
{
size_of_images.height = surface_capabilities.maxImageExtent.height;
}
}
else
{
//这种情况就将surface的size作为images的size
size_of_images = surface_capabilities.currentExtent;//currentExtent是创建的windows尺寸
}
return true;

​ 正常情况是将windows size作为images的size,但有的os是swapchain images的尺寸决定的.

select 所需的swapchain iamges使用场景

伴随swapchain创建的images常用作color attachments(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT).也就是说我们想向它们渲染东西(RT).但不限于此.也有其他用处:可以进行采样,在copy操作时作为数据源,或者作为拷贝目标.这些都是在创建swapchain时的不同的使用usages,但是需要检查usages是否支持.

1
2
image_usage = desired_usages & surface_capabilities.supportedUsageFlags;
return desired_usages == image_usage;

select a transformation of swapchain images

有的(尤其是手机)设备,images能从不同orientations看,有时需要控制图像显示到屏幕上时面向.Vulkan能做到这点,能在显示前指定图像的转换.

相关结构

VkSurfaceTransformFlagBitsKHR

Transformations定义了image在显示到屏幕前如何旋转、镜像.在swapchain创建时,能够指定期望的transformation和presentation engine,并作为显示过程的一部分.

1
2
3
4
5
if( surface_capabilities.supportedTransforms & desired_transform ) {
surface_transform = desired_transform;
} else {
surface_transform = surface_capabilities.currentTransform;
}

select 一种swapchain images 格式

format定义了color分量的数量和每个分量的bits和数据类型.在创建swapchain时

需要决定使用的颜色通道

是否使用uint或float类型

精度

线性、非线性颜色.

​ 但只能选择被支持的特性.

相关结构

VkFormat

VkColorSpaceKHR

VkSurfaceFormatKHR

VkSurfaceKHR

获得所有支持的formats,调用两次vkGetPhysicalDeviceSurfaceFormatsKHR,存在列表VkSurfaceFormatKHR里,如果只返回一个VK_FORMAT_UNDEFINED,也就是说对format没有限制.

1
2
3
4
5
6
if( (1 == surface_formats.size()) &&
(VK_FORMAT_UNDEFINED == surface_formats[0].format) ) {
image_format = desired_surface_format.format;
image_color_space = desired_surface_format.colorSpace;
return true;
}

当返回一系列的VkSurfaceFormatKHR时,需要选择image format好color space都支持的.

1
2
3
4
5
6
7
8
for( auto & surface_format : surface_formats ) {
if( (desired_surface_format.format == surface_format.format) &&
(desired_surface_format.colorSpace == surface_format.colorSpace) ) {
image_format = desired_surface_format.format;
image_color_space = desired_surface_format.colorSpace;
return true;
}
}

最后,如果期望公式不支持,选择第一个吧

1
2
3
4
5
image_format = surface_formats[0].format;
image_color_space = surface_formats[0].colorSpace;
std::cout << "Desired format is not supported. Selecting available format -
colorspace combination." << std::endl;
return true;

创建swapchain

一个swapchain用于显示images到屏幕上.那是一组能被app请求并显示到app窗口上的images.它们有相同的属性(properties).当准备好所有的参数:数量,size,format,swapchain images的使用usage,选择一个支持的先试试modes,就可以创建swapchain了.

一个swapchain是一组Images,伴随swapchain自动创建、销毁.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
VkSwapchainCreateInfoKHR swapchain_create_info = {
VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
nullptr,
0,
presentation_surface,//surface
image_count,//minImageCount
surface_format.format,
surface_format.colorSpace,//imageColorSpace
image_size,
1,//imageArrayLayers
image_usage,
VK_SHARING_MODE_EXCLUSIVE,//imageSharingMode
0,//queueFamilyIndexCount
nullptr,
surface_transform,
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
present_mode,
VK_TRUE,
old_swapchain
};
VkResult result = vkCreateSwapchainKHR( logical_device,
&swapchain_create_info, nullptr, &swapchain );
if( (VK_SUCCESS != result) ||
(VK_NULL_HANDLE == swapchain) ) {
std::cout << "Could not create a swapchain." << std::endl;
return false;
}
1
vkDestroySwapchainKHR

一个app的窗口只能关联一个swapchain,当创建一个新的swapchain时,需要销毁之前为这个窗口创建的swapchain.

1
2
3
4
if( VK_NULL_HANDLE != old_swapchain ) {
vkDestroySwapchainKHR( logical_device, old_swapchain, nullptr );
old_swapchain = VK_NULL_HANDLE;
}

获得swapchain images的handles

vkGetSwapchainImagesKHR

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
uint32_t images_count = 0;
VkResult result = VK_SUCCESS;
result = vkGetSwapchainImagesKHR( logical_device, swapchain, &images_count,
nullptr );
if( (VK_SUCCESS != result) ||
(0 == images_count) ) {
std::cout << "Could not get the number of swapchain images." <<
std::endl;
return false;
}
swapchain_images.resize( images_count );
result = vkGetSwapchainImagesKHR( logical_device, swapchain, &images_count,
&swapchain_images[0] );
if( (VK_SUCCESS != result) ||
(0 == images_count) ) {
std::cout << "Could not enumerate swapchain images." << std::endl;
return false;
}
return true;

驱动可能创建多余创建swapchain时传参数量的images.我们设置了最小数量,但vulkan实现时可能创建更多.

vulkan中,如果想绘制一个image需要有它的handle.需要创建一个包裹image的image view且在创建framebuffer时用到.framebuffer时一组在渲染过程中用到的images.

得到得是一个数而不是handle本身.这个数字表示使用vkGetSwapchainImagesKHR得到的images数组的索引.因此了解images总数、顺序、handles对于正确使用swapchain和images很必要.

创建一个swapchain(R8G8B8A8 format & mailbox显示模式)

无transformations,标准color attachment image usage.

已有设施

1
2
3
4
5
6
VkPhysicalDevice physical_device;
VkSurfaceKHR presentation_surface;
VkDevice logical_device;
VkSwapchainKHR old_swapchain;
VkPresentModeKHR desired_present_mode;
VkSurfaceCapabilitiesKHR surface_capabilities;
1
2
3
uint32_t number_of_images;
VkExtent2D image_size;
VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
VkPresentModeKHR desired_present_mode;
if( !SelectDesiredPresentationMode( physical_device, presentation_surface,
VK_PRESENT_MODE_MAILBOX_KHR, desired_present_mode ) ) {
return false;
}
VkSurfaceCapabilitiesKHR surface_capabilities;
if( !GetCapabilitiesOfPresentationSurface( physical_device,
presentation_surface, surface_capabilities ) ) {
return false;
}
uint32_t number_of_images;
if( !SelectNumberOfSwapchainImages( surface_capabilities, number_of_images
) ) {
return false;
}
VkExtent2D image_size;
if( !ChooseSizeOfSwapchainImages( surface_capabilities, image_size ) ) {
return false;
}
if( (0 == image_size.width) ||
(0 == image_size.height) ) {
return true;
}
VkImageUsageFlags image_usage;
if( !SelectDesiredUsageScenariosOfSwapchainImages( surface_capabilities,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, image_usage ) ) {
return false;
}
VkSurfaceTransformFlagBitsKHR surface_transform;
SelectTransformationOfSwapchainImages( surface_capabilities,
VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, surface_transform );
VkFormat image_format;
VkColorSpaceKHR image_color_space;
if( !SelectFormatOfSwapchainImages( physical_device, presentation_surface,
{ VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR },
image_format, image_color_space ) ) {
return false;
}


if( !CreateSwapchain( logical_device, presentation_surface,
number_of_images, { image_format, image_color_space }, image_size,
image_usage, surface_transform, desired_present_mode, old_swapchain,
swapchain ) ) {
return false;
}
if( !GetHandlesOfSwapchainImages( logical_device, swapchain,
swapchain_images ) ) {
return false;
}
return true;

获得swapchain iamge

vkGetSwapchainImagesKHR

semaphores和fences

semphores用于同步device的queues.不能用于同步app的commands提交.

fences app

1
2
3
4
5
6
7
8
9
10
VkResult result;
result = vkAcquireNextImageKHR( logical_device, swapchain, 2000000000,
semaphore, fence, &image_index );
switch( result ) {
case VK_SUCCESS:
case VK_SUBOPTIMAL_KHR:
return true;
default:
return false;
}

在immediate模式下,可能images都不可用,也就是可能失败.第三个参数是一个超时参数(ns),

如果要让驱动在处理commands前进行等待,semaphore会用到.

app侧等待性能影响更大.

返回值也很重要

如果返回VK_SUBOPTIMAL_KHR,意味着我们能用这个image,但它不再最适合presentation engine.需要重新创建swapchain.但不必立即做.

当返回VK_ERROR_OUT_OF_DATE_KHR时,image就不能用了,我们需要立即重建swapchain.

对于swapchain最后需要注意的时在能使用一张image前,我们需要改变(transition)它的layout,layout时image的内部内存组织–可能跟当前目的不同.如果想用于不同目的就修改它的layout.

比如,用于presentation engine的images必须有VK_IMAGE_LAYOUT_PRESENT_SRC_KHR层.如果用于渲染必须有VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,改变layout的操作称为transition.

present an image

1
2
3
4
struct PresentInfo {
VkSwapchainKHR Swapchain;
uint32_t ImageIndex;//想显示的image index
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
VkPresentInfoKHR present_info = {
VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
nullptr,
static_cast<uint32_t>(rendering_semaphores.size()),
rendering_semaphores.size() > 0 ? &rendering_semaphores[0] : nullptr,
static_cast<uint32_t>(swapchains.size()),
swapchains.size() > 0 ? &swapchains[0] : nullptr,
swapchains.size() > 0 ? &image_indices[0] : nullptr,
nullptr
};
result = vkQueuePresentKHR( queue, &present_info );
switch( result ) {
case VK_SUCCESS:
return true;
default:
return false;
}

在提交image前,需要修改其layout为VK_IMAGE_LAYOUT_PRESENT_SRC_KHR否则presentation engine可能无法显示它.

当提交命令时,rendering_semaphores用于同步

destroy

1
2
3
4
if( swapchain ) {
vkDestroySwapchainKHR( logical_device, swapchain, nullptr );
swapchain = VK_NULL_HANDLE;
}
1
2
3
4
if( presentation_surface ) {
vkDestroySurfaceKHR( instance, presentation_surface, nullptr );
presentation_surface = VK_NULL_HANDLE;
}