您的位置:首页 > 其它

vulakn教程--Drawing a Triangle--Draw--Render and presentation

2016-09-21 23:11 549 查看
原文地址 : vulkan-tutorial

Rendering and presentation

接下来我们将使用
drawFrame()
函数将三角形显示到屏幕上。

drawFrame()要做如下几件事:

从Swap Chain 请求一个image。

执行带有这个image的command buffer ,这个image曾被当做attachment存储在framebuffer中(Execute the command buffer with that image as attachment in the framebuffer)。

将image 返回到swap chain 等待显示。

虽然所有的操作都在一个函数里运行,但是它们的执行却是异步的,这个函数调用在它真正完成任务前就会返回,并且函数内部各个操作的执行顺序也不是确定的。这就产生了一个问题,因为我们希望后一个操作在前一个操作完成后才进行。

在Vulkan中可以使用两种方法进行同步:
fences
semaphore
,他们都能够使一个操作发送信号(signal),另一个操作等待(wait)
fence
或者
semaphore
, 最终使的
fence
semaphore
unginaled
状态变为
signaled
状态。所不同的是
fence
可以在程序中使用
vkWaitForFences()
来获取状态,而
semaphore
则不可以。
Fence
主要在渲染操作时同步应用自身(synchronize your application itself with rendering operation),而
semaphore
被设计为同步一个或跨多个命令队列工作。而我们想要同步绘画命令的队列操作和显示命令的队列操作,所以
semaphore
更为适合。

// image 已经得到,可以被渲染了
VDeleter<VkSemaphore> imageAvailableSemaphore {device, vkDestroySemaphore};
// image 渲染完毕可以被提交显示了
VDeleter<VkSemaphore> renderFinishedSemaphore {device, vkDestroySemaphore};


添加创建semaphore的函数:

void createSemaphores() {
VkSemaphoreCreateInfo semaphoreInfo = {};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;

if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS ||
vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS) {

throw std::runtime_error("failed to create semaphores!");
}
}


Acquiring an image from the swap chain

drawFrame
函数的第一步是要获取swap chain里的image,因为swap chain是一个扩展功能,所以需要使用
vk***KHR
命名约定:

void drawFrame() {
uint32_t imageIndex;
vkAcquireNextImageKHR(device, swapChain,
std::numeric_limits<uint64_t>::max(),
imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);
}


让我们来看看
vkAcquireNextImageKHR(…)
:

VkResult vkAcquireNextImageKHR(
VkDevice device,
VkSwapchainKHR swapchain,
uint64_t timeout,
VkSemaphore semaphore,
VkFence fence,
uint32_t* pImageIndex);


timeout
表示等待时间,单位纳秒,如果
timeout=0,
函数不被阻塞,立刻返回:成功或者
VK_NOT_READY
。使用64位整数的最大值表示一直等待,直到获得数据。同步既可以使用
semaphore
也可以使用
fence
,或者两者一起。
pImageIndex
表示可使用的Image索引,对应于
swapChainImages
数组,我们将用这个索引来选择合适的Command buffer 。

Submitting the command buffer

通过
VkSubmitInfo
来配置Command buffer 提交到队列和同步控制:

VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;

VkSemaphore waitSemaphores[] = {imageAvailableSemaphore};
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;


waitStages
表示pipeline将在何处等待,我希望当image 可以访问时,将颜色写入image,所以定义stage为pipeline的写color atatchement.

submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffers[imageIndex];


提交哪一个command buffer 运行。

VkSemaphore signalSemaphores[] = {renderFinishedSemaphore};
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;


当Command buffer 执行完成后,哪一个semaphore发送信号(signal).

提交Command buffer 到队列的函数:

VkResult vkQueueSubmit(
VkQueue queue, //队列类型
uint32_t submitCount, //submitInfo数目
const VkSubmitInfo* pSubmits,
VkFence fence); //执行完时发送信号


现在提交我们的Command buffer 到图形队列:

if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
throw std::runtime_error("failed to submit draw command buffer!");
}


Subpass dependencies

不知你是否记得,Render Pass 中的
subpass
自动处理image (attachment)的layout转换,这些转换被subpass 依赖(subpass dependencies) 所控制,它指定了
subpass
间的内存和执行依赖,虽然我们只有一个subpass,但是在执行这个subpass的前后操作也被隐式的当做subpass了。

有两个内置的依赖(built-in dependencies)控制render pass前和render pass后的转换,前者出现的时机并不正确,因为它假定render pass前的转换发生在pipeline开始的时候,但是这个时候我们还没有获得image呢!因为image是在
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
阶段(stage)才获得的。所以我们必须重写/覆盖这个依赖。

现在回到
createRenderPass
中。

Vulkan 使用
VkSubpassDependency
来描述这些依赖:

VkSubpassDependency dependency = {};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0;


srcSubpass
dstSubpass
分别表示依赖的索引和从属的subpass(即生产者与消费者的索引).
VK_SUBPASS_EXTERNAL
代表Render pass 前或后的隐含的subpass,这取决于
VK_S**_EX**L
是被定义在src还是dst。索引0指向我们定义的第一个同时也是唯一的一个subpass。
dst
必须大于
src
以防止循环依赖。

dependency.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
dependency.srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;


这两个字段分别定义了我们将在什么操作上等待以及这个操作在何种阶段发生。我们必须等待Swap Chain从image读完之后才能访问它,这个操作发生在pipeline的最后阶段。

dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;


读和写 color attachment 操作必须在
_COLOR_ATTACHMENT_
阶段进行等待,这些设置保证:除非必要(如:当我们真的想写颜色(color)的时候)否则转换将不会发生。

现在让我们回到创建Render Pass阶段,修改
renderPassInfo


renderPassInfo.dependencyCount = 1;
renderPassInfo.pDependencies = &dependency;


Presentation

绘画帧(drawing a frame)的最后一步就是把绘画结果返回到Swap Chain里,以致最终显示到屏幕上。显示(Presentation) 通过
VkPresentationKHR
来配置:

VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;


pWaitSemaphores
表示显示前需要等待的信号量(semaphore),如同
VkSubmitInfo


VkSwapchainKHR swapChains[] = {swapChain};
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;


pSwapchains
表示将要把image 提交到的Swap Chain的数组。
pImageIndices
表示要提交到Swap Chain的image索引数组,总是一个数据(
pImageIndices
中的每一个元素对应
pSwapchains
中的每一个元素)。

presentInfo.pResults = nullptr; // Optional


这是最后一个字段,表示每个Swap Chain的显示(Presentation)结果是否正确,它是一个
VkResult
数组,因为我们只有一个Swap Chain和一个image,所以通过函数返回值就能判断了。

vkQueuePresentKHR(presentQueue, &presentInfo);


vkQueuePresentKHR
提交显示请求。

之后我们要为
vkAcquireNextImageKHR
vkQueuePresentKHR
添加错误控制方法,因为他们的失败并不意味着程序必须终止。

运行程序你将看到 :



如果你的Validation Layers 可用的话,当关闭窗口时就会在控制台收到从
debugCallback
返回的debug信息:



因为
drawFrame
里的操作是异步的,这就意味着当我们关掉主循环(
mainLoop()
函数)时,绘图操作和显示操作可能还在进行,这个时候剥夺它们的资源是不明智的。

为了修正这个错误,可以在主
mainLoop()
退出前先等待Logical Device 结束:

void mainLoop() {
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
drawFrame();
}

vkDeviceWaitIdle(device);
}


同样也可以使用
vkQueueWaitIdle
来等待具体某个命令队列里的的某个操作的结束。

Conclusion

我们用了800多行的代码才完成了使一个简单的三角形显示到屏幕上的功能,这确实是一个巨大的工作,但也从侧面反映了一个事实:Vulakn 给你提供了更多显示控制硬件的权利。 我建议你花费一些时间重读这些代码,在脑中构想出一幅模型图画,想一想每个Vulkan对象的作用以及它们之间的联系。

源码 : source code
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息