vulakn教程--Drawing a Triangle--Presentation--SwapChain
来源:互联网 发布:kettle使用JAVA入库 编辑:程序博客网 时间:2024/05/06 06:35
原文链接: 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,首先我们要有能力获取显卡的所有扩展:
VkResult vkEnumerateDeviceExtensionProperties( VkPhysicalDevice physicalDevice, const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties);
现在,你肯定对这种结构再熟悉不过了。pLayerName是一个过滤选项,它是一个Validation layer的名字,表示只枚举这个Validation layer所对应的所有扩展,如果pLayerName为nullptr (NULL),表示要获取对应physicalDevice下的所有扩展。
Vulkan.h 下有如下定义:
#define VK_KHR_SWAPCHAIN_EXTENSION_NAME "VK_KHR_swapchain"
physicalDevice 必须支持此扩展,我们才能创建Swap Chain.
添加:
const std::vector<const char*> deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME};
修改isDeviceSuitable(…):
bool isDeviceSuitable(VkPhysicalDevice device) { QueueFamilyIndices indices = findQueueFamilies(device); bool extensionsSupported = checkDeviceExtensionSupport(device); return indices.isComplete() && extensionsSupported;}
添加检查支持deviceExtensions扩展的函数checkDeviceExtensionSupport:
bool checkDeviceExtensionSupport(VkPhysicalDevice device) { uint32_t extensionCount; vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); std::vector<VkExtensionProperties> availableExtensions(extensionCount); vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); for (const auto& extension : availableExtensions) { requiredExtensions.erase(extension.extensionName); } return requiredExtensions.empty();}
修改在创建Logical Device时的VkDeviceCreateInfo createInfo = {};
结构,添加extensions选项:
createInfo.enabledExtensionCount = deviceExtensions.size();createInfo.ppEnabledExtensionNames = deviceExtensions.data();
查询Swap Chain的支持细节
只是检查显卡是否支持Swap Chain还不够,因为Swap Chain还可能和我们的window surface不兼容。创建一个Swap Chain还需要一系列配置工作,比创建Instance和Logical Device复杂多了。
所以我们还要检查以下三种属性:
Surface 的性能(Capabilities)(比如 : min/max number of images in swap chain, min/max width and height of images)。
Surface 的格式(formats)(比如 : pixel format, color space)。
可用的显示模式(present mode)。
如同QueueFamilyIndices,定义结构SwapChainSupportDetails:
struct SwapChainSupportDetails { VkSurfaceCapabilitiesKHR capabilities; std::vector<VkSurfaceFormatKHR> formats; std::vector<VkPresentModeKHR> presentModes;};
VkSurfaceCapabilitiesKHR
先从VkSurfaceCapabilitiesKHR 开始介绍吧。
VkSurfaceCapabilitiesKHR 结构:
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 的结构比较简单:
typedef struct VkExtent2D { uint32_t width; uint32_t height;} VkExtent2D;
VkSurfaceTransformFlagsKHR同VkSurfaceTransformFlagBitsKHR表示如何转换图片:
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:
VkResult vkGetPhysicalDeviceSurfaceCapabilitiesKHR( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR* pSurfaceCapabilities);
VkSurfaceFormatKHR
VkSurfaceFormatKHR 结构:
typedef struct VkSurfaceFormatKHR { VkFormat format; VkColorSpaceKHR colorSpace;} VkSurfaceFormatKHR;
VkFormat 一个非常庞大的结构:
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 :
typedef enum VkColorSpaceKHR { VK_COLOR_SPACE_SRGB_NONLINEAR_KHR = 0,} VkColorSpaceKHR;
VkColorSpaceKHR 表示是否支持SRGB颜色,是观察者获取更加精确的颜色感知效果。(more accurate perceived colors).
获取支持的VkSurfaceFormatKHR:
VkResult vkGetPhysicalDeviceSurfaceFormatsKHR( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pSurfaceFormatCount, VkSurfaceFormatKHR* pSurfaceFormats);
VkPresentModeKHR
VkPresentModeKHR 结构:
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:
VkResult vkGetPhysicalDeviceSurfacePresentModesKHR( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pPresentModeCount, VkPresentModeKHR* pPresentModes);
获取Swap Chain支持
添加函数 :
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device);
实现 :
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的各种支持的功能:
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设置,我们决定从以下三个方面入手:
- Surface (格式)format (如:color depth)
- 显示模式(Presentation mode)(如:渲染后的图片“交换”到显示器的时机).
- 交换的大小(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颜色。
定义函数:
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {}
swapChainSupport.fromats
将作为参数传递进去。
这里有三个选择方案:
plan A:
如果surface没有首选的Format,则只会有一个Format,且Format.format值为VK_FORMAT_UNDEFINED,那么我们就按我们的需求来返回Format:
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:
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列表中的第一个往往就能满足我们的需求:
return availableFormats[0]; //直接 返回第一个Format
将上述思想综合起来,我们将得到 :
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& 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
有:
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),可以先看看有没有它,所以我们的实现是:
VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR> 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
设置width
和height
来匹配window
的分辨率。但是有些window Manager会将currentExtent
设置为uint32_t
的最大值,来表示允许我们设置不同的值,这个时候我们可以从minImageExtent
和maxImageExtent
中选择最匹配window
的尺寸值。
实现如下:
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::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; }}
以上逻辑还是很好理解的:
当
currentExtent.width/height
任何一个值不是uint32_t
的最大值时,直接选择Capabilities.currentExtent
,currentExtent
最符合window
尺寸。否则,从Window 尺寸和支持的最大尺寸(
maxImageExtent
)取最小的,因为尺寸不能超过window,也不能超过capabilities
支持的最大尺寸。最后为了得到最好的效果,取结果值和minImageExtent
的最大值。
创建Swap Chain
现在我们已经获得了所有创建Swap Chain的必要信息,接下来就开始创建Swap Chain 吧。
首先我们获取上一步的操作结果,它们包含了我们应该设置的参数:
void createSwapChain() { SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); VkExtent2D extent =chooseSwapExtent(swapChainSupport.capabilities); ... ...}
设置Swap Chain 中image的数量,本质上是指队列的长度:
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 之前我们需要填充一个大的结构体:
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.
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
的取值为:
VK_SHARING_MODE_EXCLUSIVE : image 一段时间内只能属于一种队列,所有权的转换必须明确声明,这个选项可以提供较好的性能。VK_SHARING_MODE_CONCURRENT : image 可以跨多种队列使用,所有权的转换不必明确声明。
如果grapics queue 和 present queue 不同,我们将采用Concurrent模式,主要目的是避免明确的所有权的转换,这一概念现在不讲。如果两个queue 种类相同,我们采用Exclusive模式,因为Concurrent模式需要至少两个不同种类的队列。
// 不对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 :
VkSwapchainKHR swapChain; // 声明if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); }
声明几个变量,我们在接下来的步骤要用到:
std::vector<VkImage> swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; swapChainImageFormat = surfaceFormat.format; swapChainExtent = extent;
swapChain
里的image
随swapChain
一起创建、一起销毁。
获取swapChain
里所有的images
:
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
Image
的清理工作将随着SwapChain的清理而被清理。
源码:
源码太多了,后续源码不再贴在这里。
原文源码地址: source code
CSDN 站内地址: csdn 故障,没传上去。
- vulakn教程--Drawing a Triangle--Presentation--SwapChain
- vulakn教程--Drawing a Triangle--Presentation--Window surface
- vulakn教程--Drawing a Triangle--Presentation-- Image views
- vulakn教程--Drawing a Triangle--Draw--Render and presentation
- vulakn教程--Drawing a Triangle--Pipeline--Introduction
- vulakn教程--Drawing a Triangle--Draw--Framebuffer
- vulakn教程--Drawing a Triangle--Draw--CommandBuffer
- vulakn教程--Drawing a Triangle--Set up--Base code
- vulakn教程--Drawing a Triangle--Set up--Instance
- vulakn教程--Drawing a Triangle--Set up--Validation layers
- vulakn教程--Drawing a Triangle--Set up--Logical Device
- vulakn教程--Drawing a Triangle--Pipeline--Shader Module
- vulakn教程--Drawing a Triangle--Pipeline--Fixed function
- vulakn教程--Drawing a Triangle--Pipeline--Render passes
- vulakn教程--Drawing a Triangle--Set up--Physical Device and Queue Family
- DirectX 教程: DirectX Tutorial - Direct3D: Drawing a Triangle
- Drawing a UIView
- Drawing With A Color
- 每天一个数组函数
- Java 中的 ==, equals 与 hashCode 的区别与联系
- 树莓派3初始化
- 【JAVA笔记——器】Spring Aop 实现Log日志系统——基本实现
- 能容入团队开发成员必备的优秀习惯,提高效率的小技巧
- vulakn教程--Drawing a Triangle--Presentation--SwapChain
- SQL语句:复制表结构以及同时插入多条数据
- zookeeper的重要配置
- JAVA程序签发数字证书
- 混乱
- Android回调机制之Activity与DialogFragment数据传递
- 学习Spring必学的Java基础知识(7)----事务基础知识
- 毕业设计总结篇之开题篇——基于android的创意展示平台(混合app)
- Android布局管理器