vulakn教程--Drawing a Triangle--Presentation--SwapChain

发表于2016-12-09
评论0 835浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏程序行业精英群

711501594

原文链接: Vulakn-tutorial


SwapChain

这一章节我们将学习这样一种结构/基础(infrastructure),它能为我们提供要渲染的图片,然后渲染结的果可以显到屏幕上。这样的结构就是Swap Chain , Swap Chain必须被Vulkan显示的创建。从本质上讲,Swap Chain就是一个图片的队列(a queue of images),这里的图片等着被显示到屏幕上。我们的应用将会获得一个图片,然后绘画它,之后将它提交到队列中去。Swap Chain 通常的作用是通过屏幕刷新率(refresh rate of the screen)来同步控制图片的显示。

检查显卡是否支持 Swap chain

显卡有各种理由阻止你将images直接提交给屏幕,例如系统只是个服务器(Server),不支持显示。其次,图片显示和窗口系统(window system)以及和窗口相连的window surface密切相关。所以Swap Chain并不属于核心Vulkan 功能。这就要求必须支持VK_KHR_swapchain扩展。

我们需要检查显卡是否支持Swap chain,首先我们要有能力获取显卡的所有扩展:

1
2
3
4
5
VkResult vkEnumerateDeviceExtensionProperties(
    VkPhysicalDevice physicalDevice,
    const char* pLayerName,
    uint32_t* pPropertyCount,
    VkExtensionProperties* pProperties);

现在,你肯定对这种结构再熟悉不过了。pLayerName是一个过滤选项,它是一个Validation layer的名字,表示只枚举这个Validation layer所对应的所有扩展,如果pLayerName为nullptr (NULL),表示要获取对应physicalDevice下的所有扩展。

Vulkan.h 下有如下定义:

1
#define VK_KHR_SWAPCHAIN_EXTENSION_NAME   "VK_KHR_swapchain"

physicalDevice 必须支持此扩展,我们才能创建Swap Chain. 
添加:

1
2
3
const std::vector<const char*=""> deviceExtensions = {
    VK_KHR_SWAPCHAIN_EXTENSION_NAME
};const>

修改isDeviceSuitable(…):

1
2
3
4
5
bool isDeviceSuitable(VkPhysicalDevice device) {
    QueueFamilyIndices indices = findQueueFamilies(device);
    bool extensionsSupported = checkDeviceExtensionSupport(device);
    return indices.isComplete() && extensionsSupported;
}

添加检查支持deviceExtensions扩展的函数checkDeviceExtensionSupport:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
    uint32_t extensionCount;
    vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
 
    std::vector availableExtensions(extensionCount);
    vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
 
    std::setstring> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());
 
    for (const auto& extension : availableExtensions) {
        requiredExtensions.erase(extension.extensionName);
    }
 
    return requiredExtensions.empty();
}

修改在创建Logical Device时的VkDeviceCreateInfo createInfo = {};结构,添加extensions选项:

1
2
createInfo.enabledExtensionCount = deviceExtensions.size();
createInfo.ppEnabledExtensionNames = deviceExtensions.data();

查询Swap Chain的支持细节

只是检查显卡是否支持Swap Chain还不够,因为Swap Chain还可能和我们的window surface不兼容。创建一个Swap Chain还需要一系列配置工作,比创建Instance和Logical Device复杂多了。 
所以我们还要检查以下三种属性:

  1. Surface 的性能(Capabilities)(比如 : min/max number of images in swap chain, min/max width and height of images)。

  2. Surface 的格式(formats)(比如 : pixel format, color space)。

  3. 可用的显示模式(present mode)。

如同QueueFamilyIndices,定义结构SwapChainSupportDetails:

1
2
3
4
5
struct SwapChainSupportDetails {
    VkSurfaceCapabilitiesKHR capabilities;
    std::vector formats;
    std::vector presentModes;
};

VkSurfaceCapabilitiesKHR

先从VkSurfaceCapabilitiesKHR 开始介绍吧。

VkSurfaceCapabilitiesKHR 结构:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct VkSurfaceCapabilitiesKHR {
    uint32_t                         minImageCount;
    uint32_t                         maxImageCount;
    VkExtent2D                       currentExtent;
    VkExtent2D                       minImageExtent;
    VkExtent2D                       maxImageExtent;
    uint32_t                         maxImageArrayLayers;
    VkSurfaceTransformFlagsKHR       supportedTransforms;
    VkSurfaceTransformFlagBitsKHR    currentTransform;
    VkCompositeAlphaFlagsKHR         supportedCompositeAlpha;
    VkImageUsageFlags                supportedUsageFlags;
} VkSurfaceCapabilitiesKHR;

VkExtent2D 的结构比较简单:

1
2
3
4
typedef struct VkExtent2D {
    uint32_t    width;
    uint32_t    height;
} VkExtent2D;

VkSurfaceTransformFlagsKHR同VkSurfaceTransformFlagBitsKHR表示如何转换图片:

1
2
3
4
5
6
7
8
9
10
11
12
typedef enum VkSurfaceTransformFlagBitsKHR {
    VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR = 0x00000001,
    VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR = 0x00000002,
    VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR = 0x00000004,
    VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR = 0x00000008,
    VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_BIT_KHR = 0x00000010,
    VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR = 0x00000020,
    VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_180_BIT_KHR = 0x00000040,
    VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR = 0x00000080,
    VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR = 0x00000100,
    VK_SURFACE_TRANSFORM_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
} VkSurfaceTransformFlagBitsKHR;

获取VkSurfaceCapabilitiesKHR:

1
2
3
4
VkResult vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
    VkPhysicalDevice physicalDevice,
    VkSurfaceKHR surface,
    VkSurfaceCapabilitiesKHR* pSurfaceCapabilities);

VkSurfaceFormatKHR

VkSurfaceFormatKHR 结构:

1
2
3
4
typedef struct VkSurfaceFormatKHR {
    VkFormat           format;
    VkColorSpaceKHR    colorSpace;
} VkSurfaceFormatKHR;

VkFormat 一个非常庞大的结构:

1
2
3
4
5
6
7
8
9
10
typedef enum VkFormat {
    VK_FORMAT_UNDEFINED = 0,
    VK_FORMAT_R4G4_UNORM_PACK8 = 1,
    VK_FORMAT_R4G4B4A4_UNORM_PACK16 = 2,
    VK_FORMAT_B4G4R4A4_UNORM_PACK16 = 3,
    VK_FORMAT_R5G6B5_UNORM_PACK16 = 4,
    VK_FORMAT_B5G6R5_UNORM_PACK16 = 5,
    ...
    ...
} VkFormat;

VkColorSpaceKHR :

1
2
3
typedef enum VkColorSpaceKHR {
    VK_COLOR_SPACE_SRGB_NONLINEAR_KHR = 0,
} VkColorSpaceKHR;

VkColorSpaceKHR 表示是否支持SRGB颜色,是观察者获取更加精确的颜色感知效果。(more accurate perceived colors).

获取支持的VkSurfaceFormatKHR:

1
2
3
4
5
VkResult vkGetPhysicalDeviceSurfaceFormatsKHR(
    VkPhysicalDevice physicalDevice,
    VkSurfaceKHR surface,
    uint32_t* pSurfaceFormatCount,
    VkSurfaceFormatKHR* pSurfaceFormats);

VkPresentModeKHR

VkPresentModeKHR 结构:

1
2
3
4
5
6
7
8
typedef enum VkPresentModeKHR {
    VK_PRESENT_MODE_IMMEDIATE_KHR = 0,
    VK_PRESENT_MODE_MAILBOX_KHR = 1,
    VK_PRESENT_MODE_FIFO_KHR = 2,
    VK_PRESENT_MODE_FIFO_RELAXED_KHR = 3,
    ...
    VK_PRESENT_MODE_MAX_ENUM_KHR = 0x7FFFFFFF
} VkPresentModeKHR;

VkPresentModeKHR 指显示模式。

获取支持的VkPresentModeKHR:

1
2
3
4
5
VkResult vkGetPhysicalDeviceSurfacePresentModesKHR(
    VkPhysicalDevice physicalDevice,
    VkSurfaceKHR surface,
    uint32_t* pPresentModeCount,
    VkPresentModeKHR* pPresentModes);

获取Swap Chain支持

添加函数 :

1
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device);

实现 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
    SwapChainSupportDetails details;
    // Capabilities
    vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
    //formats
    uint32_t formatCount;
    vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
    if (formatCount != 0) {
        details.formats.resize(formatCount);
        vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
    }
    // presentMode
    uint32_t presentModeCount;
    vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);
 
    if (presentModeCount != 0) {
        details.presentModes.resize(presentModeCount);
        vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
    }
    return details;
}

现在修改选择Physical Device时的 isDeviceSuitable(…) ,使它具有检测Swap Chain的各种支持的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
bool isDeviceSuitable(VkPhysicalDevice device) {
    QueueFamilyIndices indices = findQueueFamilies(device);
//支持swap chain ?
    bool extensionsSupported = checkDeviceExtensionSupport(device);
//swap chain的细节特性也支持? Format,mode...?
    bool swapChainAdequate = false;
    if (extensionsSupported) {
        SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); //调用此函数
        swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
    }
 
    return indices.isComplete() && extensionsSupported && swapChainAdequate;
}

只要有一个format和presentMode我们就认为此显卡支持Swap Chain 并且兼容surface。 现在我们的Physical Device 越发强大。

为Swap Chain 选择最佳设置

如果swapChainAdequate的值为true.那么我们有理由肯定Swap Chain已经被支持了。而且Swap Chain 的所有特性支持也都被写入swapChainSupport变量中了。但是针对不同的优化有着不同的设置选项,为了寻找最佳的Swap Chain设置,我们决定从以下三个方面入手:

  1. Surface (格式)format (如:color depth)
  2. 显示模式(Presentation mode)(如:渲染后的图片“交换”到显示器的时机).
  3. 交换的大小(Swap extent)(如:图片在swap chain里的分辨率)

Format的选择

正如之前所说,所有被支持的Format都已存在 swapChainSupport.fromats中,每一个Format是一个VkSurfaceFormatKHR结构,包含format 和 colorSpace。

我们的需求: 
format 为:VK_FORMAT_B8G8R8A8_UNORM ,因为这种颜色比较通用; 
colorSpace 为:VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,即支持SRGB颜色。

定义函数:

1
2
3
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) {
 
}

swapChainSupport.fromats 将作为参数传递进去。

这里有三个选择方案: 
plan A: 
如果surface没有首选的Format,则只会有一个Format,且Format.format值为VK_FORMAT_UNDEFINED,那么我们就按我们的需求来返回Format:

1
2
3
if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) {
    return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
}

Plan B: 
如果我们不能自由的选择,Format, 那么我们就从Format的列表中查找是否有我们感兴趣的Format:

1
2
3
4
5
for (const auto& availableFormat : availableFormats) {
    if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
        return availableFormat;
    }
}

Plan C: 
如果plan A 和 plane B 都失败了,我们可以将Format列表中的Format根据某种最佳(“good”)准则进行排序,然后选择最好的。但我不想这么复杂,因为Format列表中的第一个往往就能满足我们的需求:

1
return availableFormats[0];  //直接 返回第一个Format

将上述思想综合起来,我们将得到 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) {
// plan A
    if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) {
        return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
    }
// plan B
    for (const auto& availableFormat : availableFormats) {
        if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
            return availableFormat;
        }
    }
// plan C
    return availableFormats[0]; 
}

选择 Presentation mode

正如之前所列出的,我们所列出的,VkpresentModeKHR 有:

1
2
3
4
5
VK_PRESENT_MODE_IMMEDIATE_KHR = 0, //单  ,立即显示,可能出现撕裂
VK_PRESENT_MODE_MAILBOX_KHR = 1, // 三缓冲
VK_PRESENT_MODE_FIFO_KHR = 2,  //双
VK_PRESENT_MODE_FIFO_RELAXED_KHR = 3, //类似 FIFO
…...  详细参阅文档 ..


因为VK_PRESENT_MODE_FIFO_KHR 一定是可以得到的,又可以避免撕裂,所以presentMode 我们选择VK_PRESENT_MODE_FIFO_KHR

但我个人认为三缓冲更加绝妙(nice),可以先看看有没有它,所以我们的实现是:

1
2
3
4
5
6
7
8
9
VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) {
    for (const auto& availablePresentMode : availablePresentModes) {
        if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
            return availablePresentMode;
        }
    }
 
    return VK_PRESENT_MODE_FIFO_KHR;
}

Swap extent

现在就剩下一个最重要的属性需要设置了,extent 是Swap Chain中image的分辨率(resolution) ,通常它与window的尺寸一样,vulkan让我们通过设置currentExtent设置widthheight来匹配window的分辨率。但是有些window Manager会将currentExtent设置为uint32_t的最大值,来表示允许我们设置不同的值,这个时候我们可以从minImageExtentmaxImageExtent中选择最匹配window的尺寸值。

实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
    if (capabilities.currentExtent.width != std::numeric_limits::max()) {
        return capabilities.currentExtent;
} else {
        // window 的尺寸
        VkExtent2D actualExtent = {WIDTH, HEIGHT};
 
        actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width));
        actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height));
 
        return actualExtent;
    }
}

以上逻辑还是很好理解的:

  1. currentExtent.width/height 任何一个值不是uint32_t的最大值时,直接选择Capabilities.currentExtentcurrentExtent最符合window尺寸。

  2. 否则,从Window 尺寸和支持的最大尺寸(maxImageExtent)取最小的,因为尺寸不能超过window,也不能超过capabilities支持的最大尺寸。最后为了得到最好的效果,取结果值和minImageExtent的最大值。

创建Swap Chain

现在我们已经获得了所有创建Swap Chain的必要信息,接下来就开始创建Swap Chain 吧。

首先我们获取上一步的操作结果,它们包含了我们应该设置的参数:

1
2
3
4
5
6
7
8
void createSwapChain() {
    SwapChainSupportDetails swapChainSupport =  querySwapChainSupport(physicalDevice);
    VkSurfaceFormatKHR  surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
    VkPresentModeKHR presentMode =  chooseSwapPresentMode(swapChainSupport.presentModes);
    VkExtent2D  extent =chooseSwapExtent(swapChainSupport.capabilities);
    ...
    ...
}

设置Swap Chain 中image的数量,本质上是指队列的长度:

1
2
3
4
5
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
 
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
    imageCount = swapChainSupport.capabilities.maxImageCount;
}

minImageCount 个image 已经十分合适了,但是为了更好的支持三缓冲,我们又多加了一个。maxImageCount 如果为0, 表示不对最大数量做任何限制。

作为创建Vulkan对象的惯例,在创建Swap Chain 之前我们需要填充一个大的结构体:

1
2
3
4
5
6
VkSwapchainCreateInfoKHR createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface;
createInfo.minImageCount = imageCount;
createInfo.imageFormat = surfaceFormat.format; createInfo.imageColorSpace = surfaceFormat.colorSpace; createInfo.imageExtent = extent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

imageArrayLayers 表示image的层次,除非创建3D应用,否则这个值将为1. 
imageUsage我们要使用Swap Chain里的image做什么操作,在这个教程中我们将直接对image进行渲染,这就意味着Image将被当做颜色附件使用(color attachment)。如果你想先渲染一个单独的图片然后再进行处理,那就应该使用VK_IMAGE_USAGE_TRANSFER_DST_BIT并使用内存转换操作将渲染好的image 转换到SwapChain里。VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT 表示image可以用作创建VkImageView,在VkFrameBuffer中适合使用color 或者 reslove attachment.

1
2
3
4
5
6
7
8
9
10
11
QueueFamilyIndices indices = findQueueFamilies(physicalDevice); uint32_t queueFamilyIndices[] = {
(uint32_t) indices.graphicsFamily,
(uint32_t) indices.presentFamily
};
 
if (indices.graphicsFamily != indices.presentFamily) {
    createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;       createInfo.queueFamilyIndexCount = 2;
createInfo.pQueueFamilyIndices = queueFamilyIndices;
} else {
    createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;    createInfo.queueFamilyIndexCount = 0; // Optional   createInfo.pQueueFamilyIndices = nullptr; // Optional
}

imageSharingMode 表示多种队列中,image如何使用,如果grapics queue 和 present queue不相同,就会出现这多种队列访问image的情况:我们在grapics queue 中绘画image,然后将它提交到presention queue 去等待显示。 
imageSharingMode 的取值为:

1
2
VK_SHARING_MODE_EXCLUSIVE : image 一段时间内只能属于一种队列,所有权的转换必须明确声明,这个选项可以提供较好的性能。
VK_SHARING_MODE_CONCURRENT : image 可以跨多种队列使用,所有权的转换不必明确声明。

如果grapics queue 和 present queue 不同,我们将采用Concurrent模式,主要目的是避免明确的所有权的转换,这一概念现在不讲。如果两个queue 种类相同,我们采用Exclusive模式,因为Concurrent模式需要至少两个不同种类的队列。

1
2
3
4
5
6
7
8
// 不对Image  变换
createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
// 忽略和其他窗口颜色混合时的Alpha 通道
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
 
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE; // 不处理那些被遮盖的像素
createInfo.oldSwapchain = VK_NULL_HANDLE; // 暂时不用置空即可

最后创建 Swap Chain :

1
2
3
4
5
VkSwapchainKHR swapChain;  // 声明
 
if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
throw std::runtime_error("failed to create swap chain!");
}


声明几个变量,我们在接下来的步骤要用到:

1
2
3
4
5
std::vector swapChainImages;
VkFormat swapChainImageFormat;
VkExtent2D swapChainExtent;
swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;

swapChain里的imageswapChain一起创建、一起销毁。 
获取swapChain 里所有的images:

1
2
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount,     swapChainImages.data());

Image 的清理工作将随着SwapChain的清理而被清理。


源码:

源码太多了,后续源码不再贴在这里。

原文源码地址: source code

原文链接

著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

游戏学院公众号二维码
腾讯游戏学院
微信公众号

提供更专业的游戏知识学习平台