Ch6-1 无图像帧缓冲
无图像帧缓冲(imageless framebuffer),是不指定image view的帧缓冲,它仅指定对图像附件的要求,而具体的image view则在渲染通道开始时指定。
无图像帧缓冲的(些微)好处显而易见:
原先,有几个会被用作渲染目标的图像就得有几个帧缓冲,代码量和需要管理的对象数量都显得冗余。而无图像帧缓冲可以适配任何符合要求的图像附件,交换链中的多张图像只需要有一个帧缓冲即可,而且交换链之外的图像只要能创建出符合要求的image view就也能用同一个帧缓冲。
(注:其实这东西卵用不大,直接看下一节也无妨)
本节在之前Ch2中绘制三角形代码的基础上,使用无图像帧缓冲搭配交换链图像进行渲染,以简单演示使用方法。
开启无图像帧缓冲设备特性
无图像帧缓冲是在Vulkan1.1.114版本中引入的功能,Vulkan1.2版本之前由设备级扩展"VK_KHR_imageless_framebuffer"提供(且依赖于其他扩展),从1.2版本开始为Vulkan核心功能的一部分。
取得Vulkan在本机上可用的最高版本,只有1.0.XXX的话直接返回。
Vulkan版本达到1.2的话,在前一节的代码里已经修改过graphicsBase::CreateDevice(...)了,并且该函数的逻辑是“能开的特性都开了”,所以只要检查设备特性是否支持即可:
int main() { graphicsBase::Base().UseLatestApiVersion(); if (graphicsBase::Base().ApiVersion() < VK_API_VERSION_1_1) //1.0.XXX的情况,在这里弹出信息窗口让用户升级显卡驱动吧!? return -1; if (graphicsBase::Base().ApiVersion() < VK_API_VERSION_1_2) { /*1.1.XXX的情况,待填充*/ } else if (!InitializeWindow({ 1280, 720 }) || !graphicsBase::Base().PhysicalDeviceVulkan12Features().imagelessFramebuffer) return -1; /*...*/ }
Note
Vulkan版本和硬件支持
通过vkEnumerateInstanceVersion(...)取得的是Vulkan运行时(runtime)的版本,而非显卡驱动支持到的版本(稍微想想就明白,毕竟物理设备列表都得是创建Vulkan实例之后才取得的)。
运行时可以单独更新,但显卡驱动的支持也得更上。而更新显卡驱动时往往也会一并更新Vulkan运行时,所以如果用户的Vulkan运行时版本古旧,那这家伙的显卡驱动多半是很久没更新过了!
若Vulkan版本达到1.1但未到1.2,使用以下代码通过扩展开启:
int main() { graphicsBase::Base().UseLatestApiVersion(); if (graphicsBase::Base().ApiVersion() < VK_API_VERSION_1_1) return -1; if (graphicsBase::Base().ApiVersion() < VK_API_VERSION_1_2) { graphicsBase::Base().AddDeviceExtension(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME);//Vulkan1.1.XXX需要这个扩展 graphicsBase::Base().AddDeviceExtension(VK_KHR_IMAGELESS_FRAMEBUFFER_EXTENSION_NAME); VkPhysicalDeviceImagelessFramebufferFeatures physicalDeviceImagelessFramebufferFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES, }; graphicsBase::Base().AddNextStructure_PhysicalDeviceFeatures(physicalDeviceImagelessFramebufferFeatures); if (!InitializeWindow({ 1280, 720 }) || !physicalDeviceImagelessFramebufferFeatures.imagelessFramebuffer) return -1; } else if (!InitializeWindow({ 1280, 720 }) || !graphicsBase::Base().PhysicalDeviceVulkan12Features().imagelessFramebuffer) return -1; /*...*/ }
创建无图像帧缓冲
原先是在easyVulkan::CreateRpwf_Screen(...)中创建的帧缓冲,我们就在其基础上进行修改。
先定义renderPassWithFramebuffer结构体,将渲染通道与其对应的帧缓冲放在一起以便于管理:
using namespace vulkan; const VkExtent2D& windowSize = graphicsBase::Base().SwapchainCreateInfo().imageExtent; namespace easyVulkan { struct renderPassWithFramebuffer { renderPass renderPass; framebuffer framebuffer; }; const auto& CreateRpwf_Screen_ImagelessFramebuffer() { static renderPassWithFramebuffer rpwf; /*待后续填充*/ return rpwf; } };
这节不改渲染通道,照抄原先的即可:
const auto& CreateRpwf_Screen_ImagelessFramebuffer() { static renderPassWithFramebuffer rpwf; VkAttachmentDescription attachmentDescription = { .format = graphicsBase::Base().SwapchainCreateInfo().imageFormat, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR }; VkAttachmentReference attachmentReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; VkSubpassDescription subpassDescription = { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, .pColorAttachments = &attachmentReference }; VkSubpassDependency subpassDependency = { .srcSubpass = VK_SUBPASS_EXTERNAL, .dstSubpass = 0, .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, .srcAccessMask = 0, .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT }; VkRenderPassCreateInfo renderPassCreateInfo = { .attachmentCount = 1, .pAttachments = &attachmentDescription, .subpassCount = 1, .pSubpasses = &subpassDescription, .dependencyCount = 1, .pDependencies = &subpassDependency }; rpwf.renderPass.Create(renderPassCreateInfo); /*待后续填充*/ return rpwf; }
创建帧缓冲的部分还是要考虑重建交换链的情况,写lambda并设置为回调:
const auto& CreateRpwf_Screen_ImagelessFramebuffer() { static renderPassWithFramebuffer rpwf; VkAttachmentDescription attachmentDescription = { /*...*/ }; VkAttachmentReference attachmentReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; VkSubpassDescription subpassDescription = { /*...*/ }; VkSubpassDependency subpassDependency = { /*...*/ }; VkRenderPassCreateInfo renderPassCreateInfo = { /*...*/ }; rpwf.renderPass.Create(renderPassCreateInfo); auto CreateFramebuffer = [] { /*待后续填充*/ VkFramebufferCreateInfo framebufferCreateInfo = { .pNext = /*待填充*/, .flags = /*待填充*/, .renderPass = rpwf.renderPass, .attachmentCount = 1, .width = windowSize.width, .height = windowSize.height, .layers = 1 }; rpwf.framebuffer.Create(framebufferCreateInfo); }; auto DestroyFramebuffer = [] { rpwf.framebuffer.~framebuffer(); }; CreateFramebuffer(); ExecuteOnce(rpwf); //防止需重建逻辑设备时,重复添加回调函数 graphicsBase::Base().AddCallback_CreateSwapchain(CreateFramebuffer); graphicsBase::Base().AddCallback_DestroySwapchain(DestroyFramebuffer); return rpwf; }
-
虽然没有图像附件了,VkFramebufferCreateInfo::pAttachments被无视(其值对创建帧缓冲无影响),但VkFramebufferCreateInfo::attachmentCount仍旧需要与渲染通道的图像附件数量匹配。
创建无图像帧缓冲时,VkFramebufferCreateInfo的pNext链中要包含一个VkFramebufferAttachmentsCreateInfo类型的结构体,并在VkFramebufferCreateInfo::flags中指定VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT:
auto CreateFramebuffer = [] { /*待后续填充*/ VkFramebufferAttachmentsCreateInfo framebufferAttachmentsCreateInfo = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENTS_CREATE_INFO, /*待填充*/ }; VkFramebufferCreateInfo framebufferCreateInfo = { .pNext = &framebufferAttachmentsCreateInfo, .flags = VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT, .renderPass = rpwf.renderPass, .attachmentCount = 1, .width = windowSize.width, .height = windowSize.height, .layers = 1 }; rpwf.framebuffer.Create(framebufferCreateInfo); };
struct VkFramebufferAttachmentsCreateInfo 的成员说明 |
|
---|---|
VkStructureType sType |
结构体的类型,本处必须是VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENTS_CREATE_INFO |
const void* pNext |
如有必要,指向一个用于扩展该结构体的结构体 |
uint32_t attachmentImageInfoCount |
被描述的图像附件的数量,跟VkFramebufferCreateInfo::attachmentCount一致 |
const VkFramebufferAttachmentImageInfo* pAttachmentImageInfos |
指向VkFramebufferAttachmentImageInfo的数组,用于描述对图像附件的要求 |
-
根据VUID-VkFramebufferCreateInfo-flags-03191,在attachmentImageInfoCount不等于VkFramebufferCreateInfo::attachmentCount但是为0的情况下验证层不会报错,目前这一点似乎没有什么实际意义(谷歌一圈查不到任何解释)。
-
如果你疑惑pAttachmentImageInfos所指数组中各个元素该是什么顺序,前情提要:渲染通道中与图像附件相关的参数(格式、采样点数、存读选项、内存布局)大都是在VkRenderPassCreateInfo::pAttachments中指定的,原先由VkFramebufferCreateInfo::pAttachments指定与之一一对应的具体图像附件。对于无图像帧缓冲自然是以此类推。
Note
Q:为什么要让attachmentImageInfoCount跟VkFramebufferCreateInfo::attachmentCount重复提供同一个数值?
A:我猜是因为API设计得考虑到这个结构今后可能在其他场合被单独使用(至少到撰写本文为止,VkFramebufferAttachmentsCreateInfo只会被VkFramebufferCreateInfo引用)。
struct VkFramebufferAttachmentImageInfo 的成员说明 |
|
---|---|
VkStructureType sType |
结构体的类型,本处必须是VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENT_IMAGE_INFO |
const void* pNext |
如有必要,指向一个用于扩展该结构体的结构体 |
VkImageCreateFlags flags |
届时的图像附件,其对应的图像(VkImage)在被创建时被指定的flags |
VkImageUsageFlags usage |
届时的图像附件,其对应的图像(VkImage)在被创建时被指定的用途 |
uint32_t width |
届时的图像附件的宽度 |
uint32_t height |
届时的图像附件的高度 |
uint32_t layerCount |
届时会被指定为图像附件的图像视图(VkImageView)的图层数 |
uint32_t viewFormatCount |
见后文 |
const VkFormat* pViewFormats |
见后文 |
-
flags和usage与创建相应图像时指定的VkImageCreateInfo::flags和VkImageCreateInfo::usage匹配。
-
届时的图像附件,其对应的图像在被创建时,如果VkImageCreateInfo的pNext链中包含了VkImageFormatListCreateInfo,那么这里的pViewFormats所指数组为与VkImageFormatListCreateInfo中相同的格式列表,否则指向届时会被指定为图像附件的image view的格式即可(等价于创建渲染通道过程中指定的图像格式)。
VkImageFormatListCreateInfo是干什么的?
要讲这个,先前情提要一下可变格式图像:创建图像时,若VkImageCreateInfo::flags中注明VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,则之后可以为图像创建不同格式的image view。
但是在一些平台上,可变格式图像的访问效率可能较差,因为驱动不清楚可能会以哪些格式的view来访问图像,就可能失去优化机会。
因此后来就有了VkImageFormatListCreateInfo,该类型结构体用以注明之后会为图像创建的image view会是哪些格式。
至于如果你要问,创建帧缓冲的时候注明这个干啥?底层的事情我也不知道!
于是来创建能与交换链图像搭配的无图像帧缓冲。
flags和usage怎么填?我们不是保存了交换链的创建信息嘛:
auto CreateFramebuffer = [] { VkFramebufferAttachmentImageInfo framebufferAttachmentImageInfo = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENT_IMAGE_INFO, .usage = graphicsBase::Base().SwapchainCreateInfo().imageUsage, .width = /*待填充*/, .height = /*待填充*/, .layerCount = /*待填充*/, .viewFormatCount = /*待填充*/, .pViewFormats = /*待填充*/ }; VkFramebufferAttachmentsCreateInfo framebufferAttachmentsCreateInfo = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENTS_CREATE_INFO, .attachmentImageInfoCount = 1, .pAttachmentImageInfos = &framebufferAttachmentImageInfo }; VkFramebufferCreateInfo framebufferCreateInfo = { .pNext = &framebufferAttachmentsCreateInfo, .flags = VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT, .renderPass = rpwf.renderPass, .attachmentCount = 1, .width = windowSize.width, .height = windowSize.height, .layers = 1 }; rpwf.framebuffer.Create(framebufferCreateInfo); };
然而突然意识到,VkSwapchainCreateInfo根本没有VkImageCreateFlags类型的成员!
好在我们当初创建交换链的时候也没有注明什么flags,这里的flags留作0就行了。。。
哎呀这可治标不治本,给你个VkSwapchainCreateFlagBitsKHR到VkImageCreateFlagBits的转换列表吧:
版本要求 |
||
---|---|---|
1.1 + VK_KHR_swapchain |
VK_SWAPCHAIN_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT_KHR |
VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT |
1.1 + VK_KHR_swapchain |
VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR |
VK_IMAGE_CREATE_PROTECTED_BIT |
VK_KHR_swapchain_mutable_format |
VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR |
VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT (出处) |
然后填写宽高和图层数。
图像附件是通过image view指定的,我们没有存交换链图像的image view的创建信息,但创建view时根本不会指定尺寸,view的尺寸等价于与其对应的图像。至于图层数,当然是1啦:
VkFramebufferAttachmentImageInfo framebufferAttachmentImageInfo = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENT_IMAGE_INFO, .usage = graphicsBase::Base().SwapchainCreateInfo().imageUsage, //前情提要:windowSize即graphicsBase::Base().SwapchainCreateInfo().imageExtent .width = windowSize.width, .height = windowSize.height, .layerCount = 1, .viewFormatCount = /*待填充*/ .pViewFormats = /*待填充*/ }; if (graphicsBase::Base().SwapchainCreateInfo().flags & VK_SWAPCHAIN_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT_KHR) framebufferAttachmentImageInfo.flags |= VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT; if (graphicsBase::Base().SwapchainCreateInfo().flags & VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR) framebufferAttachmentImageInfo.flags |= VK_IMAGE_CREATE_PROTECTED_BIT; if (graphicsBase::Base().SwapchainCreateInfo().flags & VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR) framebufferAttachmentImageInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT;
最后,既然我们没有把交换链图像指定为可变格式,更遑论写没写过VkImageFormatListCreateInfo,只要让pViewFormats指向交换链创建信息中的imageFormat便好:
VkFramebufferAttachmentImageInfo framebufferAttachmentImageInfo = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENT_IMAGE_INFO, .usage = graphicsBase::Base().SwapchainCreateInfo().imageUsage, .width = windowSize.width, .height = windowSize.height, .layerCount = 1, .viewFormatCount = 1, .pViewFormats = &graphicsBase::Base().SwapchainCreateInfo().imageFormat }; if (graphicsBase::Base().SwapchainCreateInfo().flags & VK_SWAPCHAIN_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT_KHR) framebufferAttachmentImageInfo.flags |= VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT; if (graphicsBase::Base().SwapchainCreateInfo().flags & VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR) framebufferAttachmentImageInfo.flags |= VK_IMAGE_CREATE_PROTECTED_BIT; if (graphicsBase::Base().SwapchainCreateInfo().flags & VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR) framebufferAttachmentImageInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT;
开始渲染通道
使用无图像帧缓冲的话,需要在开始渲染通道时,在VkRenderPassBeginInfo的pNext链中用VkRenderPassAttachmentBeginInfo指定具体的图像附件(没有任何难点):
struct VkRenderPassAttachmentBeginInfo 的成员说明 |
|
---|---|
VkStructureType sType |
结构体的类型,本处必须是VK_STRUCTURE_TYPE_RENDER_PASS_ATTACHMENT_BEGIN_INFO |
const void* pNext |
如有必要,指向一个用于扩展该结构体的结构体 |
uint32_t attachmentCount |
图像附件的数量 |
VkImageView pAttachments |
指向VkImageView的数组,用于指定图像附件 |
int main() { if (!InitializeWindow({1280,720})) return -1; const auto& [renderPass, framebuffer] = RenderPassAndFramebuffers(); CreateLayout(); CreatePipeline(); fence fence; semaphore semaphore_imageIsAvailable; semaphore semaphore_renderingIsOver; commandBuffer commandBuffer; commandPool commandPool(graphicsBase::Base().QueueFamilyIndex_Graphics(), VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT); commandPool.AllocateBuffers(commandBuffer); VkClearValue clearColor = { .color = { 1.f, 0.f, 0.f, 1.f } }; while (!glfwWindowShouldClose(pWindow)) { while (glfwGetWindowAttrib(pWindow, GLFW_ICONIFIED)) glfwWaitEvents(); graphicsBase::Base().SwapImage(semaphore_imageIsAvailable); auto i = graphicsBase::Base().CurrentImageIndex(); commandBuffer.Begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT); VkImageView attachment = graphicsBase::Base().SwapchainImageView(i); VkRenderPassAttachmentBeginInfo renderPassAttachmentBeginInfo = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_ATTACHMENT_BEGIN_INFO, .attachmentCount = 1, .pAttachments = &attachment }; VkRenderPassBeginInfo renderPassBeginInfo = { .pNext = &renderPassAttachmentBeginInfo, .framebuffer = framebuffer, .renderArea = { {}, windowSize }, .clearValueCount = 1, .pClearValues = &clearColor }; renderPass.CmdBegin(commandBuffer, renderPassBeginInfo); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_triangle); vkCmdDraw(commandBuffer, 3, 1, 0, 0); renderPass.CmdEnd(commandBuffer); commandBuffer.End(); graphicsBase::Base().SubmitCommandBuffer_Graphics(commandBuffer, semaphore_imageIsAvailable, semaphore_renderingIsOver, fence); graphicsBase::Base().PresentImage(semaphore_renderingIsOver); glfwPollEvents(); TitleFps(); fence.WaitAndReset(); } TerminateWindow(); return 0; }
运行程序,渲染的结果不是重点(依旧是那个红背景和蓝三角形),验证层没报错便对了。
本节的示例代码参见:EasyVulkan.hpp和Ch6-1.hpp